diff --git a/dawn.json b/dawn.json index a0fa36bd01..b89bbe341c 100644 --- a/dawn.json +++ b/dawn.json @@ -160,6 +160,9 @@ } ] }, + "buffer create mapped callback": { + "category": "natively defined" + }, "buffer copy view": { "category": "structure", "extensible": true, @@ -417,6 +420,14 @@ {"name": "descriptor", "type": "buffer descriptor", "annotation": "const*"} ] }, + { + "name": "create buffer mapped async", + "args": [ + {"name": "descriptor", "type": "buffer descriptor", "annotation": "const*"}, + {"name": "callback", "type": "buffer create mapped callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, { "name": "create command encoder", "returns": "command encoder" diff --git a/dawn_wire.json b/dawn_wire.json index b935b54061..e178732009 100644 --- a/dawn_wire.json +++ b/dawn_wire.json @@ -36,6 +36,12 @@ { "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" }, { "name": "result", "type": "ObjectHandle", "handle_type": "buffer" } ], + "device create buffer mapped async": [ + { "name": "device", "type": "device" }, + { "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" }, + { "name": "request serial", "type": "uint32_t" }, + { "name": "result", "type": "ObjectHandle", "handle_type": "buffer" } + ], "destroy object": [ { "name": "object type", "type": "ObjectType" }, { "name": "object id", "type": "ObjectId" } @@ -75,6 +81,7 @@ "BufferSetSubData", "BufferUnmap", "DeviceCreateBufferMapped", + "DeviceCreateBufferMappedAsync", "QueueCreateFence", "FenceGetCompletedValue", "QueueSignal" diff --git a/generator/templates/api.h b/generator/templates/api.h index e558fb51ed..bdd10ec815 100644 --- a/generator/templates/api.h +++ b/generator/templates/api.h @@ -49,6 +49,9 @@ // Custom types depending on the target language typedef void (*DawnDeviceErrorCallback)(const char* message, void* userdata); +typedef void (*DawnBufferCreateMappedCallback)(DawnBufferMapAsyncStatus status, + DawnCreateBufferMappedResult result, + void* userdata); typedef void (*DawnBufferMapReadCallback)(DawnBufferMapAsyncStatus status, const void* data, uint64_t dataLength, diff --git a/generator/templates/mock_api.cpp b/generator/templates/mock_api.cpp index 7f2cdd01d9..b41d41ec83 100644 --- a/generator/templates/mock_api.cpp +++ b/generator/templates/mock_api.cpp @@ -60,6 +60,17 @@ void ProcTableAsClass::DeviceSetErrorCallback(DawnDevice self, OnDeviceSetErrorCallback(self, callback, userdata); } +void ProcTableAsClass::DeviceCreateBufferMappedAsync(DawnDevice self, + const DawnBufferDescriptor* descriptor, + DawnBufferCreateMappedCallback callback, + void* userdata) { + auto object = reinterpret_cast(self); + object->createBufferMappedCallback = callback; + object->userdata1 = userdata; + + OnDeviceCreateBufferMappedAsyncCallback(self, descriptor, callback, userdata); +} + void ProcTableAsClass::BufferMapReadAsync(DawnBuffer self, DawnBufferMapReadCallback callback, void* userdata) { @@ -95,12 +106,16 @@ void ProcTableAsClass::CallDeviceErrorCallback(DawnDevice device, const char* me auto object = reinterpret_cast(device); object->deviceErrorCallback(message, object->userdata1); } -void ProcTableAsClass::CallMapReadCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, const void* data, uint32_t dataLength) { +void ProcTableAsClass::CallCreateBufferMappedCallback(DawnDevice device, DawnBufferMapAsyncStatus status, DawnCreateBufferMappedResult result) { + auto object = reinterpret_cast(device); + object->createBufferMappedCallback(status, result, object->userdata1); +} +void ProcTableAsClass::CallMapReadCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, const void* data, uint64_t dataLength) { auto object = reinterpret_cast(buffer); object->mapReadCallback(status, data, dataLength, object->userdata1); } -void ProcTableAsClass::CallMapWriteCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, void* data, uint32_t dataLength) { +void ProcTableAsClass::CallMapWriteCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, void* data, uint64_t dataLength) { auto object = reinterpret_cast(buffer); object->mapWriteCallback(status, data, dataLength, object->userdata1); } diff --git a/generator/templates/mock_api.h b/generator/templates/mock_api.h index b8640823fe..f075130085 100644 --- a/generator/templates/mock_api.h +++ b/generator/templates/mock_api.h @@ -54,6 +54,10 @@ class ProcTableAsClass { void DeviceSetErrorCallback(DawnDevice self, DawnDeviceErrorCallback callback, void* userdata); + void DeviceCreateBufferMappedAsync(DawnDevice self, + const DawnBufferDescriptor* descriptor, + DawnBufferCreateMappedCallback callback, + void* userdata); void BufferMapReadAsync(DawnBuffer self, DawnBufferMapReadCallback callback, void* userdata); @@ -69,6 +73,10 @@ class ProcTableAsClass { virtual void OnDeviceSetErrorCallback(DawnDevice device, DawnDeviceErrorCallback callback, void* userdata) = 0; + virtual void OnDeviceCreateBufferMappedAsyncCallback(DawnDevice self, + const DawnBufferDescriptor* descriptor, + DawnBufferCreateMappedCallback callback, + void* userdata) = 0; virtual void OnBufferMapReadAsyncCallback(DawnBuffer buffer, DawnBufferMapReadCallback callback, void* userdata) = 0; @@ -82,13 +90,15 @@ class ProcTableAsClass { // Calls the stored callbacks void CallDeviceErrorCallback(DawnDevice device, const char* message); - void CallMapReadCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, const void* data, uint32_t dataLength); - void CallMapWriteCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, void* data, uint32_t dataLength); + void CallCreateBufferMappedCallback(DawnDevice device, DawnBufferMapAsyncStatus status, DawnCreateBufferMappedResult result); + void CallMapReadCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, const void* data, uint64_t dataLength); + void CallMapWriteCallback(DawnBuffer buffer, DawnBufferMapAsyncStatus status, void* data, uint64_t dataLength); void CallFenceOnCompletionCallback(DawnFence fence, DawnFenceCompletionStatus status); struct Object { ProcTableAsClass* procs = nullptr; DawnDeviceErrorCallback deviceErrorCallback = nullptr; + DawnBufferCreateMappedCallback createBufferMappedCallback = nullptr; DawnBufferMapReadCallback mapReadCallback = nullptr; DawnBufferMapWriteCallback mapWriteCallback = nullptr; DawnFenceOnCompletionCallback fenceOnCompletionCallback = nullptr; @@ -124,6 +134,7 @@ class MockProcTable : public ProcTableAsClass { {% endfor %} MOCK_METHOD3(OnDeviceSetErrorCallback, void(DawnDevice device, DawnDeviceErrorCallback callback, void* userdata)); + MOCK_METHOD4(OnDeviceCreateBufferMappedAsyncCallback, void(DawnDevice device, const DawnBufferDescriptor* descriptor, DawnBufferCreateMappedCallback callback, void* userdata)); MOCK_METHOD3(OnBufferMapReadAsyncCallback, void(DawnBuffer buffer, DawnBufferMapReadCallback callback, void* userdata)); MOCK_METHOD3(OnBufferMapWriteAsyncCallback, void(DawnBuffer buffer, DawnBufferMapWriteCallback callback, void* userdata)); MOCK_METHOD4(OnFenceOnCompletionCallback, diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index 26d0d44e28..207a398b7b 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -68,6 +68,7 @@ namespace dawn_native { DeviceBase::~DeviceBase() { // Devices must explicitly free the uploader ASSERT(mDynamicUploader == nullptr); + ASSERT(mDeferredCreateBufferMappedAsyncResults.empty()); } void DeviceBase::HandleError(const char* message) { @@ -291,6 +292,25 @@ namespace dawn_native { return result; } + void DeviceBase::CreateBufferMappedAsync(const BufferDescriptor* descriptor, + dawn::BufferCreateMappedCallback callback, + void* userdata) { + DawnCreateBufferMappedResult result = CreateBufferMapped(descriptor); + + DawnBufferMapAsyncStatus status = DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS; + if (result.data == nullptr || result.dataLength != descriptor->size) { + status = DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR; + } + + DeferredCreateBufferMappedAsync deferred_info; + deferred_info.callback = callback; + deferred_info.status = status; + deferred_info.result = result; + deferred_info.userdata = userdata; + + // The callback is deferred so it matches the async behavior of WebGPU. + mDeferredCreateBufferMappedAsyncResults.push_back(deferred_info); + } CommandEncoderBase* DeviceBase::CreateCommandEncoder() { return new CommandEncoderBase(this); } @@ -387,6 +407,12 @@ namespace dawn_native { void DeviceBase::Tick() { TickImpl(); + { + auto deferredResults = std::move(mDeferredCreateBufferMappedAsyncResults); + for (const auto& deferred : deferredResults) { + deferred.callback(deferred.status, deferred.result, deferred.userdata); + } + } mFenceSignalTracker->Tick(GetCompletedCommandSerial()); } diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index a79cdd9bba..e980b32827 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -108,6 +108,9 @@ namespace dawn_native { BindGroupLayoutBase* CreateBindGroupLayout(const BindGroupLayoutDescriptor* descriptor); BufferBase* CreateBuffer(const BufferDescriptor* descriptor); DawnCreateBufferMappedResult CreateBufferMapped(const BufferDescriptor* descriptor); + void CreateBufferMappedAsync(const BufferDescriptor* descriptor, + dawn::BufferCreateMappedCallback callback, + void* userdata); CommandEncoderBase* CreateCommandEncoder(); ComputePipelineBase* CreateComputePipeline(const ComputePipelineDescriptor* descriptor); PipelineLayoutBase* CreatePipelineLayout(const PipelineLayoutDescriptor* descriptor); @@ -202,7 +205,15 @@ namespace dawn_native { struct Caches; std::unique_ptr mCaches; + struct DeferredCreateBufferMappedAsync { + dawn::BufferCreateMappedCallback callback; + DawnBufferMapAsyncStatus status; + DawnCreateBufferMappedResult result; + void* userdata; + }; + std::unique_ptr mFenceSignalTracker; + std::vector mDeferredCreateBufferMappedAsyncResults; dawn::DeviceErrorCallback mErrorCallback = nullptr; void* mErrorUserdata = 0; diff --git a/src/dawn_wire/client/ApiProcs.cpp b/src/dawn_wire/client/ApiProcs.cpp index 4ae80c6484..2a499534d3 100644 --- a/src/dawn_wire/client/ApiProcs.cpp +++ b/src/dawn_wire/client/ApiProcs.cpp @@ -101,6 +101,57 @@ namespace dawn_wire { namespace client { return result; } + void ClientDeviceCreateBufferMappedAsync(DawnDevice cDevice, + const DawnBufferDescriptor* descriptor, + DawnBufferCreateMappedCallback callback, + void* userdata) { + Device* device = reinterpret_cast(cDevice); + Client* wireClient = device->GetClient(); + + auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device); + Buffer* buffer = bufferObjectAndSerial->object.get(); + + uint32_t serial = buffer->requestSerial++; + + struct CreateBufferMappedInfo { + DawnBuffer buffer; + DawnBufferCreateMappedCallback callback; + void* userdata; + }; + + CreateBufferMappedInfo* info = new CreateBufferMappedInfo; + info->buffer = reinterpret_cast(buffer); + info->callback = callback; + info->userdata = userdata; + + Buffer::MapRequestData request; + request.writeCallback = [](DawnBufferMapAsyncStatus status, void* data, uint64_t dataLength, + void* userdata) { + auto info = std::unique_ptr( + static_cast(userdata)); + + DawnCreateBufferMappedResult result; + result.buffer = info->buffer; + result.data = data; + result.dataLength = dataLength; + + info->callback(status, result, info->userdata); + }; + request.userdata = info; + request.isWrite = true; + buffer->requests[serial] = request; + + DeviceCreateBufferMappedAsyncCmd cmd; + cmd.device = cDevice; + cmd.descriptor = descriptor; + cmd.requestSerial = serial; + cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->serial}; + + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(wireClient->GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer, *wireClient); + } + uint64_t ClientFenceGetCompletedValue(DawnFence cSelf) { auto fence = reinterpret_cast(cSelf); return fence->completedValue; diff --git a/src/dawn_wire/server/ServerBuffer.cpp b/src/dawn_wire/server/ServerBuffer.cpp index 4dfa80ee2e..595fe165ed 100644 --- a/src/dawn_wire/server/ServerBuffer.cpp +++ b/src/dawn_wire/server/ServerBuffer.cpp @@ -86,6 +86,32 @@ namespace dawn_wire { namespace server { return true; } + bool Server::DoDeviceCreateBufferMappedAsync(DawnDevice device, + const DawnBufferDescriptor* descriptor, + uint32_t requestSerial, + ObjectHandle bufferResult) { + if (!DoDeviceCreateBufferMapped(device, descriptor, bufferResult)) { + return false; + } + + auto* bufferData = BufferObjects().Get(bufferResult.id); + ASSERT(bufferData != nullptr); + + ReturnBufferMapWriteAsyncCallbackCmd cmd; + cmd.buffer = ObjectHandle{bufferResult.id, bufferResult.serial}; + cmd.requestSerial = requestSerial; + cmd.status = bufferData->mapWriteState == BufferMapWriteState::Mapped + ? DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS + : DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR; + cmd.dataLength = bufferData->mappedDataSize; + + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); + + return true; + } + bool Server::DoBufferSetSubDataInternal(ObjectId bufferId, uint64_t start, uint64_t offset, diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp index 6b691d9049..dc21b2aeb1 100644 --- a/src/tests/end2end/BufferTests.cpp +++ b/src/tests/end2end/BufferTests.cpp @@ -226,6 +226,7 @@ DAWN_INSTANTIATE_TEST(BufferSetSubDataTests, OpenGLBackend, VulkanBackend); +// TODO(enga): These tests should use the testing toggle to initialize resources to 1. class CreateBufferMappedTests : public DawnTest { protected: static void MapReadCallback(DawnBufferMapAsyncStatus status, @@ -248,37 +249,97 @@ class CreateBufferMappedTests : public DawnTest { return mappedData; } + void CheckResultStartsZeroed(const dawn::CreateBufferMappedResult& result, uint64_t size) { + ASSERT_EQ(result.dataLength, size); + for (uint64_t i = 0; i < result.dataLength; ++i) { + uint8_t value = *(reinterpret_cast(result.data) + i); + ASSERT_EQ(value, 0u); + } + } + + dawn::CreateBufferMappedResult CreateBufferMapped(dawn::BufferUsageBit usage, uint64_t size) { + dawn::BufferDescriptor descriptor; + descriptor.nextInChain = nullptr; + descriptor.size = size; + descriptor.usage = usage; + + dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); + CheckResultStartsZeroed(result, size); + return result; + } + + dawn::CreateBufferMappedResult CreateBufferMappedWithData(dawn::BufferUsageBit usage, + const std::vector& data) { + size_t byteLength = data.size() * sizeof(uint32_t); + dawn::CreateBufferMappedResult result = CreateBufferMapped(usage, byteLength); + memcpy(result.data, data.data(), byteLength); + + return result; + } + + template + dawn::CreateBufferMappedResult CreateBufferMappedAsyncAndWait(dawn::BufferUsageBit usage, + uint64_t size) { + dawn::BufferDescriptor descriptor; + descriptor.nextInChain = nullptr; + descriptor.size = size; + descriptor.usage = usage; + + struct ResultInfo { + dawn::CreateBufferMappedResult result; + bool done = false; + } resultInfo; + + device.CreateBufferMappedAsync( + &descriptor, + [](DawnBufferMapAsyncStatus status, DawnCreateBufferMappedResult result, + void* userdata) { + ASSERT_EQ(status, expectedStatus); + auto* resultInfo = reinterpret_cast(userdata); + resultInfo->result.buffer = dawn::Buffer::Acquire(result.buffer); + resultInfo->result.data = result.data; + resultInfo->result.dataLength = result.dataLength; + resultInfo->done = true; + }, + &resultInfo); + + while (!resultInfo.done) { + WaitABit(); + } + + CheckResultStartsZeroed(resultInfo.result, size); + + return resultInfo.result; + } + + dawn::CreateBufferMappedResult CreateBufferMappedAsyncWithDataAndWait( + dawn::BufferUsageBit usage, + const std::vector& data) { + size_t byteLength = data.size() * sizeof(uint32_t); + dawn::CreateBufferMappedResult result = CreateBufferMappedAsyncAndWait(usage, byteLength); + memcpy(result.data, data.data(), byteLength); + + return result; + } + private: const void* mappedData = nullptr; }; // Test that the simplest CreateBufferMapped works for MapWrite buffers. TEST_P(CreateBufferMappedTests, MapWriteUsageSmall) { - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = 4; - descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc; - uint32_t myData = 230502; - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, &myData, sizeof(myData)); + dawn::CreateBufferMappedResult result = CreateBufferMappedWithData( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); result.buffer.Unmap(); - EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); } // Test that the simplest CreateBufferMapped works for MapRead buffers. TEST_P(CreateBufferMappedTests, MapReadUsageSmall) { - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = 4; - descriptor.usage = dawn::BufferUsageBit::MapRead; - uint32_t myData = 230502; - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, &myData, sizeof(myData)); + dawn::CreateBufferMappedResult result = + CreateBufferMappedWithData(dawn::BufferUsageBit::MapRead, {myData}); result.buffer.Unmap(); const void* mappedData = MapReadAsyncAndWait(result.buffer); @@ -288,15 +349,9 @@ TEST_P(CreateBufferMappedTests, MapReadUsageSmall) { // Test that the simplest CreateBufferMapped works for non-mappable buffers. TEST_P(CreateBufferMappedTests, NonMappableUsageSmall) { - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = 4; - descriptor.usage = dawn::BufferUsageBit::TransferSrc; - uint32_t myData = 4239; - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, &myData, sizeof(myData)); + dawn::CreateBufferMappedResult result = + CreateBufferMappedWithData(dawn::BufferUsageBit::TransferSrc, {myData}); result.buffer.Unmap(); EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); @@ -310,14 +365,8 @@ TEST_P(CreateBufferMappedTests, MapWriteUsageLarge) { myData.push_back(i); } - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = static_cast(kDataSize * sizeof(uint32_t)); - descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc; - - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, myData.data(), kDataSize * sizeof(uint32_t)); + dawn::CreateBufferMappedResult result = CreateBufferMappedWithData( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); result.buffer.Unmap(); EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), result.buffer, 0, kDataSize); @@ -331,14 +380,8 @@ TEST_P(CreateBufferMappedTests, MapReadUsageLarge) { myData.push_back(i); } - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = static_cast(kDataSize * sizeof(uint32_t)); - descriptor.usage = dawn::BufferUsageBit::MapRead; - - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, myData.data(), kDataSize * sizeof(uint32_t)); + dawn::CreateBufferMappedResult result = + CreateBufferMappedWithData(dawn::BufferUsageBit::MapRead, myData); result.buffer.Unmap(); const void* mappedData = MapReadAsyncAndWait(result.buffer); @@ -354,59 +397,19 @@ TEST_P(CreateBufferMappedTests, NonMappableUsageLarge) { myData.push_back(i); } - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = static_cast(kDataSize * sizeof(uint32_t)); - descriptor.usage = dawn::BufferUsageBit::TransferSrc; - - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, myData.data(), kDataSize * sizeof(uint32_t)); + dawn::CreateBufferMappedResult result = + CreateBufferMappedWithData(dawn::BufferUsageBit::TransferSrc, {myData}); result.buffer.Unmap(); EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), result.buffer, 0, kDataSize); } -// Test that CreateBufferMapped returns zero-initialized data -// TODO(enga): This should use the testing toggle to initialize resources to 1. -TEST_P(CreateBufferMappedTests, MappableZeroInitialized) { - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = 4; - descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc; - - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - ASSERT_EQ(*reinterpret_cast(result.data), 0); - result.buffer.Unmap(); -} - -// Test that CreateBufferMapped returns zero-initialized data -// TODO(enga): This should use the testing toggle to initialize resources to 1. -TEST_P(CreateBufferMappedTests, NonMappableZeroInitialized) { - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = 4; - descriptor.usage = dawn::BufferUsageBit::TransferSrc; - - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - ASSERT_EQ(*reinterpret_cast(result.data), 0); - result.buffer.Unmap(); -} - // Test that mapping a buffer is valid after CreateBufferMapped and Unmap TEST_P(CreateBufferMappedTests, CreateThenMapSuccess) { - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = 4; - descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc; - static uint32_t myData = 230502; static uint32_t myData2 = 1337; - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, &myData, sizeof(myData)); + dawn::CreateBufferMappedResult result = CreateBufferMappedWithData( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); result.buffer.Unmap(); EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); @@ -432,15 +435,9 @@ TEST_P(CreateBufferMappedTests, CreateThenMapSuccess) { // Test that is is invalid to map a buffer twice when using CreateBufferMapped TEST_P(CreateBufferMappedTests, CreateThenMapBeforeUnmapFailure) { - dawn::BufferDescriptor descriptor; - descriptor.nextInChain = nullptr; - descriptor.size = 4; - descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc; - uint32_t myData = 230502; - dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor); - ASSERT_EQ(result.dataLength, descriptor.size); - memcpy(result.data, &myData, sizeof(myData)); + dawn::CreateBufferMappedResult result = CreateBufferMappedWithData( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); ASSERT_DEVICE_ERROR([&]() { bool done = false; @@ -463,6 +460,140 @@ TEST_P(CreateBufferMappedTests, CreateThenMapBeforeUnmapFailure) { EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); } +// Test that the simplest CreateBufferMappedAsync works for MapWrite buffers. +TEST_P(CreateBufferMappedTests, MapWriteUsageSmallAsync) { + uint32_t myData = 230502; + dawn::CreateBufferMappedResult result = CreateBufferMappedAsyncWithDataAndWait( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); + result.buffer.Unmap(); + EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); +} + +// Test that the simplest CreateBufferMappedAsync works for MapRead buffers. +TEST_P(CreateBufferMappedTests, MapReadUsageSmallAsync) { + uint32_t myData = 230502; + dawn::CreateBufferMappedResult result = + CreateBufferMappedAsyncWithDataAndWait(dawn::BufferUsageBit::MapRead, {myData}); + result.buffer.Unmap(); + + const void* mappedData = MapReadAsyncAndWait(result.buffer); + ASSERT_EQ(myData, *reinterpret_cast(mappedData)); + result.buffer.Unmap(); +} + +// Test that the simplest CreateBufferMappedAsync works for non-mappable buffers. +TEST_P(CreateBufferMappedTests, NonMappableUsageSmallAsync) { + uint32_t myData = 4239; + dawn::CreateBufferMappedResult result = + CreateBufferMappedAsyncWithDataAndWait(dawn::BufferUsageBit::TransferSrc, {myData}); + result.buffer.Unmap(); + + EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); +} + +// Test CreateBufferMappedAsync for a large MapWrite buffer +TEST_P(CreateBufferMappedTests, MapWriteUsageLargeAsync) { + constexpr uint64_t kDataSize = 1000 * 1000; + std::vector myData; + for (uint32_t i = 0; i < kDataSize; ++i) { + myData.push_back(i); + } + + dawn::CreateBufferMappedResult result = CreateBufferMappedAsyncWithDataAndWait( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); + result.buffer.Unmap(); + + EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), result.buffer, 0, kDataSize); +} + +// Test CreateBufferMappedAsync for a large MapRead buffer +TEST_P(CreateBufferMappedTests, MapReadUsageLargeAsync) { + constexpr uint64_t kDataSize = 1000 * 1000; + std::vector myData; + for (uint32_t i = 0; i < kDataSize; ++i) { + myData.push_back(i); + } + + dawn::CreateBufferMappedResult result = + CreateBufferMappedAsyncWithDataAndWait(dawn::BufferUsageBit::MapRead, {myData}); + result.buffer.Unmap(); + + const void* mappedData = MapReadAsyncAndWait(result.buffer); + ASSERT_EQ(0, memcmp(mappedData, myData.data(), kDataSize * sizeof(uint32_t))); + result.buffer.Unmap(); +} + +// Test CreateBufferMappedAsync for a large non-mappable buffer +TEST_P(CreateBufferMappedTests, NonMappableUsageLargeAsync) { + constexpr uint64_t kDataSize = 1000 * 1000; + std::vector myData; + for (uint32_t i = 0; i < kDataSize; ++i) { + myData.push_back(i); + } + + dawn::CreateBufferMappedResult result = + CreateBufferMappedAsyncWithDataAndWait(dawn::BufferUsageBit::TransferSrc, {myData}); + result.buffer.Unmap(); + + EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), result.buffer, 0, kDataSize); +} + +// Test that mapping a buffer is valid after CreateBufferMappedAsync and Unmap +TEST_P(CreateBufferMappedTests, CreateThenMapSuccessAsync) { + static uint32_t myData = 230502; + static uint32_t myData2 = 1337; + dawn::CreateBufferMappedResult result = CreateBufferMappedAsyncWithDataAndWait( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); + result.buffer.Unmap(); + + EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); + + bool done = false; + result.buffer.MapWriteAsync( + [](DawnBufferMapAsyncStatus status, void* data, uint64_t, void* userdata) { + ASSERT_EQ(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, status); + ASSERT_NE(nullptr, data); + + *static_cast(data) = myData2; + *static_cast(userdata) = true; + }, + &done); + + while (!done) { + WaitABit(); + } + + result.buffer.Unmap(); + EXPECT_BUFFER_U32_EQ(myData2, result.buffer, 0); +} + +// Test that is is invalid to map a buffer twice when using CreateBufferMappedAsync +TEST_P(CreateBufferMappedTests, CreateThenMapBeforeUnmapFailureAsync) { + uint32_t myData = 230502; + dawn::CreateBufferMappedResult result = CreateBufferMappedAsyncWithDataAndWait( + dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc, {myData}); + + ASSERT_DEVICE_ERROR([&]() { + bool done = false; + result.buffer.MapWriteAsync( + [](DawnBufferMapAsyncStatus status, void* data, uint64_t, void* userdata) { + ASSERT_EQ(DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR, status); + ASSERT_EQ(nullptr, data); + + *static_cast(userdata) = true; + }, + &done); + + while (!done) { + WaitABit(); + } + }()); + + // CreateBufferMappedAsync is unaffected by the MapWrite error. + result.buffer.Unmap(); + EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0); +} + DAWN_INSTANTIATE_TEST(CreateBufferMappedTests, D3D12Backend, MetalBackend, diff --git a/src/tests/unittests/wire/WireBufferMappingTests.cpp b/src/tests/unittests/wire/WireBufferMappingTests.cpp index 03b939ef66..f82b0ad90f 100644 --- a/src/tests/unittests/wire/WireBufferMappingTests.cpp +++ b/src/tests/unittests/wire/WireBufferMappingTests.cpp @@ -59,6 +59,28 @@ namespace { mockBufferMapWriteCallback->Call(status, lastMapWritePointer, dataLength, userdata); } + class MockBufferCreateMappedCallback { + public: + MOCK_METHOD5(Call, + void(DawnBufferMapAsyncStatus status, + DawnBuffer buffer, + uint32_t* ptr, + uint64_t dataLength, + void* userdata)); + }; + + std::unique_ptr> mockCreateBufferMappedCallback; + uint32_t* lastCreateMappedPointer = nullptr; + void ToMockCreateBufferMappedCallback(DawnBufferMapAsyncStatus status, + DawnCreateBufferMappedResult result, + void* userdata) { + // Assume the data is uint32_t to make writing matchers easier + lastCreateMappedPointer = static_cast(result.data); + // Unpack DawnCreateBufferMappedResult to make writing matchers easier + mockCreateBufferMappedCallback->Call(status, result.buffer, lastCreateMappedPointer, + result.dataLength, userdata); + } + } // anonymous namespace class WireBufferMappingTests : public WireTest { @@ -72,6 +94,8 @@ class WireBufferMappingTests : public WireTest { mockBufferMapReadCallback = std::make_unique>(); mockBufferMapWriteCallback = std::make_unique>(); + mockCreateBufferMappedCallback = + std::make_unique>(); DawnBufferDescriptor descriptor; descriptor.nextInChain = nullptr; @@ -91,6 +115,7 @@ class WireBufferMappingTests : public WireTest { // Delete mocks so that expectations are checked mockBufferMapReadCallback = nullptr; mockBufferMapWriteCallback = nullptr; + mockCreateBufferMappedCallback = nullptr; } void FlushServer() { @@ -98,6 +123,7 @@ class WireBufferMappingTests : public WireTest { Mock::VerifyAndClearExpectations(&mockBufferMapReadCallback); Mock::VerifyAndClearExpectations(&mockBufferMapWriteCallback); + Mock::VerifyAndClearExpectations(&mockCreateBufferMappedCallback); } protected: @@ -612,3 +638,184 @@ TEST_F(WireBufferMappingTests, CreateBufferMappedThenMapFailure) { FlushClient(); } + +// Test successful CreateBufferMappedAsync +TEST_F(WireBufferMappingTests, CreateBufferMappedAsyncSuccess) { + DawnBufferDescriptor descriptor; + descriptor.nextInChain = nullptr; + + DawnCreateBufferMappedResult apiResult; + uint32_t serverBufferContent = 31337; + apiResult.buffer = apiBuffer; + apiResult.data = reinterpret_cast(&serverBufferContent); + apiResult.dataLength = 4; + + uint32_t updatedContent = 4242; + uint32_t zero = 0; + + dawnDeviceCreateBufferMappedAsync(device, &descriptor, ToMockCreateBufferMappedCallback, nullptr); + + EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) + .WillOnce(Return(apiResult)) + .RetiresOnSaturation(); + + FlushClient(); + + DawnBuffer buffer; + // The callback always gets a buffer full of zeroes. + EXPECT_CALL(*mockCreateBufferMappedCallback, Call(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, _, + Pointee(Eq(zero)), sizeof(uint32_t), _)) + .WillOnce(::testing::SaveArg<1>(&buffer)); + + FlushServer(); + + // Write something to the mapped pointer + *lastCreateMappedPointer = updatedContent; + + dawnBufferUnmap(buffer); + EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); + + FlushClient(); + + // After the buffer is unmapped, the content of the buffer is updated on the server + ASSERT_EQ(serverBufferContent, updatedContent); +} + +// Test CreateBufferMappedAsync with map error +TEST_F(WireBufferMappingTests, CreateBufferMappedAsyncMapError) { + DawnBufferDescriptor descriptor; + descriptor.nextInChain = nullptr; + + DawnCreateBufferMappedResult apiResult; + apiResult.buffer = apiBuffer; + apiResult.data = nullptr; // error mapping + apiResult.dataLength = 4; + + dawnDeviceCreateBufferMappedAsync(device, &descriptor, ToMockCreateBufferMappedCallback, nullptr); + + EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) + .WillOnce(Return(apiResult)) + .RetiresOnSaturation(); + + FlushClient(); + + DawnBuffer buffer; + EXPECT_CALL(*mockCreateBufferMappedCallback, + Call(DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR, _, nullptr, 0, _)) + .WillOnce(::testing::SaveArg<1>(&buffer)); + + FlushServer(); + + dawnBufferUnmap(buffer); + EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); + + FlushClient(); +} + +// Test that the CreateBufferMappedCallback isn't fired twice when unmap() is called inside the +// callback +TEST_F(WireBufferMappingTests, UnmapInsideCreateBufferMappedAsyncCallback) { + DawnBufferDescriptor descriptor; + descriptor.nextInChain = nullptr; + + DawnCreateBufferMappedResult apiResult; + uint32_t serverBufferContent = 31337; + apiResult.buffer = apiBuffer; + apiResult.data = reinterpret_cast(&serverBufferContent); + apiResult.dataLength = 4; + + uint32_t zero = 0; + + dawnDeviceCreateBufferMappedAsync(device, &descriptor, ToMockCreateBufferMappedCallback, nullptr); + + EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) + .WillOnce(Return(apiResult)) + .RetiresOnSaturation(); + + FlushClient(); + + DawnBuffer buffer; + // The callback always gets a buffer full of zeroes. + EXPECT_CALL(*mockCreateBufferMappedCallback, Call(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, _, + Pointee(Eq(zero)), sizeof(uint32_t), _)) + .WillOnce(DoAll(::testing::SaveArg<1>(&buffer), + InvokeWithoutArgs([&]() { dawnBufferUnmap(buffer); }))); + + FlushServer(); + + EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); + + FlushClient(); +} + +// Test that the CreateBufferMappedCallback isn't fired twice when the buffer is deleted inside +// the callback +TEST_F(WireBufferMappingTests, ReleaseInsideCreateBufferMappedAsyncCallback) { + DawnBufferDescriptor descriptor; + descriptor.nextInChain = nullptr; + + DawnCreateBufferMappedResult apiResult; + uint32_t serverBufferContent = 31337; + apiResult.buffer = apiBuffer; + apiResult.data = reinterpret_cast(&serverBufferContent); + apiResult.dataLength = 4; + + uint32_t zero = 0; + + dawnDeviceCreateBufferMappedAsync(device, &descriptor, ToMockCreateBufferMappedCallback, nullptr); + + EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) + .WillOnce(Return(apiResult)) + .RetiresOnSaturation(); + + FlushClient(); + + DawnBuffer buffer; + // The callback always gets a buffer full of zeroes. + EXPECT_CALL(*mockCreateBufferMappedCallback, Call(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, _, + Pointee(Eq(zero)), sizeof(uint32_t), _)) + .WillOnce(DoAll(::testing::SaveArg<1>(&buffer), + InvokeWithoutArgs([&]() { dawnBufferRelease(buffer); }))); + + FlushServer(); + + EXPECT_CALL(api, BufferRelease(apiBuffer)); + + FlushClient(); +} + +// Test that the CreateBufferMappedCallback isn't fired twice when the buffer is destroyed inside +// the callback +TEST_F(WireBufferMappingTests, DestroyInsideCreateBufferMappedAsyncCallback) { + DawnBufferDescriptor descriptor; + descriptor.nextInChain = nullptr; + + DawnCreateBufferMappedResult apiResult; + uint32_t serverBufferContent = 31337; + apiResult.buffer = apiBuffer; + apiResult.data = reinterpret_cast(&serverBufferContent); + apiResult.dataLength = 4; + + uint32_t zero = 0; + + dawnDeviceCreateBufferMappedAsync(device, &descriptor, ToMockCreateBufferMappedCallback, nullptr); + + EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) + .WillOnce(Return(apiResult)) + .RetiresOnSaturation(); + + FlushClient(); + + DawnBuffer buffer; + // The callback always gets a buffer full of zeroes. + EXPECT_CALL(*mockCreateBufferMappedCallback, Call(DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, _, + Pointee(Eq(zero)), sizeof(uint32_t), _)) + .WillOnce(DoAll(::testing::SaveArg<1>(&buffer), + InvokeWithoutArgs([&]() { dawnBufferDestroy(buffer); }))); + + FlushServer(); + + EXPECT_CALL(api, BufferDestroy(apiBuffer)); + + FlushClient(); +}