[WebGPU] Implement GPUErrors for and relax GPUBuffer validation rules
authorjustin_fan@apple.com <justin_fan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Aug 2019 23:39:38 +0000 (23:39 +0000)
committerjustin_fan@apple.com <justin_fan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Aug 2019 23:39:38 +0000 (23:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=200852

Reviewed by Dean Jackson.

Source/WebCore:

Fix incorrect usage validation during GPUBuffer creation.
Implement GPUError reporting for GPUBuffer creation and methods.

Test: webgpu/buffer-errors.html

* Modules/webgpu/WebGPUBuffer.cpp:
(WebCore::WebGPUBuffer::create):
(WebCore::WebGPUBuffer::WebGPUBuffer):
(WebCore::WebGPUBuffer::unmap):
(WebCore::WebGPUBuffer::destroy):
(WebCore::WebGPUBuffer::rejectOrRegisterPromiseCallback):
* Modules/webgpu/WebGPUBuffer.h: Now inherits from GPUObjectBase.
* Modules/webgpu/WebGPUDevice.cpp:
(WebCore::WebGPUDevice::createBuffer const):
(WebCore::WebGPUDevice::createBufferMapped const):
* platform/graphics/gpu/GPUBuffer.h: No longer inherits from GPUObjectBase.
* platform/graphics/gpu/GPUObjectBase.h:
(WebCore::GPUObjectBase::errorScopes):
(WebCore::GPUObjectBase::generateError): Deleted.
* platform/graphics/gpu/cocoa/GPUBufferMetal.mm:
(WebCore::GPUBuffer::validateBufferUsage):
(WebCore::GPUBuffer::tryCreate): Alignment issue should be general WebGPU requirement.
(WebCore::GPUBuffer::GPUBuffer):
(WebCore::GPUBuffer::~GPUBuffer): Must do cleanup without generating errors.
(WebCore::GPUBuffer::registerMappingCallback):
(WebCore::GPUBuffer::copyStagingBufferToGPU):
(WebCore::GPUBuffer::unmap):
(WebCore::GPUBuffer::destroy):

LayoutTests:

Add a test to ensure GPUBuffer errors are generated properly.

* webgpu/buffer-errors-expected.txt: Added.
* webgpu/buffer-errors.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@249183 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/webgpu/buffer-errors-expected.txt [new file with mode: 0644]
LayoutTests/webgpu/buffer-errors.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/webgpu/WebGPUBuffer.cpp
Source/WebCore/Modules/webgpu/WebGPUBuffer.h
Source/WebCore/Modules/webgpu/WebGPUDevice.cpp
Source/WebCore/platform/graphics/gpu/GPUBuffer.h
Source/WebCore/platform/graphics/gpu/GPUObjectBase.h
Source/WebCore/platform/graphics/gpu/cocoa/GPUBufferMetal.mm

index 92426d4..be12b0e 100644 (file)
@@ -1,3 +1,15 @@
+2019-08-27  Justin Fan  <justin_fan@apple.com>
+
+        [WebGPU] Implement GPUErrors for and relax GPUBuffer validation rules
+        https://bugs.webkit.org/show_bug.cgi?id=200852
+
+        Reviewed by Dean Jackson.
+
+        Add a test to ensure GPUBuffer errors are generated properly.
+
+        * webgpu/buffer-errors-expected.txt: Added.
+        * webgpu/buffer-errors.html: Added.
+
 2019-08-27  Russell Epstein  <repstein@apple.com>
 
         Test Gardening for scrollingcoordinator/ios/scroll-position-after-reattach.html
diff --git a/LayoutTests/webgpu/buffer-errors-expected.txt b/LayoutTests/webgpu/buffer-errors-expected.txt
new file mode 100644 (file)
index 0000000..56bfd91
--- /dev/null
@@ -0,0 +1,13 @@
+
+PASS GPUBuffers can be created with both read-only and STORAGE usages. 
+PASS unmap on already unmapped, mappable GPUBuffer should not generate error. 
+PASS GPUBuffer created via createBufferMapped cannot be remapped. 
+PASS GPUBufferDescriptor with both MAP_READ and MAP_WRITE usage should fail. 
+PASS Too-large GPUBufferDescriptor size should fail with out-of-memory error. 
+PASS mapReadAsync on non-MAP_READ GPUBuffer should fail. 
+PASS mapWriteAsync on non-MAP_WRITE GPUBuffer should fail. 
+PASS unmap on non-mappable GPUBuffer should fail. 
+PASS createBufferMapped: non-4-byte-aligned GPUBufferDesriptor size should fail. 
+PASS Any method call on an invalid GPUBuffer should fail. 
+PASS Any method call on a destroyed GPUBuffer should fail. 
+
diff --git a/LayoutTests/webgpu/buffer-errors.html b/LayoutTests/webgpu/buffer-errors.html
new file mode 100644 (file)
index 0000000..d504df4
--- /dev/null
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test GPUBuffer Errors.</title>
+<body>
+<script src="js/webgpu-functions.js"></script>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script>
+let tests = {};
+
+tests["GPUBuffers can be created with both read-only and STORAGE usages."] = async device => {
+    device.pushErrorScope("validation");
+    device.createBuffer({
+        size: 4,
+        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.INDEX | GPUBufferUsage.TRANSFER_SRC | GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE
+    });
+    return popNullError(device);
+};
+
+tests["unmap on already unmapped, mappable GPUBuffer should not generate error."] = async device => {
+    const buffer = device.createBuffer({
+        size: 4,
+        usage: GPUBufferUsage.MAP_READ |
+            GPUBufferUsage.TRANSFER_SRC |
+            GPUBufferUsage.TRANSFER_DST |
+            GPUBufferUsage.VERTEX |
+            GPUBufferUsage.INDEX |
+            GPUBufferUsage.UNIFORM |
+            GPUBufferUsage.STORAGE
+    });
+
+    device.pushErrorScope("validation");
+    buffer.unmap();
+    return popNullError(device);
+};
+
+tests["GPUBuffer created via createBufferMapped cannot be remapped."] = async device => {
+    device.pushErrorScope("validation");
+    const [buffer, _] = device.createBufferMapped({ size: 4, usage: 0 });
+    // Should not fail.
+    buffer.unmap();
+    await popNullError(device);
+
+    device.pushErrorScope("validation");
+    // Should fail.
+    buffer.unmap();
+    await popValidationError(device);
+
+    device.pushErrorScope("validation");
+    // Should fail.
+    buffer.mapReadAsync().then(() => { assert_unreached(); }).catch(e => {});
+    return popValidationError(device);
+};
+
+tests["GPUBufferDescriptor with both MAP_READ and MAP_WRITE usage should fail."] = async device => {
+    device.pushErrorScope("validation");
+    device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.MAP_WRITE });
+    return popValidationError(device);
+};
+
+tests["Too-large GPUBufferDescriptor size should fail with out-of-memory error."] = async device => {
+    device.pushErrorScope("out-of-memory");
+    device.createBuffer({ size: 99999999999, usage: GPUBufferUsage.NONE });
+    return popMemoryError(device);
+};
+
+tests["mapReadAsync on non-MAP_READ GPUBuffer should fail."] = async device => {
+    const buffer = device.createBuffer({
+        size: 4,
+        usage: GPUBufferUsage.MAP_WRITE |
+            GPUBufferUsage.TRANSFER_SRC |
+            GPUBufferUsage.TRANSFER_DST |
+            GPUBufferUsage.VERTEX |
+            GPUBufferUsage.INDEX |
+            GPUBufferUsage.UNIFORM |
+            GPUBufferUsage.STORAGE
+    });
+
+    device.pushErrorScope("validation");
+    buffer.mapReadAsync().then(() => { assert_unreached(); }).catch(e => {});
+    return popValidationError(device);
+};
+
+tests["mapWriteAsync on non-MAP_WRITE GPUBuffer should fail."] = async device => {
+    const buffer = device.createBuffer({
+        size: 4,
+        usage: GPUBufferUsage.MAP_READ |
+            GPUBufferUsage.TRANSFER_SRC |
+            GPUBufferUsage.TRANSFER_DST |
+            GPUBufferUsage.VERTEX |
+            GPUBufferUsage.INDEX |
+            GPUBufferUsage.UNIFORM |
+            GPUBufferUsage.STORAGE
+    });
+
+    device.pushErrorScope("validation");
+    buffer.mapWriteAsync().then(() => { assert_unreached(); }).catch(e => {});
+    return popValidationError(device);
+};
+
+tests["unmap on non-mappable GPUBuffer should fail."] = async device => {
+    const buffer = device.createBuffer({
+        size: 4,
+        usage: GPUBufferUsage.TRANSFER_SRC |
+            GPUBufferUsage.TRANSFER_DST |
+            GPUBufferUsage.VERTEX |
+            GPUBufferUsage.INDEX |
+            GPUBufferUsage.UNIFORM |
+            GPUBufferUsage.STORAGE
+    });
+
+    device.pushErrorScope("validation");
+    buffer.unmap();
+    return popValidationError(device);
+};
+
+tests["createBufferMapped: non-4-byte-aligned GPUBufferDesriptor size should fail."] = async device => {
+    device.pushErrorScope("validation");
+    device.createBufferMapped({ size: 5, usage: 0 });
+    return popValidationError(device);
+};
+
+tests["Any method call on an invalid GPUBuffer should fail."] = async device => {
+    const buffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.MAP_WRITE });
+    return assertAllBufferMethodsFail(device, buffer);
+};
+
+tests["Any method call on a destroyed GPUBuffer should fail."] = async device => {
+    const buffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ });
+
+    device.pushErrorScope("validation");
+    buffer.destroy();
+    await popNullError(device);
+
+    return assertAllBufferMethodsFail(device, buffer);
+};
+
+const assertAllBufferMethodsFail = async (device, buffer) => {
+    device.pushErrorScope("validation");
+    buffer.mapReadAsync().then(() => { assert_unreached(); }).catch(e => {});
+    await popValidationError(device);
+
+    device.pushErrorScope("validation");
+    buffer.mapWriteAsync().then(() => { assert_unreached(); }).catch(e => {});
+    await popValidationError(device);
+
+    device.pushErrorScope("validation");
+    buffer.unmap();
+    await popValidationError(device);
+
+    device.pushErrorScope("validation");
+    buffer.destroy();
+    return popValidationError(device);
+};
+
+runTestsWithDevice(tests);
+</script>
+</body>
\ No newline at end of file
index c8a7c29..b48fbba 100644 (file)
@@ -1,3 +1,39 @@
+2019-08-27  Justin Fan  <justin_fan@apple.com>
+
+        [WebGPU] Implement GPUErrors for and relax GPUBuffer validation rules
+        https://bugs.webkit.org/show_bug.cgi?id=200852
+
+        Reviewed by Dean Jackson.
+
+        Fix incorrect usage validation during GPUBuffer creation.
+        Implement GPUError reporting for GPUBuffer creation and methods.
+
+        Test: webgpu/buffer-errors.html
+
+        * Modules/webgpu/WebGPUBuffer.cpp:
+        (WebCore::WebGPUBuffer::create):
+        (WebCore::WebGPUBuffer::WebGPUBuffer):
+        (WebCore::WebGPUBuffer::unmap):
+        (WebCore::WebGPUBuffer::destroy):
+        (WebCore::WebGPUBuffer::rejectOrRegisterPromiseCallback):
+        * Modules/webgpu/WebGPUBuffer.h: Now inherits from GPUObjectBase.
+        * Modules/webgpu/WebGPUDevice.cpp:
+        (WebCore::WebGPUDevice::createBuffer const):
+        (WebCore::WebGPUDevice::createBufferMapped const):
+        * platform/graphics/gpu/GPUBuffer.h: No longer inherits from GPUObjectBase.
+        * platform/graphics/gpu/GPUObjectBase.h:
+        (WebCore::GPUObjectBase::errorScopes):
+        (WebCore::GPUObjectBase::generateError): Deleted.
+        * platform/graphics/gpu/cocoa/GPUBufferMetal.mm:
+        (WebCore::GPUBuffer::validateBufferUsage):
+        (WebCore::GPUBuffer::tryCreate): Alignment issue should be general WebGPU requirement.
+        (WebCore::GPUBuffer::GPUBuffer):
+        (WebCore::GPUBuffer::~GPUBuffer): Must do cleanup without generating errors.
+        (WebCore::GPUBuffer::registerMappingCallback):
+        (WebCore::GPUBuffer::copyStagingBufferToGPU):
+        (WebCore::GPUBuffer::unmap):
+        (WebCore::GPUBuffer::destroy):
+
 2019-08-27  Zalan Bujtas  <zalan@apple.com>
 
         [LFC][TFC] Layout and position the cell boxes
