Wire: Make validation error prior to OOM if mappedAtCreation == false
This patch updates the validations about CreateBuffer() with dawn_wire
to match the latest WebGPU SPEC.
According to the SPEC, the validations in CreateBuffer() should be
executed in the below order:
1. If mappedAtCreation == true, return nullptr and a RangeError will be
generated in Chromium.
2. Validate BufferDescriptor and check if there is OOM at device timeline
3. Check if there is OOM at content timeline
Bug: dawn:1586
Change-Id: I97ff5f82a42208442ddf6e46e66381c3b3680450
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/109040
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/dawn.json b/dawn.json
index 817991f..972d68d 100644
--- a/dawn.json
+++ b/dawn.json
@@ -994,7 +994,10 @@
{
"name": "create error buffer",
"returns": "buffer",
- "tags": ["dawn"]
+ "tags": ["dawn"],
+ "args": [
+ {"name": "descriptor", "type": "buffer descriptor", "annotation": "const*"}
+ ]
},
{
"name": "create command encoder",
@@ -2567,7 +2570,8 @@
{"value": 1003, "name": "dawn encoder internal usage descriptor", "tags": ["dawn"]},
{"value": 1004, "name": "dawn instance descriptor", "tags": ["dawn", "native"]},
{"value": 1005, "name": "dawn cache device descriptor", "tags": ["dawn", "native"]},
- {"value": 1006, "name": "dawn adapter properties power preference", "tags": ["dawn", "native"]}
+ {"value": 1006, "name": "dawn adapter properties power preference", "tags": ["dawn", "native"]},
+ {"value": 1007, "name": "dawn buffer descriptor error info from wire client", "tags": ["dawn"]}
]
},
"texture": {
@@ -2979,5 +2983,14 @@
"members": [
{"name": "power preference", "type": "power preference", "default": "undefined"}
]
+ },
+ "dawn buffer descriptor error info from wire client": {
+ "category": "structure",
+ "chained": "in",
+ "chain roots": ["buffer descriptor"],
+ "tags": ["dawn"],
+ "members": [
+ {"name": "out of memory", "type": "bool", "default": "false"}
+ ]
}
}
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index fd13a3c..9256829 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -17,6 +17,7 @@
#include <cstdio>
#include <cstring>
#include <limits>
+#include <string>
#include <utility>
#include "dawn/common/Alloc.h"
@@ -96,7 +97,7 @@
} // anonymous namespace
-MaybeError ValidateBufferDescriptor(DeviceBase*, const BufferDescriptor* descriptor) {
+MaybeError ValidateBufferDescriptor(DeviceBase* device, const BufferDescriptor* descriptor) {
DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr");
DAWN_TRY(ValidateBufferUsage(descriptor->usage));
@@ -124,6 +125,14 @@
"Buffer is mapped at creation but its size (%u) is not a multiple of 4.",
descriptor->size);
+ // TODO(dawn:1525): Change to validation error after the deprecation period.
+ if (descriptor->size > device->GetLimits().v1.maxBufferSize) {
+ std::string warning =
+ absl::StrFormat("Buffer size (%u) exceeds the max buffer size limit (%u).",
+ descriptor->size, device->GetLimits().v1.maxBufferSize);
+ device->EmitDeprecationWarning(warning.c_str());
+ }
+
return {};
}
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index ff08f66..19c5df1 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1193,9 +1193,27 @@
// For Dawn Wire
-BufferBase* DeviceBase::APICreateErrorBuffer() {
- BufferDescriptor desc = {};
- return BufferBase::MakeError(this, &desc);
+BufferBase* DeviceBase::APICreateErrorBuffer(const BufferDescriptor* desc) {
+ BufferDescriptor fakeDescriptor = *desc;
+ fakeDescriptor.nextInChain = nullptr;
+
+ // The validation errors on BufferDescriptor should be prior to any OOM errors when
+ // MapppedAtCreation == false.
+ MaybeError maybeError = ValidateBufferDescriptor(this, &fakeDescriptor);
+ if (maybeError.IsError()) {
+ ConsumedError(maybeError.AcquireError(), "calling %s.CreateBuffer(%s).", this, desc);
+ } else {
+ const DawnBufferDescriptorErrorInfoFromWireClient* clientErrorInfo = nullptr;
+ FindInChain(desc->nextInChain, &clientErrorInfo);
+ if (clientErrorInfo != nullptr && clientErrorInfo->outOfMemory) {
+ ConsumedError(DAWN_OUT_OF_MEMORY_ERROR("Failed to allocate memory for buffer mapping"));
+ }
+ }
+
+ // Set the size of the error buffer to 0 as this function is called only when an OOM happens at
+ // the client side.
+ fakeDescriptor.size = 0;
+ return BufferBase::MakeError(this, &fakeDescriptor);
}
ExternalTextureBase* DeviceBase::APICreateErrorExternalTexture() {
@@ -1408,15 +1426,7 @@
ResultOrError<Ref<BufferBase>> DeviceBase::CreateBuffer(const BufferDescriptor* descriptor) {
DAWN_TRY(ValidateIsAlive());
if (IsValidationEnabled()) {
- DAWN_TRY_CONTEXT(ValidateBufferDescriptor(this, descriptor), "validating %s", descriptor);
-
- // TODO(dawn:1525): Change to validation error after the deprecation period.
- if (descriptor->size > mLimits.v1.maxBufferSize) {
- std::string warning =
- absl::StrFormat("Buffer size (%u) exceeds the max buffer size limit (%u).",
- descriptor->size, mLimits.v1.maxBufferSize);
- EmitDeprecationWarning(warning.c_str());
- }
+ DAWN_TRY(ValidateBufferDescriptor(this, descriptor));
}
Ref<BufferBase> buffer;
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 6fd0d5a..a7f96d1 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -272,7 +272,7 @@
InternalPipelineStore* GetInternalPipelineStore();
// For Dawn Wire
- BufferBase* APICreateErrorBuffer();
+ BufferBase* APICreateErrorBuffer(const BufferDescriptor* desc);
ExternalTextureBase* APICreateErrorExternalTexture();
TextureBase* APICreateErrorTexture(const TextureDescriptor* desc);
diff --git a/src/dawn/tests/end2end/BufferTests.cpp b/src/dawn/tests/end2end/BufferTests.cpp
index 3ba9261..db9f84c 100644
--- a/src/dawn/tests/end2end/BufferTests.cpp
+++ b/src/dawn/tests/end2end/BufferTests.cpp
@@ -819,6 +819,10 @@
descriptor.size = 1ull << 50;
// TODO(dawn:1525): remove warning expectation after the deprecation period.
ASSERT_DEVICE_ERROR(EXPECT_DEPRECATION_WARNING(device.CreateBuffer(&descriptor)));
+
+ // Validation errors should always be prior to OOM.
+ descriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::Uniform;
+ ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
}
// Test that a very large buffer mappedAtCreation fails gracefully.
@@ -844,12 +848,22 @@
// Test an enormous buffer fails
descriptor.size = std::numeric_limits<uint64_t>::max();
- ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
+ if (UsesWire()) {
+ wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+ ASSERT_EQ(nullptr, buffer.Get());
+ } else {
+ ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
+ }
// UINT64_MAX may be special cased. Test a smaller, but really large buffer also fails
descriptor.size = 1ull << 50;
- // TODO(dawn:1525): remove warning expectation after the deprecation period.
- ASSERT_DEVICE_ERROR(EXPECT_DEPRECATION_WARNING(device.CreateBuffer(&descriptor)));
+ if (UsesWire()) {
+ wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+ ASSERT_EQ(nullptr, buffer.Get());
+ } else {
+ // TODO(dawn:1525): remove warning expectation after the deprecation period.
+ ASSERT_DEVICE_ERROR(EXPECT_DEPRECATION_WARNING(device.CreateBuffer(&descriptor)));
+ }
}
// Test mappable buffer
@@ -864,12 +878,22 @@
// Test an enormous buffer fails
descriptor.size = std::numeric_limits<uint64_t>::max();
- ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
+ if (UsesWire()) {
+ wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+ ASSERT_EQ(nullptr, buffer.Get());
+ } else {
+ ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
+ }
// UINT64_MAX may be special cased. Test a smaller, but really large buffer also fails
descriptor.size = 1ull << 50;
- // TODO(dawn:1525): remove warning expectation after the deprecation period.
- ASSERT_DEVICE_ERROR(EXPECT_DEPRECATION_WARNING(device.CreateBuffer(&descriptor)));
+ if (UsesWire()) {
+ wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+ ASSERT_EQ(nullptr, buffer.Get());
+ } else {
+ // TODO(dawn:1525): remove warning expectation after the deprecation period.
+ ASSERT_DEVICE_ERROR(EXPECT_DEPRECATION_WARNING(device.CreateBuffer(&descriptor)));
+ }
}
}
diff --git a/src/dawn/tests/unittests/validation/BufferValidationTests.cpp b/src/dawn/tests/unittests/validation/BufferValidationTests.cpp
index 3b2f3ae..3a0b4e5 100644
--- a/src/dawn/tests/unittests/validation/BufferValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/BufferValidationTests.cpp
@@ -849,13 +849,20 @@
uint64_t kStupidLarge = uint64_t(1) << uint64_t(63);
- wgpu::Buffer buffer;
- ASSERT_DEVICE_ERROR(buffer = BufferMappedAtCreation(
- kStupidLarge, wgpu::BufferUsage::Storage | wgpu::BufferUsage::MapRead));
+ if (UsesWire()) {
+ wgpu::Buffer buffer = BufferMappedAtCreation(
+ kStupidLarge, wgpu::BufferUsage::Storage | wgpu::BufferUsage::MapRead);
+ ASSERT_EQ(nullptr, buffer.Get());
+ } else {
+ wgpu::Buffer buffer;
+ ASSERT_DEVICE_ERROR(
+ buffer = BufferMappedAtCreation(
+ kStupidLarge, wgpu::BufferUsage::Storage | wgpu::BufferUsage::MapRead));
- // GetMappedRange after mappedAtCreation OOM case returns nullptr.
- ASSERT_EQ(buffer.GetConstMappedRange(), nullptr);
- ASSERT_EQ(buffer.GetConstMappedRange(), buffer.GetMappedRange());
+ // GetMappedRange after mappedAtCreation OOM case returns nullptr.
+ ASSERT_EQ(buffer.GetConstMappedRange(), nullptr);
+ ASSERT_EQ(buffer.GetConstMappedRange(), buffer.GetMappedRange());
+ }
}
// Test validation of the GetMappedRange parameters
diff --git a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
index e49ee7d..5a7bc23 100644
--- a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
@@ -680,8 +680,7 @@
FlushClient();
}
-// Check that trying to create a buffer of size MAX_SIZE_T is an error handling in the client
-// and never gets to the server-side.
+// Check that trying to create a buffer of size MAX_SIZE_T won't get OOM error at the client side.
TEST_F(WireBufferMappingTests, MaxSizeMappableBufferOOMDirectly) {
size_t kOOMSize = std::numeric_limits<size_t>::max();
WGPUBuffer apiBuffer = api.GetNewBuffer();
@@ -694,8 +693,6 @@
descriptor.mappedAtCreation = true;
wgpuDeviceCreateBuffer(device, &descriptor);
- EXPECT_CALL(api, DeviceInjectError(apiDevice, WGPUErrorType_OutOfMemory, _));
- EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice)).WillOnce(Return(apiBuffer));
FlushClient();
}
@@ -706,8 +703,7 @@
descriptor.size = kOOMSize;
wgpuDeviceCreateBuffer(device, &descriptor);
- EXPECT_CALL(api, DeviceInjectError(apiDevice, WGPUErrorType_OutOfMemory, _));
- EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice)).WillOnce(Return(apiBuffer));
+ EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
FlushClient();
}
@@ -718,8 +714,7 @@
descriptor.size = kOOMSize;
wgpuDeviceCreateBuffer(device, &descriptor);
- EXPECT_CALL(api, DeviceInjectError(apiDevice, WGPUErrorType_OutOfMemory, _));
- EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice)).WillOnce(Return(apiBuffer));
+ EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
FlushClient();
}
}
diff --git a/src/dawn/tests/unittests/wire/WireMemoryTransferServiceTests.cpp b/src/dawn/tests/unittests/wire/WireMemoryTransferServiceTests.cpp
index b2ce580..913d6a7 100644
--- a/src/dawn/tests/unittests/wire/WireMemoryTransferServiceTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireMemoryTransferServiceTests.cpp
@@ -891,7 +891,7 @@
descriptor.mappedAtCreation = true;
WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor);
- EXPECT_EQ(nullptr, wgpuBufferGetMappedRange(buffer, 0, sizeof(mBufferContent)));
+ EXPECT_EQ(nullptr, buffer);
}
// Test buffer creation with mappedAtCreation DeserializeWriteHandle failure.
diff --git a/src/dawn/wire/client/Buffer.cpp b/src/dawn/wire/client/Buffer.cpp
index 4315452..43335ef 100644
--- a/src/dawn/wire/client/Buffer.cpp
+++ b/src/dawn/wire/client/Buffer.cpp
@@ -24,6 +24,20 @@
namespace dawn::wire::client {
+namespace {
+WGPUBuffer CreateErrorBufferOOMAtClient(Device* device, const WGPUBufferDescriptor* descriptor) {
+ if (descriptor->mappedAtCreation) {
+ return nullptr;
+ }
+ WGPUBufferDescriptor errorBufferDescriptor = *descriptor;
+ WGPUDawnBufferDescriptorErrorInfoFromWireClient errorInfo = {};
+ errorInfo.chain.sType = WGPUSType_DawnBufferDescriptorErrorInfoFromWireClient;
+ errorInfo.outOfMemory = true;
+ errorBufferDescriptor.nextInChain = &errorInfo.chain;
+ return device->CreateErrorBuffer(&errorBufferDescriptor);
+}
+} // anonymous namespace
+
// static
WGPUBuffer Buffer::Create(Device* device, const WGPUBufferDescriptor* descriptor) {
Client* wireClient = device->GetClient();
@@ -32,8 +46,7 @@
(descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 ||
descriptor->mappedAtCreation;
if (mappable && descriptor->size >= std::numeric_limits<size_t>::max()) {
- device->InjectError(WGPUErrorType_OutOfMemory, "Buffer is too large for map usage");
- return device->CreateErrorBuffer();
+ return CreateErrorBufferOOMAtClient(device, descriptor);
}
std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr;
@@ -55,8 +68,7 @@
readHandle.reset(
wireClient->GetMemoryTransferService()->CreateReadHandle(descriptor->size));
if (readHandle == nullptr) {
- device->InjectError(WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
- return CreateError(device, descriptor);
+ return CreateErrorBufferOOMAtClient(device, descriptor);
}
readHandleCreateInfoLength = readHandle->SerializeCreateSize();
cmd.readHandleCreateInfoLength = readHandleCreateInfoLength;
@@ -67,8 +79,7 @@
writeHandle.reset(
wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size));
if (writeHandle == nullptr) {
- device->InjectError(WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
- return CreateError(device, descriptor);
+ return CreateErrorBufferOOMAtClient(device, descriptor);
}
writeHandleCreateInfoLength = writeHandle->SerializeCreateSize();
cmd.writeHandleCreateInfoLength = writeHandleCreateInfoLength;
@@ -131,6 +142,8 @@
DeviceCreateErrorBufferCmd cmd;
cmd.self = ToAPI(device);
+ cmd.selfId = device->GetWireId();
+ cmd.descriptor = descriptor;
cmd.result = buffer->GetWireHandle();
client->SerializeCommand(cmd);
diff --git a/src/dawn/wire/client/Device.cpp b/src/dawn/wire/client/Device.cpp
index f2bffe2..f63875f 100644
--- a/src/dawn/wire/client/Device.cpp
+++ b/src/dawn/wire/client/Device.cpp
@@ -199,9 +199,8 @@
return Buffer::Create(this, descriptor);
}
-WGPUBuffer Device::CreateErrorBuffer() {
- WGPUBufferDescriptor fakeDescriptor = {};
- return Buffer::CreateError(this, &fakeDescriptor);
+WGPUBuffer Device::CreateErrorBuffer(const WGPUBufferDescriptor* descriptor) {
+ return Buffer::CreateError(this, descriptor);
}
WGPUQuerySet Device::CreateQuerySet(const WGPUQuerySetDescriptor* descriptor) {
diff --git a/src/dawn/wire/client/Device.h b/src/dawn/wire/client/Device.h
index 90f3eda..9ca7b31 100644
--- a/src/dawn/wire/client/Device.h
+++ b/src/dawn/wire/client/Device.h
@@ -41,7 +41,7 @@
void InjectError(WGPUErrorType type, const char* message);
bool PopErrorScope(WGPUErrorCallback callback, void* userdata);
WGPUBuffer CreateBuffer(const WGPUBufferDescriptor* descriptor);
- WGPUBuffer CreateErrorBuffer();
+ WGPUBuffer CreateErrorBuffer(const WGPUBufferDescriptor* descriptor);
void CreateComputePipelineAsync(WGPUComputePipelineDescriptor const* descriptor,
WGPUCreateComputePipelineAsyncCallback callback,
void* userdata);
diff --git a/webgpu-cts/expectations.txt b/webgpu-cts/expectations.txt
index 3bd78e0..29ebf86 100644
--- a/webgpu-cts/expectations.txt
+++ b/webgpu-cts/expectations.txt
@@ -206,11 +206,6 @@
crbug.com/dawn/1357 webgpu:api,operation,shader_module,compilation_info:offset_and_length:valid=false;name="unicode" [ Failure ]
################################################################################
-# createBuffer_invalid_and_oom failures
-################################################################################
-crbug.com/dawn/0000 webgpu:api,validation,buffer,create:createBuffer_invalid_and_oom: [ Failure ]
-
-################################################################################
# atan2 shader execution failures
# Very slow, many failing. Skip for now.
# KEEP