Diff
Modified: trunk/Source/WebGPU/ChangeLog (291570 => 291571)
--- trunk/Source/WebGPU/ChangeLog 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/ChangeLog 2022-03-21 18:58:31 UTC (rev 291571)
@@ -1,3 +1,44 @@
+2022-03-21 Myles C. Maxfield <[email protected]>
+
+ [WebGPU] Implement error reporting facilities
+ https://bugs.webkit.org/show_bug.cgi?id=238131
+
+ Reviewed by Kimmo Kinnunen.
+
+ This patch implements the GPUDevice.pushErrorScope() and GPUDevice.popErrorScope() functions,
+ according to the spec.
+
+ Now that we can report errors, we should be just about able to pass our first CTS test.
+
+ * CommandLinePlayground/main.swift:
+ * WebGPU/Buffer.mm:
+ (WebGPU::Buffer::mapAsync):
+ (WebGPU::Buffer::unmap):
+ * WebGPU/CommandEncoder.h:
+ (WebGPU::CommandEncoder::create):
+ * WebGPU/CommandEncoder.mm:
+ (WebGPU::Device::createCommandEncoder):
+ (WebGPU::CommandEncoder::CommandEncoder):
+ (WebGPU::CommandEncoder::copyBufferToBuffer):
+ (WebGPU::CommandEncoder::clearBuffer):
+ (WebGPU::CommandEncoder::finish):
+ * WebGPU/Device.h:
+ * WebGPU/Device.mm:
+ (WebGPU::Device::currentErrorScope):
+ (WebGPU::Device::generateAValidationError):
+ (WebGPU::Device::validatePopErrorScope const):
+ (WebGPU::Device::popErrorScope):
+ (WebGPU::Device::pushErrorScope):
+ (WebGPU::Device::setUncapturedErrorCallback):
+ * WebGPU/Queue.h:
+ * WebGPU/Queue.mm:
+ (WebGPU::Queue::submit):
+ * WebGPU/Sampler.h:
+ (WebGPU::Sampler::create):
+ * WebGPU/Sampler.mm:
+ (WebGPU::Device::createSampler):
+ (WebGPU::Sampler::Sampler):
+
2022-03-18 Myles C. Maxfield <[email protected]>
[WebGPU] Add #pragma marks to strategic places
Modified: trunk/Source/WebGPU/CommandLinePlayground/main.swift (291570 => 291571)
--- trunk/Source/WebGPU/CommandLinePlayground/main.swift 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/CommandLinePlayground/main.swift 2022-03-21 18:58:31 UTC (rev 291571)
@@ -58,6 +58,8 @@
}
print("Device: \(String(describing: device))")
+wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation)
+
var uploadBufferDescriptor = WGPUBufferDescriptor(nextInChain: nil, label: nil, usage: WGPUBufferUsage_MapWrite.rawValue | WGPUBufferUsage_CopySrc.rawValue, size: UInt64(MemoryLayout<Int32>.size), mappedAtCreation: false)
let uploadBuffer = wgpuDeviceCreateBuffer(device, &uploadBufferDescriptor)
assert(uploadBuffer != nil)
@@ -100,7 +102,18 @@
let readPointer = wgpuBufferGetMappedRange(downloadBuffer, 0, MemoryLayout<Int32>.size).bindMemory(to: Int32.self, capacity: 1)
print("Result: \(readPointer[0])")
wgpuBufferUnmap(downloadBuffer)
- CFRunLoopStop(CFRunLoopGetMain())
+ wgpuDevicePopErrorScopeWithBlock(device) { (type: WGPUErrorType, message: Optional<UnsafePointer<Int8>>) in
+ if type != WGPUErrorType_NoError {
+ if message != nil {
+ print("Message: \(String(cString: message!))")
+ } else {
+ print("Empty message.")
+ }
+ } else {
+ print("Success!")
+ }
+ CFRunLoopStop(CFRunLoopGetMain())
+ }
}
}
}
Modified: trunk/Source/WebGPU/WebGPU/Buffer.mm (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/Buffer.mm 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/Buffer.mm 2022-03-21 18:58:31 UTC (rev 291571)
@@ -279,7 +279,8 @@
// "If any of the following conditions are unsatisfied:"
if (!validateMapAsync(mode, offset, rangeSize)) {
- // FIXME: "Record a validation error on the current scope."
+ // "Record a validation error on the current scope."
+ m_device->generateAValidationError("Validation failure.");
// "Return a promise rejected with an OperationError on the Device timeline."
callback(WGPUBufferMapAsyncStatus_Error);
@@ -352,7 +353,8 @@
// "If any of the following requirements are unmet"
if (!validateUnmap()) {
- // FIXME: "generate a validation error and stop."
+ // "generate a validation error and stop."
+ m_device->generateAValidationError("Validation failure.");
return;
}
Modified: trunk/Source/WebGPU/WebGPU/CommandEncoder.h (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/CommandEncoder.h 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/CommandEncoder.h 2022-03-21 18:58:31 UTC (rev 291571)
@@ -35,6 +35,7 @@
class Buffer;
class CommandBuffer;
class ComputePassEncoder;
+class Device;
class QuerySet;
class RenderPassEncoder;
@@ -41,9 +42,9 @@
class CommandEncoder : public RefCounted<CommandEncoder>, public CommandsMixin {
WTF_MAKE_FAST_ALLOCATED;
public:
- static Ref<CommandEncoder> create(id<MTLCommandBuffer> commandBuffer)
+ static Ref<CommandEncoder> create(id<MTLCommandBuffer> commandBuffer, Device& device)
{
- return adoptRef(*new CommandEncoder(commandBuffer));
+ return adoptRef(*new CommandEncoder(commandBuffer, device));
}
~CommandEncoder();
@@ -64,7 +65,7 @@
void setLabel(String&&);
private:
- CommandEncoder(id<MTLCommandBuffer>);
+ CommandEncoder(id<MTLCommandBuffer>, Device&);
bool validateFinish() const;
bool validatePopDebugGroup() const;
@@ -76,6 +77,8 @@
id<MTLBlitCommandEncoder> m_blitCommandEncoder { nil };
uint64_t m_debugGroupStackSize { 0 };
+
+ const Ref<Device> m_device;
};
} // namespace WebGPU
Modified: trunk/Source/WebGPU/WebGPU/CommandEncoder.mm (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/CommandEncoder.mm 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/CommandEncoder.mm 2022-03-21 18:58:31 UTC (rev 291571)
@@ -51,11 +51,12 @@
commandBuffer.label = fromAPI(descriptor.label);
- return CommandEncoder::create(commandBuffer);
+ return CommandEncoder::create(commandBuffer, *this);
}
-CommandEncoder::CommandEncoder(id<MTLCommandBuffer> commandBuffer)
+CommandEncoder::CommandEncoder(id<MTLCommandBuffer> commandBuffer, Device& device)
: m_commandBuffer(commandBuffer)
+ , m_device(device)
{
}
@@ -142,7 +143,8 @@
// "If any of the following conditions are unsatisfied
if (!validateCopyBufferToBuffer(source, sourceOffset, destination, destinationOffset, size)) {
- // FIXME: "generate a validation error and stop."
+ // "generate a validation error and stop."
+ m_device->generateAValidationError("Validation failure.");
return;
}
@@ -212,7 +214,8 @@
// "If any of the following conditions are unsatisfied"
if (!validateClearBuffer(buffer, offset, size)) {
- // FIXME: "generate a validation error and stop."
+ // "generate a validation error and stop."
+ m_device->generateAValidationError("Validation failure.");
return;
}
@@ -253,7 +256,8 @@
// "If validationSucceeded is false, then:"
if (validationFailed) {
- // FIXME: "Generate a validation error."
+ // "Generate a validation error."
+ m_device->generateAValidationError("Validation failure.");
// FIXME: "Return a new invalid GPUCommandBuffer."
return nullptr;
Modified: trunk/Source/WebGPU/WebGPU/Device.h (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/Device.h 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/Device.h 2022-03-21 18:58:31 UTC (rev 291571)
@@ -32,6 +32,8 @@
#import <wtf/Ref.h>
#import <wtf/RefCounted.h>
#import <wtf/RefPtr.h>
+#import <wtf/Vector.h>
+#import <wtf/text/WTFString.h>
namespace WebGPU {
@@ -84,6 +86,8 @@
void setUncapturedErrorCallback(Function<void(WGPUErrorType, String&&)>&&);
void setLabel(String&&);
+ void generateAValidationError(String&& message);
+
Instance& instance() const { return m_instance; }
bool hasUnifiedMemory() const { return m_device.hasUnifiedMemory; }
@@ -90,9 +94,25 @@
private:
Device(id<MTLDevice>, id<MTLCommandQueue> defaultQueue, Instance&);
+ struct ErrorScope;
+ ErrorScope* currentErrorScope(WGPUErrorFilter);
+ bool validatePopErrorScope() const;
+
+ struct Error {
+ WGPUErrorType type;
+ String message;
+ };
+ struct ErrorScope {
+ std::optional<Error> error; // "The first GPUError, if any, observed while the GPU error scope was current."
+ const WGPUErrorFilter filter; // Determines what type of GPUError this GPU error scope observes.
+ };
+
const id<MTLDevice> m_device { nil };
const Ref<Queue> m_defaultQueue;
const Ref<Instance> m_instance;
+
+ Function<void(WGPUErrorType, String&&)> m_uncapturedErrorCallback;
+ Vector<ErrorScope> m_errorScopeStack; // "A stack of GPU error scopes that have been pushed to the GPUDevice."
};
} // namespace WebGPU
Modified: trunk/Source/WebGPU/WebGPU/Device.mm (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/Device.mm 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/Device.mm 2022-03-21 18:58:31 UTC (rev 291571)
@@ -102,17 +102,94 @@
return false;
}
+auto Device::currentErrorScope(WGPUErrorFilter type) -> ErrorScope*
+{
+ // https://gpuweb.github.io/gpuweb/#abstract-opdef-current-error-scope
+
+ // "Let scope be the last item of device.[[errorScopeStack]]."
+ // "While scope is not undefined:"
+ for (auto iterator = m_errorScopeStack.rbegin(); iterator != m_errorScopeStack.rend(); ++iterator) {
+ // "If scope.[[filter]] is type, return scope."
+ if (iterator->filter == type)
+ return &*iterator;
+ // "Set scope to the previous item of device.[[errorScopeStack]]."
+ }
+ // "Return undefined."
+ return nullptr;
+}
+
+void Device::generateAValidationError(String&& message)
+{
+ // https://gpuweb.github.io/gpuweb/#abstract-opdef-generate-a-validation-error
+
+ // "Let scope be the current error scope for error and device."
+ auto* scope = currentErrorScope(WGPUErrorFilter_Validation);
+
+ // "If scope is not undefined:"
+ if (scope) {
+ // "If scope.[[error]] is null, set scope.[[error]] to error."
+ if (!scope->error)
+ scope->error = Error { WGPUErrorType_Validation, WTFMove(message) };
+ // "Stop."
+ return;
+ }
+
+ // "Otherwise issue the following steps to the Content timeline:"
+ // "If the user agent chooses, queue a task to fire a GPUUncapturedErrorEvent named "uncapturederror" on device with an error of error."
+ if (m_uncapturedErrorCallback) {
+ m_instance->scheduleWork([protectedThis = Ref { *this }, message = WTFMove(message)]() mutable {
+ protectedThis->m_uncapturedErrorCallback(WGPUErrorType_Validation, WTFMove(message));
+ });
+ }
+}
+
+bool Device::validatePopErrorScope() const
+{
+ // FIXME: "this must not be lost."
+
+ // "this.[[errorScopeStack]].size > 0."
+ if (m_errorScopeStack.isEmpty())
+ return false;
+
+ return true;
+}
+
bool Device::popErrorScope(CompletionHandler<void(WGPUErrorType, String&&)>&& callback)
{
- // FIXME: Implement this.
- UNUSED_PARAM(callback);
- return false;
+ // https://gpuweb.github.io/gpuweb/#dom-gpudevice-poperrorscope
+
+ // "If any of the following requirements are unmet"
+ if (!validatePopErrorScope()) {
+ // "reject promise with an OperationError and stop."
+ callback(WGPUErrorType_Unknown, "popErrorScope() failed validation."_s);
+ return false;
+ }
+
+ // "Let scope be the result of popping an item off of this.[[errorScopeStack]]."
+ auto scope = m_errorScopeStack.takeLast();
+
+ // "Resolve promise with scope.[[error]]."
+ m_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)
{
- // FIXME: Implement this.
- UNUSED_PARAM(filter);
+ // https://gpuweb.github.io/gpuweb/#dom-gpudevice-pusherrorscope
+
+ // "Let scope be a new GPU error scope."
+ // "Set scope.[[filter]] to filter."
+ ErrorScope scope { std::nullopt, filter };
+
+ // "Push scope onto this.[[errorScopeStack]]."
+ m_errorScopeStack.append(WTFMove(scope));
}
void Device::setDeviceLostCallback(Function<void(WGPUDeviceLostReason, String&&)>&& callback)
@@ -123,8 +200,7 @@
void Device::setUncapturedErrorCallback(Function<void(WGPUErrorType, String&&)>&& callback)
{
- // FIXME: Implement this.
- UNUSED_PARAM(callback);
+ m_uncapturedErrorCallback = WTFMove(callback);
}
void Device::setLabel(String&&)
Modified: trunk/Source/WebGPU/WebGPU/Queue.h (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/Queue.h 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/Queue.h 2022-03-21 18:58:31 UTC (rev 291571)
@@ -66,7 +66,7 @@
void scheduleWork(Instance::WorkItem&&);
const id<MTLCommandQueue> m_commandQueue { nil };
- const Device& m_device; // The only kind of queues that exist right now are default queues, which are owned by Devices.
+ Device& m_device; // The only kind of queues that exist right now are default queues, which are owned by Devices.
uint64_t m_submittedCommandBufferCount { 0 };
uint64_t m_completedCommandBufferCount { 0 };
Modified: trunk/Source/WebGPU/WebGPU/Queue.mm (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/Queue.mm 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/Queue.mm 2022-03-21 18:58:31 UTC (rev 291571)
@@ -78,7 +78,8 @@
// "If any of the following conditions are unsatisfied"
if (!validateSubmit()) {
- // FIXME: "generate a validation error and stop."
+ // "generate a validation error and stop."
+ m_device.generateAValidationError("Validation failure.");
return;
}
Modified: trunk/Source/WebGPU/WebGPU/Sampler.h (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/Sampler.h 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/Sampler.h 2022-03-21 18:58:31 UTC (rev 291571)
@@ -31,12 +31,14 @@
namespace WebGPU {
+class Device;
+
class Sampler : public RefCounted<Sampler> {
WTF_MAKE_FAST_ALLOCATED;
public:
- static Ref<Sampler> create(id<MTLSamplerState> samplerState, const WGPUSamplerDescriptor& descriptor)
+ static Ref<Sampler> create(id<MTLSamplerState> samplerState, const WGPUSamplerDescriptor& descriptor, Device& device)
{
- return adoptRef(*new Sampler(samplerState, descriptor));
+ return adoptRef(*new Sampler(samplerState, descriptor, device));
}
~Sampler();
@@ -51,7 +53,7 @@
bool isFiltering() const { return descriptor().minFilter == WGPUFilterMode_Linear || descriptor().magFilter == WGPUFilterMode_Linear || descriptor().mipmapFilter == WGPUFilterMode_Linear; }
private:
- Sampler(id<MTLSamplerState>, const WGPUSamplerDescriptor& descriptor);
+ Sampler(id<MTLSamplerState>, const WGPUSamplerDescriptor&, Device&);
const id<MTLSamplerState> m_samplerState { nil };
@@ -58,6 +60,8 @@
const WGPUSamplerDescriptor m_descriptor { }; // "The GPUSamplerDescriptor with which the GPUSampler was created."
// "[[isComparison]] of type boolean." This is unnecessary; it's implemented in isComparison().
// "[[isFiltering]] of type boolean." This is unnecessary; it's implemented in isFiltering().
+
+ const Ref<Device> m_device;
};
} // namespace WebGPU
Modified: trunk/Source/WebGPU/WebGPU/Sampler.mm (291570 => 291571)
--- trunk/Source/WebGPU/WebGPU/Sampler.mm 2022-03-21 18:48:25 UTC (rev 291570)
+++ trunk/Source/WebGPU/WebGPU/Sampler.mm 2022-03-21 18:58:31 UTC (rev 291571)
@@ -75,7 +75,8 @@
// "If validating GPUSamplerDescriptor(this, descriptor) returns false:"
if (!validateCreateSampler(*this, descriptor)) {
- // FIXME: "Generate a validation error."
+ // "Generate a validation error."
+ generateAValidationError("Validation failure.");
// "Create a new invalid GPUSampler and return the result."
return nullptr;
@@ -201,12 +202,13 @@
if (!samplerState)
return nullptr;
- return Sampler::create(samplerState, descriptor);
+ return Sampler::create(samplerState, descriptor, *this);
}
-Sampler::Sampler(id<MTLSamplerState> samplerState, const WGPUSamplerDescriptor& descriptor)
+Sampler::Sampler(id<MTLSamplerState> samplerState, const WGPUSamplerDescriptor& descriptor, Device& device)
: m_samplerState(samplerState)
, m_descriptor(descriptor)
+ , m_device(device)
{
}