index c222e35..ba25963 100644 (file)
 
 #if ENABLE(WEBGPU)
 
-#include "Logging.h"
+#include "GPUErrorScopes.h"
+#include <wtf/text/StringConcatenate.h>
 
 namespace WebCore {
 
-Ref<WebGPUBuffer> WebGPUBuffer::create(RefPtr<GPUBuffer>&& buffer)
+Ref<WebGPUBuffer> WebGPUBuffer::create(RefPtr<GPUBuffer>&& buffer, GPUErrorScopes& errorScopes)
 {
-    return adoptRef(*new WebGPUBuffer(WTFMove(buffer)));
+    return adoptRef(*new WebGPUBuffer(WTFMove(buffer), errorScopes));
 }
 
-WebGPUBuffer::WebGPUBuffer(RefPtr<GPUBuffer>&& buffer)
-    : m_buffer(WTFMove(buffer))
+WebGPUBuffer::WebGPUBuffer(RefPtr<GPUBuffer>&& buffer, GPUErrorScopes& errorScopes)
+    : GPUObjectBase(makeRef(errorScopes))
+    , m_buffer(WTFMove(buffer))
 {
 }
 
@@ -54,36 +56,44 @@ void WebGPUBuffer::mapWriteAsync(BufferMappingPromise&& promise)
 
 void WebGPUBuffer::unmap()
 {
+    errorScopes().setErrorPrefix("GPUBuffer.unmap(): ");
+
     if (!m_buffer)
-        LOG(WebGPU, "GPUBuffer::unmap(): Invalid operation!");
+        errorScopes().generatePrefixedError("Invalid operation: invalid GPUBuffer!");
     else
-        m_buffer->unmap();
+        m_buffer->unmap(&errorScopes());
 }
 
 void WebGPUBuffer::destroy()
 {
+    errorScopes().setErrorPrefix("GPUBuffer.destroy(): ");
+
     if (!m_buffer)
-        LOG(WebGPU, "GPUBuffer::destroy(): Invalid operation!");
+        errorScopes().generatePrefixedError("Invalid operation!");
     else {
-        m_buffer->destroy();
+        m_buffer->destroy(&errorScopes());
         m_buffer = nullptr;
     }
 }
 
 void WebGPUBuffer::rejectOrRegisterPromiseCallback(BufferMappingPromise&& promise, bool isRead)
 {
+    errorScopes().setErrorPrefix(makeString("GPUBuffer.map", isRead ? "Read" : "Write", "Async(): "));
+
     if (!m_buffer) {
-        LOG(WebGPU, "GPUBuffer::map%sAsync(): Invalid operation!", isRead ? "Read" : "Write");
+        errorScopes().generatePrefixedError("Invalid operation: invalid GPUBuffer!");
         promise.reject();
         return;
     }
 
-    m_buffer->registerMappingCallback([promise = WTFMove(promise)] (JSC::ArrayBuffer* arrayBuffer) mutable {
+    m_buffer->registerMappingCallback([promise = WTFMove(promise), protectedErrorScopes = makeRef(errorScopes())] (JSC::ArrayBuffer* arrayBuffer) mutable {
         if (arrayBuffer)
             promise.resolve(*arrayBuffer);
-        else
+        else {
+            protectedErrorScopes->generateError("", GPUErrorFilter::OutOfMemory);
             promise.reject();
-    }, isRead);
+        }
+    }, isRead, errorScopes());
 }
 
 } // namespace WebCore
