blob: 3d23f10d8da190e7a76e71e8b6e81f2f5808e2be [file] [log] [blame] [edit]
/*
* Copyright (c) 2021-2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "Device.h"
#import "API.h"
#import "APIConversions.h"
#import "BindGroup.h"
#import "BindGroupLayout.h"
#import "Buffer.h"
#import "CommandEncoder.h"
#import "ComputePipeline.h"
#import "PipelineLayout.h"
#import "PresentationContext.h"
#import "QuerySet.h"
#import "Queue.h"
#import "RenderBundleEncoder.h"
#import "RenderPipeline.h"
#import "Sampler.h"
#import "ShaderModule.h"
#import "Texture.h"
#import <algorithm>
#import <notify.h>
#import <wtf/StdLibExtras.h>
#import <wtf/WeakPtr.h>
namespace WebGPU {
struct GPUFrameCapture {
static void captureSingleFrameIfNeeded(id<MTLDevice> captureObject)
{
if (enabled) {
captureFrame(captureObject);
enabled = false;
}
}
static void registerForFrameCapture(id<MTLDevice> captureObject)
{
// Allow GPU frame capture "notifyutil -p com.apple.WebKit.WebGPU.CaptureFrame" when process is
// run with __XPC_METAL_CAPTURE_ENABLED=1
// notifyutil -s com.apple.WebKit.WebGPU.CaptureFrame 10 --> captures 10 GPUQueue.submit calls
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
int captureFrameToken;
notify_register_dispatch("com.apple.WebKit.WebGPU.CaptureFrame", &captureFrameToken, dispatch_get_main_queue(), ^(int token) {
uint64_t state;
notify_get_state(token, &state);
maxSubmitCallsToCapture = std::max<int>(1, state);
enabled = true;
});
int captureFirstFrameToken;
notify_register_dispatch("com.apple.WebKit.WebGPU.ToggleCaptureFirstFrame", &captureFirstFrameToken, dispatch_get_main_queue(), ^(int) {
captureFirstFrame = !captureFirstFrame;
});
});
if (captureFirstFrame)
captureFrame(captureObject);
}
static bool shouldStopCaptureAfterSubmit()
{
++submitCallsCaptured;
auto result = submitCallsCaptured >= maxSubmitCallsToCapture;
if (result)
submitCallsCaptured = 0;
return result;
}
private:
static void captureFrame(id<MTLDevice> captureObject)
{
MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager];
if ([captureManager isCapturing])
return;
MTLCaptureDescriptor* captureDescriptor = [[MTLCaptureDescriptor alloc] init];
captureDescriptor.captureObject = captureObject;
captureDescriptor.destination = MTLCaptureDestinationGPUTraceDocument;
captureDescriptor.outputURL = [[NSFileManager.defaultManager temporaryDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.gputrace", NSUUID.UUID.UUIDString]];
NSError *error;
if (![captureManager startCaptureWithDescriptor:captureDescriptor error:&error])
WTFLogAlways("Failed to start GPU frame capture at path %@, error %@", captureDescriptor.outputURL.absoluteString, error);
else
WTFLogAlways("Success starting GPU frame capture at path %@", captureDescriptor.outputURL.absoluteString);
}
static bool captureFirstFrame;
static bool enabled;
static int submitCallsCaptured;
static int maxSubmitCallsToCapture;
};
bool GPUFrameCapture::captureFirstFrame = false;
bool GPUFrameCapture::enabled = false;
int GPUFrameCapture::submitCallsCaptured = 0;
int GPUFrameCapture::maxSubmitCallsToCapture = 1;
bool Device::shouldStopCaptureAfterSubmit()
{
return GPUFrameCapture::shouldStopCaptureAfterSubmit();
}
bool Device::isDestroyed() const
{
return m_destroyed;
}
Ref<Device> Device::create(id<MTLDevice> device, String&& deviceLabel, HardwareCapabilities&& capabilities, Adapter& adapter)
{
id<MTLCommandQueue> commandQueue = [device newCommandQueue];
if (!commandQueue)
return Device::createInvalid(adapter);
// See the comment in Device::setLabel() about why we're not setting the label on the MTLDevice here.
commandQueue.label = @"Default queue";
if (!deviceLabel.isEmpty())
commandQueue.label = [NSString stringWithFormat:@"Default queue for device %s", deviceLabel.utf8().data()];
return adoptRef(*new Device(device, commandQueue, WTFMove(capabilities), adapter));
}
Device::Device(id<MTLDevice> device, id<MTLCommandQueue> defaultQueue, HardwareCapabilities&& capabilities, Adapter& adapter)
: m_device(device)
, m_defaultQueue(Queue::create(defaultQueue, *this))
, m_capabilities(WTFMove(capabilities))
, m_adapter(adapter)
{
#if PLATFORM(MAC)
auto devices = MTLCopyAllDevicesWithObserver(&m_deviceObserver, [weakThis = ThreadSafeWeakPtr { *this }](id<MTLDevice> device, MTLDeviceNotificationName) {
RefPtr<Device> protectedThis = weakThis.get();
if (!protectedThis)
return;
auto& instance = protectedThis->instance();
instance.scheduleWork([protectedThis = WTFMove(protectedThis), device = device]() {
if (![protectedThis->m_device isEqual:device])
return;
protectedThis->loseTheDevice(WGPUDeviceLostReason_Undefined);
});
});
#if ASSERT_ENABLED
bool found = false;
for (id<MTLDevice> observedDevice in devices) {
if ([observedDevice isEqual:device]) {
found = true;
break;
}
}
ASSERT(found);
#else
UNUSED_VARIABLE(devices);
#endif
#endif
#if HAVE(COREVIDEO_METAL_SUPPORT)
CVMetalTextureCacheRef coreVideoTextureCache;
CVReturn result = CVMetalTextureCacheCreate(nullptr, nullptr, device, nullptr, &coreVideoTextureCache);
ASSERT_UNUSED(result, result == kCVReturnSuccess);
m_coreVideoTextureCache = coreVideoTextureCache;
#endif
GPUFrameCapture::registerForFrameCapture(m_device);
m_placeholderBuffer = safeCreateBuffer(1, MTLStorageModeShared);
auto desc = [MTLTextureDescriptor new];
desc.width = 1;
desc.height = 1;
desc.mipmapLevelCount = 1;
desc.pixelFormat = MTLPixelFormatR8Unorm;
desc.textureType = MTLTextureType2D;
desc.storageMode = MTLStorageModeShared;
desc.usage = MTLTextureUsageShaderRead;
m_placeholderTexture = [m_device newTextureWithDescriptor:desc];
}
Device::Device(Adapter& adapter)
: m_defaultQueue(Queue::createInvalid(*this))
, m_adapter(adapter)
{
if (!m_adapter->isValid())
makeInvalid();
}
Device::~Device()
{
#if PLATFORM(MAC)
MTLRemoveDeviceObserver(m_deviceObserver);
#endif
if (m_deviceLostCallback) {
m_deviceLostCallback(WGPUDeviceLostReason_Destroyed, ""_s);
m_deviceLostCallback = nullptr;
}
}
void Device::loseTheDevice(WGPUDeviceLostReason reason)
{
// https://gpuweb.github.io/gpuweb/#lose-the-device
m_adapter->makeInvalid();
makeInvalid();
if (m_deviceLostCallback) {
m_deviceLostCallback(reason, "Device lost."_s);
m_deviceLostCallback = nullptr;
}
// FIXME: The spec doesn't actually say to do this, but it's pretty important because
// the total number of command queues alive at a time is limited to a pretty low limit.
// We should make sure either that this is unobservable or that the spec says to do this.
m_defaultQueue->makeInvalid();
m_isLost = true;
}
void Device::destroy()
{
m_destroyed = true;
loseTheDevice(WGPUDeviceLostReason_Destroyed);
}
size_t Device::enumerateFeatures(WGPUFeatureName* features)
{
// The API contract for this requires that sufficient space has already been allocated for the output.
// This requires the caller calling us twice: once to get the amount of space to allocate, and once to fill the space.
if (features)
std::copy(m_capabilities.features.begin(), m_capabilities.features.end(), features);
return m_capabilities.features.size();
}
bool Device::getLimits(WGPUSupportedLimits& limits)
{
if (limits.nextInChain != nullptr)
return false;
limits.limits = m_capabilities.limits;
return true;
}
id<MTLBuffer> Device::placeholderBuffer() const
{
return m_placeholderBuffer;
}
id<MTLTexture> Device::placeholderTexture() const
{
return m_placeholderTexture;
}
Queue& Device::getQueue()
{
return m_defaultQueue;
}
bool Device::hasFeature(WGPUFeatureName feature) const
{
return std::find(m_capabilities.features.begin(), m_capabilities.features.end(), feature);
}
auto Device::currentErrorScope(WGPUErrorFilter type) -> ErrorScope*
{
// https://gpuweb.github.io/gpuweb/#abstract-opdef-current-error-scope
for (auto iterator = m_errorScopeStack.rbegin(); iterator != m_errorScopeStack.rend(); ++iterator) {
if (iterator->filter == type)
return &*iterator;
}
return nullptr;
}
void Device::generateAValidationError(String&& message)
{
// https://gpuweb.github.io/gpuweb/#abstract-opdef-generate-a-validation-error
auto* scope = currentErrorScope(WGPUErrorFilter_Validation);
if (scope) {
if (!scope->error)
scope->error = Error { WGPUErrorType_Validation, WTFMove(message) };
return;
}
if (m_uncapturedErrorCallback)
m_uncapturedErrorCallback(WGPUErrorType_Validation, WTFMove(message));
}
void Device::generateAnOutOfMemoryError(String&& message)
{
// https://gpuweb.github.io/gpuweb/#abstract-opdef-generate-an-out-of-memory-error
auto* scope = currentErrorScope(WGPUErrorFilter_OutOfMemory);
if (scope) {
if (!scope->error)
scope->error = Error { WGPUErrorType_OutOfMemory, WTFMove(message) };
return;
}
if (m_uncapturedErrorCallback)
m_uncapturedErrorCallback(WGPUErrorType_OutOfMemory, WTFMove(message));
}
void Device::generateAnInternalError(String&& message)
{
// https://gpuweb.github.io/gpuweb/#abstract-opdef-generate-an-internal-error
auto* scope = currentErrorScope(WGPUErrorFilter_Internal);
if (scope) {
if (!scope->error)
scope->error = Error { WGPUErrorType_Internal, WTFMove(message) };
return;
}
if (m_uncapturedErrorCallback)
m_uncapturedErrorCallback(WGPUErrorType_Internal, WTFMove(message));
}
uint32_t Device::maxBuffersPlusVertexBuffersForVertexStage() const
{
ASSERT(m_capabilities.limits.maxBindGroupsPlusVertexBuffers > 0);
return m_capabilities.limits.maxBindGroupsPlusVertexBuffers;
}
uint32_t Device::maxBuffersForFragmentStage() const
{
return m_capabilities.limits.maxBindGroups;
}
uint32_t Device::maxBuffersForComputeStage() const
{
return m_capabilities.limits.maxBindGroups;
}
uint32_t Device::vertexBufferIndexForBindGroup(uint32_t groupIndex) const
{
ASSERT(maxBuffersPlusVertexBuffersForVertexStage() > 0);
return WGSL::vertexBufferIndexForBindGroup(groupIndex, maxBuffersPlusVertexBuffersForVertexStage() - 1);
}
void Device::captureFrameIfNeeded() const
{
GPUFrameCapture::captureSingleFrameIfNeeded(m_device);
}
std::optional<WGPUErrorType> Device::validatePopErrorScope() const
{
if (m_isLost)
return WGPUErrorType_NoError;
if (m_errorScopeStack.isEmpty())
return WGPUErrorType_Unknown;
return std::nullopt;
}
bool Device::popErrorScope(CompletionHandler<void(WGPUErrorType, String&&)>&& callback)
{
// https://gpuweb.github.io/gpuweb/#dom-gpudevice-poperrorscope
if (auto errorType = validatePopErrorScope()) {
callback(*errorType, "popErrorScope() failed validation."_s);
return false;
}
auto scope = m_errorScopeStack.takeLast();
instance().scheduleWork([scope = WTFMove(scope), callback = WTFMove(callback)]() mutable {
if (scope.error)
callback(scope.error->type, WTFMove(scope.error->message));
else
callback(WGPUErrorType_NoError, { });
});
// FIXME: Make sure this is the right thing to return.
return true;
}
void Device::pushErrorScope(WGPUErrorFilter filter)
{
// https://gpuweb.github.io/gpuweb/#dom-gpudevice-pusherrorscope
ErrorScope scope { std::nullopt, filter };
m_errorScopeStack.append(WTFMove(scope));
}
void Device::setDeviceLostCallback(Function<void(WGPUDeviceLostReason, String&&)>&& callback)
{
m_deviceLostCallback = WTFMove(callback);
if (m_isLost)
loseTheDevice(WGPUDeviceLostReason_Destroyed);
else if (!m_adapter->isValid())
loseTheDevice(WGPUDeviceLostReason_Undefined);
}
bool Device::isValid() const
{
return m_device;
}
void Device::setUncapturedErrorCallback(Function<void(WGPUErrorType, String&&)>&& callback)
{
m_uncapturedErrorCallback = WTFMove(callback);
}
void Device::setLabel(String&&)
{
// Because MTLDevices are process-global, we can't set the label on it, because 2 contexts' labels would fight each other.
}
} // namespace WebGPU
#pragma mark WGPU Stubs
void wgpuDeviceReference(WGPUDevice device)
{
WebGPU::fromAPI(device).ref();
}
void wgpuDeviceRelease(WGPUDevice device)
{
WebGPU::fromAPI(device).deref();
}
WGPUBindGroup wgpuDeviceCreateBindGroup(WGPUDevice device, const WGPUBindGroupDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createBindGroup(*descriptor));
}
WGPUBindGroupLayout wgpuDeviceCreateBindGroupLayout(WGPUDevice device, const WGPUBindGroupLayoutDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createBindGroupLayout(*descriptor));
}
WGPUBuffer wgpuDeviceCreateBuffer(WGPUDevice device, const WGPUBufferDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createBuffer(*descriptor));
}
WGPUCommandEncoder wgpuDeviceCreateCommandEncoder(WGPUDevice device, const WGPUCommandEncoderDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createCommandEncoder(*descriptor));
}
WGPUComputePipeline wgpuDeviceCreateComputePipeline(WGPUDevice device, const WGPUComputePipelineDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createComputePipeline(*descriptor));
}
void wgpuDeviceCreateComputePipelineAsync(WGPUDevice device, const WGPUComputePipelineDescriptor* descriptor, WGPUCreateComputePipelineAsyncCallback callback, void* userdata)
{
WebGPU::fromAPI(device).createComputePipelineAsync(*descriptor, [callback, userdata](WGPUCreatePipelineAsyncStatus status, Ref<WebGPU::ComputePipeline>&& pipeline, String&& message) {
callback(status, WebGPU::releaseToAPI(WTFMove(pipeline)), message.utf8().data(), userdata);
});
}
void wgpuDeviceCreateComputePipelineAsyncWithBlock(WGPUDevice device, WGPUComputePipelineDescriptor const * descriptor, WGPUCreateComputePipelineAsyncBlockCallback callback)
{
WebGPU::fromAPI(device).createComputePipelineAsync(*descriptor, [callback = WebGPU::fromAPI(WTFMove(callback))](WGPUCreatePipelineAsyncStatus status, Ref<WebGPU::ComputePipeline>&& pipeline, String&& message) {
callback(status, WebGPU::releaseToAPI(WTFMove(pipeline)), message.utf8().data());
});
}
WGPUPipelineLayout wgpuDeviceCreatePipelineLayout(WGPUDevice device, const WGPUPipelineLayoutDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createPipelineLayout(*descriptor));
}
WGPUQuerySet wgpuDeviceCreateQuerySet(WGPUDevice device, const WGPUQuerySetDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createQuerySet(*descriptor));
}
WGPURenderBundleEncoder wgpuDeviceCreateRenderBundleEncoder(WGPUDevice device, const WGPURenderBundleEncoderDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createRenderBundleEncoder(*descriptor));
}
WGPURenderPipeline wgpuDeviceCreateRenderPipeline(WGPUDevice device, const WGPURenderPipelineDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createRenderPipeline(*descriptor));
}
void wgpuDeviceCreateRenderPipelineAsync(WGPUDevice device, const WGPURenderPipelineDescriptor* descriptor, WGPUCreateRenderPipelineAsyncCallback callback, void* userdata)
{
WebGPU::fromAPI(device).createRenderPipelineAsync(*descriptor, [callback, userdata](WGPUCreatePipelineAsyncStatus status, Ref<WebGPU::RenderPipeline>&& pipeline, String&& message) {
callback(status, WebGPU::releaseToAPI(WTFMove(pipeline)), message.utf8().data(), userdata);
});
}
void wgpuDeviceCreateRenderPipelineAsyncWithBlock(WGPUDevice device, WGPURenderPipelineDescriptor const * descriptor, WGPUCreateRenderPipelineAsyncBlockCallback callback)
{
WebGPU::fromAPI(device).createRenderPipelineAsync(*descriptor, [callback = WebGPU::fromAPI(WTFMove(callback))](WGPUCreatePipelineAsyncStatus status, Ref<WebGPU::RenderPipeline>&& pipeline, String&& message) {
callback(status, WebGPU::releaseToAPI(WTFMove(pipeline)), message.utf8().data());
});
}
WGPUSampler wgpuDeviceCreateSampler(WGPUDevice device, const WGPUSamplerDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createSampler(*descriptor));
}
WGPUExternalTexture wgpuDeviceImportExternalTexture(WGPUDevice device, const WGPUExternalTextureDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createExternalTexture(*descriptor));
}
WGPUShaderModule wgpuDeviceCreateShaderModule(WGPUDevice device, const WGPUShaderModuleDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createShaderModule(*descriptor));
}
WGPUSwapChain wgpuDeviceCreateSwapChain(WGPUDevice device, WGPUSurface surface, const WGPUSwapChainDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createSwapChain(WebGPU::fromAPI(surface), *descriptor));
}
WGPUTexture wgpuDeviceCreateTexture(WGPUDevice device, const WGPUTextureDescriptor* descriptor)
{
return WebGPU::releaseToAPI(WebGPU::fromAPI(device).createTexture(*descriptor));
}
void wgpuDeviceDestroy(WGPUDevice device)
{
WebGPU::fromAPI(device).destroy();
}
size_t wgpuDeviceEnumerateFeatures(WGPUDevice device, WGPUFeatureName* features)
{
return WebGPU::fromAPI(device).enumerateFeatures(features);
}
WGPUBool wgpuDeviceGetLimits(WGPUDevice device, WGPUSupportedLimits* limits)
{
return WebGPU::fromAPI(device).getLimits(*limits);
}
WGPUQueue wgpuDeviceGetQueue(WGPUDevice device)
{
return &WebGPU::fromAPI(device).getQueue();
}
WGPUBool wgpuDeviceHasFeature(WGPUDevice device, WGPUFeatureName feature)
{
return WebGPU::fromAPI(device).hasFeature(feature);
}
void wgpuDevicePopErrorScope(WGPUDevice device, WGPUErrorCallback callback, void* userdata)
{
WebGPU::fromAPI(device).popErrorScope([callback, userdata](WGPUErrorType type, String&& message) {
callback(type, message.utf8().data(), userdata);
});
}
void wgpuDevicePopErrorScopeWithBlock(WGPUDevice device, WGPUErrorBlockCallback callback)
{
WebGPU::fromAPI(device).popErrorScope([callback = WebGPU::fromAPI(WTFMove(callback))](WGPUErrorType type, String&& message) {
callback(type, message.utf8().data());
});
}
void wgpuDevicePushErrorScope(WGPUDevice device, WGPUErrorFilter filter)
{
WebGPU::fromAPI(device).pushErrorScope(filter);
}
void wgpuDeviceSetDeviceLostCallback(WGPUDevice device, WGPUDeviceLostCallback callback, void* userdata)
{
return WebGPU::fromAPI(device).setDeviceLostCallback([callback, userdata](WGPUDeviceLostReason reason, String&& message) {
if (callback)
callback(reason, message.utf8().data(), userdata);
});
}
void wgpuDeviceSetDeviceLostCallbackWithBlock(WGPUDevice device, WGPUDeviceLostBlockCallback callback)
{
return WebGPU::fromAPI(device).setDeviceLostCallback([callback = WebGPU::fromAPI(WTFMove(callback))](WGPUDeviceLostReason reason, String&& message) {
if (callback)
callback(reason, message.utf8().data());
});
}
void wgpuDeviceSetUncapturedErrorCallback(WGPUDevice device, WGPUErrorCallback callback, void* userdata)
{
return WebGPU::fromAPI(device).setUncapturedErrorCallback([callback, userdata](WGPUErrorType type, String&& message) {
if (callback)
callback(type, message.utf8().data(), userdata);
});
}
void wgpuDeviceSetUncapturedErrorCallbackWithBlock(WGPUDevice device, WGPUErrorBlockCallback callback)
{
return WebGPU::fromAPI(device).setUncapturedErrorCallback([callback = WebGPU::fromAPI(WTFMove(callback))](WGPUErrorType type, String&& message) {
if (callback)
callback(type, message.utf8().data());
});
}
void wgpuDeviceSetLabel(WGPUDevice device, const char* label)
{
WebGPU::fromAPI(device).setLabel(WebGPU::fromAPI(label));
}