index 587970d..d996b5e 100644 (file)
@@ -29,8 +29,8 @@
 
 #include "GPUBuffer.h"
 #include "GPUBufferUsage.h"
+#include "GPUObjectBase.h"
 #include "JSDOMPromiseDeferred.h"
-#include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
 
 namespace JSC {
@@ -41,9 +41,9 @@ namespace WebCore {
 
 struct GPUBufferDescriptor;
 
-class WebGPUBuffer : public RefCounted<WebGPUBuffer> {
+class WebGPUBuffer : public GPUObjectBase {
 public:
-    static Ref<WebGPUBuffer> create(RefPtr<GPUBuffer>&&);
+    static Ref<WebGPUBuffer> create(RefPtr<GPUBuffer>&&, GPUErrorScopes&);
 
     GPUBuffer* buffer() { return m_buffer.get(); }
     const GPUBuffer* buffer() const { return m_buffer.get(); }
@@ -55,7 +55,7 @@ public:
     void destroy();
 
 private:
-    explicit WebGPUBuffer(RefPtr<GPUBuffer>&&);
+    explicit WebGPUBuffer(RefPtr<GPUBuffer>&&, GPUErrorScopes&);
 
     void rejectOrRegisterPromiseCallback(BufferMappingPromise&&, bool);
 
index 55f7ffb..f404b80 100644 (file)
@@ -91,7 +91,7 @@ Ref<WebGPUBuffer> WebGPUDevice::createBuffer(const GPUBufferDescriptor& descript
     m_errorScopes->setErrorPrefix("GPUDevice.createBuffer(): ");
 
     auto buffer = m_device->tryCreateBuffer(descriptor, GPUBufferMappedOption::NotMapped, m_errorScopes);
-    return WebGPUBuffer::create(WTFMove(buffer));
+    return WebGPUBuffer::create(WTFMove(buffer), m_errorScopes);
 }
 
 Vector<JSC::JSValue> WebGPUDevice::createBufferMapped(JSC::ExecState& state, const GPUBufferDescriptor& descriptor) const
@@ -106,7 +106,7 @@ Vector<JSC::JSValue> WebGPUDevice::createBufferMapped(JSC::ExecState& state, con
         wrappedArrayBuffer = toJS(&state, JSC::jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject()), arrayBuffer);
     }
 
-    auto webBuffer = WebGPUBuffer::create(WTFMove(buffer));
+    auto webBuffer = WebGPUBuffer::create(WTFMove(buffer), m_errorScopes);
     auto wrappedWebBuffer = toJS(&state, JSC::jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject()), webBuffer);
 
     return { wrappedWebBuffer, wrappedArrayBuffer };
index 07f40ae..3256b4e 100644 (file)
@@ -29,7 +29,6 @@
 
 #include "DeferrableTask.h"
 #include "GPUBufferUsage.h"
-#include "GPUObjectBase.h"
 #include <wtf/Function.h>
 #include <wtf/OptionSet.h>
 #include <wtf/Ref.h>
@@ -49,6 +48,7 @@ class ArrayBuffer;
 namespace WebCore {
 
 class GPUDevice;
+class GPUErrorScopes;
 
 struct GPUBufferDescriptor;
 
@@ -61,7 +61,7 @@ using PlatformBuffer = void;
 #endif
 using PlatformBufferSmartPtr = RetainPtr<PlatformBuffer>;
 
-class GPUBuffer : public GPUObjectBase {
+class GPUBuffer : public RefCounted<GPUBuffer> {
 public:
     enum class State {
         Mapped,
@@ -94,9 +94,9 @@ public:
 #endif
 
     using MappingCallback = WTF::Function<void(JSC::ArrayBuffer*)>;
-    void registerMappingCallback(MappingCallback&&, bool);
-    void unmap();
-    void destroy();
+    void registerMappingCallback(MappingCallback&&, bool, GPUErrorScopes&);
+    void unmap(GPUErrorScopes*);
+    void destroy(GPUErrorScopes*);
 
 private:
     struct PendingMappingCallback : public RefCounted<PendingMappingCallback> {
@@ -111,13 +111,13 @@ private:
         PendingMappingCallback(MappingCallback&&);
     };
 
-    GPUBuffer(PlatformBufferSmartPtr&&, GPUDevice&, size_t, OptionSet<GPUBufferUsage::Flags>, GPUBufferMappedOption, GPUErrorScopes&);
+    GPUBuffer(PlatformBufferSmartPtr&&, GPUDevice&, size_t, OptionSet<GPUBufferUsage::Flags>, GPUBufferMappedOption);
     static bool validateBufferUsage(const GPUDevice&, OptionSet<GPUBufferUsage::Flags>, GPUErrorScopes&);
 
     JSC::ArrayBuffer* stagingBufferForRead();
     JSC::ArrayBuffer* stagingBufferForWrite();
     void runMappingCallback();
-    void copyStagingBufferToGPU();
+    void copyStagingBufferToGPU(GPUErrorScopes*);
 
     bool isMapWrite() const { return m_usage.contains(GPUBufferUsage::Flags::MapWrite); }
     bool isMapRead() const { return m_usage.contains(GPUBufferUsage::Flags::MapRead); }
index 6653bcf..6577057 100644 (file)
 namespace WebCore {
 
 class GPUObjectBase : public RefCounted<GPUObjectBase> {
-public:
-    void generateError(const String& message, GPUErrorFilter filter = GPUErrorFilter::Validation)
-    {
-        m_errorScopes->generateError(message, filter);
-    }
-
 protected:
     GPUObjectBase(Ref<GPUErrorScopes>&& reporter)
         : m_errorScopes(WTFMove(reporter)) { }
 
+    GPUErrorScopes& errorScopes() { return m_errorScopes; }
+
 private:
     Ref<GPUErrorScopes> m_errorScopes;
 };
index da70ae6..eadfebf 100644 (file)
@@ -30,7 +30,7 @@
 
 #import "GPUBufferDescriptor.h"
 #import "GPUDevice.h"
-#import "Logging.h"
+#import "GPUErrorScopes.h"
 #import <JavaScriptCore/ArrayBuffer.h>
 #import <Metal/Metal.h>
 #import <wtf/BlockObjCExceptions.h>
@@ -46,7 +46,7 @@ static constexpr auto readOnlyFlags = OptionSet<GPUBufferUsage::Flags> { GPUBuff
 bool GPUBuffer::validateBufferUsage(const GPUDevice& device, OptionSet<GPUBufferUsage::Flags> usage, GPUErrorScopes& errorScopes)
 {
     if (!device.platformDevice()) {
-        LOG(WebGPU, "GPUBuffer::tryCreate(): Invalid GPUDevice!");
+        errorScopes.generatePrefixedError("Invalid GPUDevice!");
         return false;
     }
 
@@ -55,11 +55,6 @@ bool GPUBuffer::validateBufferUsage(const GPUDevice& device, OptionSet<GPUBuffer
         return false;
     }
 
-    if (usage.containsAny(readOnlyFlags) && (usage & GPUBufferUsage::Flags::Storage)) {
-        LOG(WebGPU, "GPUBuffer::tryCreate(): Buffer cannot have both STORAGE and a read-only usage!");
-        return false;
-    }
-
     return true;
 }
 
@@ -76,16 +71,14 @@ RefPtr<GPUBuffer> GPUBuffer::tryCreate(GPUDevice& device, const GPUBufferDescrip
     if (!validateBufferUsage(device, usage, errorScopes))
         return nullptr;
 
-#if PLATFORM(MAC)
     // copyBufferToBuffer calls require 4-byte alignment. "Unmapping" a mapped-on-creation GPUBuffer
     // that is otherwise unmappable requires such a copy to upload data.
     if (isMapped == GPUBufferMappedOption::IsMapped
         && !usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })
         && descriptor.size % 4) {
-        LOG(WebGPU, "GPUBuffer::tryCreate(): Data must be aligned to a multiple of 4 bytes!");
+        errorScopes.generatePrefixedError("Data must be aligned to a multiple of 4 bytes!");
         return nullptr;
     }
-#endif
 
     // FIXME: Metal best practices: Read-only one-time-use data less than 4 KB should not allocate a MTLBuffer and be used in [MTLCommandEncoder set*Bytes] calls instead.
 
@@ -107,12 +100,11 @@ RefPtr<GPUBuffer> GPUBuffer::tryCreate(GPUDevice& device, const GPUBufferDescrip
         return nullptr;
     }
 
-    return adoptRef(*new GPUBuffer(WTFMove(mtlBuffer), device, size, usage, isMapped, errorScopes));
+    return adoptRef(*new GPUBuffer(WTFMove(mtlBuffer), device, size, usage, isMapped));
 }
 
-GPUBuffer::GPUBuffer(RetainPtr<MTLBuffer>&& buffer, GPUDevice& device, size_t size, OptionSet<GPUBufferUsage::Flags> usage, GPUBufferMappedOption isMapped, GPUErrorScopes& errorScopes)
-    : GPUObjectBase(makeRef(errorScopes))
-    , m_platformBuffer(WTFMove(buffer))
+GPUBuffer::GPUBuffer(RetainPtr<MTLBuffer>&& buffer, GPUDevice& device, size_t size, OptionSet<GPUBufferUsage::Flags> usage, GPUBufferMappedOption isMapped)
+    : m_platformBuffer(WTFMove(buffer))
     , m_device(makeRef(device))
     , m_byteLength(size)
     , m_usage(usage)
@@ -125,7 +117,7 @@ GPUBuffer::GPUBuffer(RetainPtr<MTLBuffer>&& buffer, GPUDevice& device, size_t si
 
 GPUBuffer::~GPUBuffer()
 {
-    destroy();
+    destroy(nullptr);
 }
 
 bool GPUBuffer::isReadOnly() const
@@ -177,16 +169,16 @@ void GPUBuffer::commandBufferCompleted()
 }
 #endif // USE(METAL)
 
-void GPUBuffer::registerMappingCallback(MappingCallback&& callback, bool isRead)
+void GPUBuffer::registerMappingCallback(MappingCallback&& callback, bool isRead, GPUErrorScopes& errorScopes)
 {
     // Reject if request is invalid.
     if (isRead && !isMapReadable()) {
-        LOG(WebGPU, "GPUBuffer::mapReadAsync(): Invalid operation!");
+        errorScopes.generatePrefixedError("Invalid operation!");
         callback(nullptr);
         return;
     }
     if (!isRead && !isMapWriteable()) {
-        LOG(WebGPU, "GPUBuffer::mapWriteAsync(): Invalid operation!");
+        errorScopes.generatePrefixedError("Invalid operation!");
         callback(nullptr);
         return;
     }
@@ -226,7 +218,7 @@ JSC::ArrayBuffer* GPUBuffer::stagingBufferForWrite()
     return m_stagingBuffer.get();
 }
 
-void GPUBuffer::copyStagingBufferToGPU()
+void GPUBuffer::copyStagingBufferToGPU(GPUErrorScopes* errorScopes)
 {
     MTLCommandQueue *queue;
     if (!m_device->tryGetQueue() || !(queue = m_device->tryGetQueue()->platformQueue()))
@@ -239,8 +231,8 @@ void GPUBuffer::copyStagingBufferToGPU()
     stagingMtlBuffer = adoptNS([m_device->platformDevice() newBufferWithLength:static_cast<NSUInteger>(m_byteLength) options:MTLResourceCPUCacheModeDefaultCache]);
     END_BLOCK_OBJC_EXCEPTIONS;
 
-    if (!stagingMtlBuffer) {
-        LOG(WebGPU, "GPUBuffer::unmap(): Unable to create staging buffer!");
+    if (!stagingMtlBuffer && errorScopes) {
+        errorScopes->generateError("", GPUErrorFilter::OutOfMemory);
         return;
     }
 
@@ -258,10 +250,10 @@ void GPUBuffer::copyStagingBufferToGPU()
     END_BLOCK_OBJC_EXCEPTIONS;
 }
 
-void GPUBuffer::unmap()
+void GPUBuffer::unmap(GPUErrorScopes* errorScopes)
 {
-    if (!m_isMappedFromCreation && !isMappable()) {
-        LOG(WebGPU, "GPUBuffer::unmap(): Invalid operation: buffer is not mappable!");
+    if (!m_isMappedFromCreation && !isMappable() && errorScopes) {
+        errorScopes->generatePrefixedError("Invalid operation: GPUBuffer is not mappable!");
         return;
     }
 
@@ -271,7 +263,7 @@ void GPUBuffer::unmap()
             ASSERT(m_platformBuffer && m_platformBuffer.get().contents);
             memcpy(m_platformBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
         } else if (m_isMappedFromCreation)
-            copyStagingBufferToGPU();
+            copyStagingBufferToGPU(errorScopes);
 
         m_isMappedFromCreation = false;
         m_stagingBuffer = nullptr;
@@ -284,10 +276,10 @@ void GPUBuffer::unmap()
     }
 }
 
-void GPUBuffer::destroy()
+void GPUBuffer::destroy(GPUErrorScopes* errorScopes)
 {
     if (state() == State::Mapped)
-        unmap();
+        unmap(errorScopes);
 
     m_platformBuffer = nullptr;
 }