// Copyright 2017 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include "dawn/tests/DawnTest.h" class BufferMappingTests : public DawnTest { protected: void MapAsyncAndWait(const wgpu::Buffer& buffer, wgpu::MapMode mode, size_t offset, size_t size) { bool done = false; buffer.MapAsync( mode, offset, size, [](WGPUBufferMapAsyncStatus status, void* userdata) { ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status); *static_cast(userdata) = true; }, &done); while (!done) { WaitABit(); } } wgpu::Buffer CreateMapReadBuffer(uint64_t size) { wgpu::BufferDescriptor descriptor; descriptor.size = size; descriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; return device.CreateBuffer(&descriptor); } wgpu::Buffer CreateMapWriteBuffer(uint64_t size) { wgpu::BufferDescriptor descriptor; descriptor.size = size; descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc; return device.CreateBuffer(&descriptor); } }; void CheckMapping(const void* actual, const void* expected, size_t size) { EXPECT_NE(actual, nullptr); if (actual != nullptr) { EXPECT_EQ(0, memcmp(actual, expected, size)); } } // Test that the simplest map read works TEST_P(BufferMappingTests, MapRead_Basic) { wgpu::Buffer buffer = CreateMapReadBuffer(4); uint32_t myData = 0x01020304; constexpr size_t kSize = sizeof(myData); queue.WriteBuffer(buffer, 0, &myData, kSize); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4); CheckMapping(buffer.GetConstMappedRange(), &myData, kSize); CheckMapping(buffer.GetConstMappedRange(0, kSize), &myData, kSize); buffer.Unmap(); } // Test map-reading a zero-sized buffer. TEST_P(BufferMappingTests, MapRead_ZeroSized) { wgpu::Buffer buffer = CreateMapReadBuffer(0); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, wgpu::kWholeMapSize); ASSERT_NE(buffer.GetConstMappedRange(), nullptr); buffer.Unmap(); } // Test map-reading with a non-zero offset TEST_P(BufferMappingTests, MapRead_NonZeroOffset) { wgpu::Buffer buffer = CreateMapReadBuffer(12); uint32_t myData[3] = {0x01020304, 0x05060708, 0x090A0B0C}; queue.WriteBuffer(buffer, 0, &myData, sizeof(myData)); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 8, 4); ASSERT_EQ(myData[2], *static_cast(buffer.GetConstMappedRange(8))); buffer.Unmap(); } // Map read and unmap twice. Test that both of these two iterations work. TEST_P(BufferMappingTests, MapRead_Twice) { wgpu::Buffer buffer = CreateMapReadBuffer(4); uint32_t myData = 0x01020304; queue.WriteBuffer(buffer, 0, &myData, sizeof(myData)); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4); ASSERT_EQ(myData, *static_cast(buffer.GetConstMappedRange())); buffer.Unmap(); myData = 0x05060708; queue.WriteBuffer(buffer, 0, &myData, sizeof(myData)); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4); ASSERT_EQ(myData, *static_cast(buffer.GetConstMappedRange())); buffer.Unmap(); } // Map read and test multiple get mapped range data TEST_P(BufferMappingTests, MapRead_MultipleMappedRange) { wgpu::Buffer buffer = CreateMapReadBuffer(12); uint32_t myData[] = {0x00010203, 0x04050607, 0x08090a0b}; queue.WriteBuffer(buffer, 0, &myData, 12); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 12); ASSERT_EQ(myData[0], *static_cast(buffer.GetConstMappedRange(0))); ASSERT_EQ(myData[1], *(static_cast(buffer.GetConstMappedRange(0)) + 1)); ASSERT_EQ(myData[2], *(static_cast(buffer.GetConstMappedRange(0)) + 2)); ASSERT_EQ(myData[2], *static_cast(buffer.GetConstMappedRange(8))); buffer.Unmap(); } // Test map-reading a large buffer. TEST_P(BufferMappingTests, MapRead_Large) { constexpr uint32_t kDataSize = 1000 * 1000; constexpr size_t kByteSize = kDataSize * sizeof(uint32_t); wgpu::Buffer buffer = CreateMapReadBuffer(kByteSize); std::vector myData; for (uint32_t i = 0; i < kDataSize; ++i) { myData.push_back(i); } queue.WriteBuffer(buffer, 0, myData.data(), kByteSize); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, kByteSize); EXPECT_EQ(nullptr, buffer.GetConstMappedRange(0, kByteSize + 4)); EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(), myData.data(), kByteSize)); EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(8), myData.data() + 2, kByteSize - 8)); EXPECT_EQ( 0, memcmp(buffer.GetConstMappedRange(8, kByteSize - 8), myData.data() + 2, kByteSize - 8)); buffer.Unmap(); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 16, kByteSize - 16); // Size is too big. EXPECT_EQ(nullptr, buffer.GetConstMappedRange(16, kByteSize - 12)); // Offset defaults to 0 which is less than 16 EXPECT_EQ(nullptr, buffer.GetConstMappedRange()); // Offset less than 8 is less than 16 EXPECT_EQ(nullptr, buffer.GetConstMappedRange(8)); // Test a couple values. EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(16), myData.data() + 4, kByteSize - 16)); EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(24), myData.data() + 6, kByteSize - 24)); buffer.Unmap(); } // Test that GetConstMappedRange works inside map-read callback TEST_P(BufferMappingTests, MapRead_InCallback) { constexpr size_t kBufferSize = 12; wgpu::Buffer buffer = CreateMapReadBuffer(kBufferSize); uint32_t myData[3] = {0x01020304, 0x05060708, 0x090A0B0C}; static constexpr size_t kSize = sizeof(myData); queue.WriteBuffer(buffer, 0, &myData, kSize); struct UserData { bool done; wgpu::Buffer buffer; void* expected; }; UserData user{false, buffer, &myData}; buffer.MapAsync( wgpu::MapMode::Read, 0, kBufferSize, [](WGPUBufferMapAsyncStatus status, void* userdata) { UserData* user = static_cast(userdata); EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status); if (status == WGPUBufferMapAsyncStatus_Success) { CheckMapping(user->buffer.GetConstMappedRange(), user->expected, kSize); CheckMapping(user->buffer.GetConstMappedRange(0, kSize), user->expected, kSize); CheckMapping(user->buffer.GetConstMappedRange(8, 4), static_cast(user->expected) + 2, sizeof(uint32_t)); user->buffer.Unmap(); } user->done = true; }, &user); while (!user.done) { WaitABit(); } } // Test that the simplest map write works. TEST_P(BufferMappingTests, MapWrite_Basic) { wgpu::Buffer buffer = CreateMapWriteBuffer(4); uint32_t myData = 2934875; MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4); ASSERT_NE(nullptr, buffer.GetMappedRange()); ASSERT_NE(nullptr, buffer.GetConstMappedRange()); memcpy(buffer.GetMappedRange(), &myData, sizeof(myData)); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(myData, buffer, 0); } // Test that the simplest map write works with a range. TEST_P(BufferMappingTests, MapWrite_BasicRange) { wgpu::Buffer buffer = CreateMapWriteBuffer(4); uint32_t myData = 2934875; MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4); ASSERT_NE(nullptr, buffer.GetMappedRange(0, 4)); ASSERT_NE(nullptr, buffer.GetConstMappedRange(0, 4)); memcpy(buffer.GetMappedRange(), &myData, sizeof(myData)); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(myData, buffer, 0); } // Test map-writing a zero-sized buffer. TEST_P(BufferMappingTests, MapWrite_ZeroSized) { wgpu::Buffer buffer = CreateMapWriteBuffer(0); MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize); ASSERT_NE(buffer.GetConstMappedRange(), nullptr); ASSERT_NE(buffer.GetMappedRange(), nullptr); buffer.Unmap(); } // Test map-writing with a non-zero offset. TEST_P(BufferMappingTests, MapWrite_NonZeroOffset) { wgpu::Buffer buffer = CreateMapWriteBuffer(12); uint32_t myData = 2934875; MapAsyncAndWait(buffer, wgpu::MapMode::Write, 8, 4); memcpy(buffer.GetMappedRange(8), &myData, sizeof(myData)); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(myData, buffer, 8); } // Map, write and unmap twice. Test that both of these two iterations work. TEST_P(BufferMappingTests, MapWrite_Twice) { wgpu::Buffer buffer = CreateMapWriteBuffer(4); uint32_t myData = 2934875; MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4); memcpy(buffer.GetMappedRange(), &myData, sizeof(myData)); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(myData, buffer, 0); myData = 9999999; MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4); memcpy(buffer.GetMappedRange(), &myData, sizeof(myData)); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(myData, buffer, 0); } // Map write and unmap twice with different ranges and make sure the first write is preserved TEST_P(BufferMappingTests, MapWrite_TwicePreserve) { wgpu::Buffer buffer = CreateMapWriteBuffer(12); uint32_t data1 = 0x08090a0b; size_t offset1 = 8; MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset1, wgpu::kWholeMapSize); memcpy(buffer.GetMappedRange(offset1), &data1, sizeof(data1)); buffer.Unmap(); uint32_t data2 = 0x00010203; size_t offset2 = 0; MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset2, wgpu::kWholeMapSize); memcpy(buffer.GetMappedRange(offset2), &data2, sizeof(data2)); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(data1, buffer, offset1); EXPECT_BUFFER_U32_EQ(data2, buffer, offset2); } // Map write and unmap twice with overlapping ranges and make sure data is updated correctly TEST_P(BufferMappingTests, MapWrite_TwiceRangeOverlap) { wgpu::Buffer buffer = CreateMapWriteBuffer(16); uint32_t data1[] = {0x01234567, 0x89abcdef}; size_t offset1 = 8; MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset1, 8); memcpy(buffer.GetMappedRange(offset1, 8), data1, 8); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(0x00000000, buffer, 0); EXPECT_BUFFER_U32_EQ(0x00000000, buffer, 4); EXPECT_BUFFER_U32_EQ(0x01234567, buffer, 8); EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 12); uint32_t data2[] = {0x01234567, 0x89abcdef, 0x55555555}; size_t offset2 = 0; MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset2, 12); memcpy(buffer.GetMappedRange(offset2, 12), data2, 12); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(0x01234567, buffer, 0); EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 4); EXPECT_BUFFER_U32_EQ(0x55555555, buffer, 8); EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 12); } // Map write and test multiple mapped range data get updated correctly TEST_P(BufferMappingTests, MapWrite_MultipleMappedRange) { wgpu::Buffer buffer = CreateMapWriteBuffer(12); uint32_t data1 = 0x08090a0b; size_t offset1 = 8; uint32_t data2 = 0x00010203; size_t offset2 = 0; MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 12); memcpy(buffer.GetMappedRange(offset1), &data1, sizeof(data1)); memcpy(buffer.GetMappedRange(offset2), &data2, sizeof(data2)); buffer.Unmap(); EXPECT_BUFFER_U32_EQ(data1, buffer, offset1); EXPECT_BUFFER_U32_EQ(data2, buffer, offset2); } // Test mapping a large buffer. TEST_P(BufferMappingTests, MapWrite_Large) { constexpr uint32_t kDataSize = 1000 * 1000; constexpr size_t kByteSize = kDataSize * sizeof(uint32_t); wgpu::Buffer buffer = CreateMapWriteBuffer(kDataSize * sizeof(uint32_t)); std::vector myData; for (uint32_t i = 0; i < kDataSize; ++i) { myData.push_back(i); } MapAsyncAndWait(buffer, wgpu::MapMode::Write, 16, kByteSize - 20); EXPECT_EQ(nullptr, buffer.GetMappedRange()); EXPECT_EQ(nullptr, buffer.GetMappedRange(0)); EXPECT_EQ(nullptr, buffer.GetMappedRange(8)); EXPECT_EQ(nullptr, buffer.GetMappedRange(16, kByteSize - 8)); memcpy(buffer.GetMappedRange(16, kByteSize - 20), myData.data(), kByteSize - 20); buffer.Unmap(); EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffer, 16, kDataSize - 5); } // Stress test mapping many buffers. TEST_P(BufferMappingTests, MapWrite_ManySimultaneous) { constexpr uint32_t kDataSize = 1000; std::vector myData; for (uint32_t i = 0; i < kDataSize; ++i) { myData.push_back(i); } constexpr uint32_t kBuffers = 100; std::array buffers; uint32_t mapCompletedCount = 0; // Create buffers and request mapping them. wgpu::BufferDescriptor descriptor; descriptor.size = static_cast(kDataSize * sizeof(uint32_t)); descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc; for (uint32_t i = 0; i < kBuffers; ++i) { buffers[i] = device.CreateBuffer(&descriptor); buffers[i].MapAsync( wgpu::MapMode::Write, 0, descriptor.size, [](WGPUBufferMapAsyncStatus status, void* userdata) { ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status); (*static_cast(userdata))++; }, &mapCompletedCount); } // Wait for all mappings to complete while (mapCompletedCount != kBuffers) { WaitABit(); } // All buffers are mapped, write into them and unmap them all. for (uint32_t i = 0; i < kBuffers; ++i) { memcpy(buffers[i].GetMappedRange(0, descriptor.size), myData.data(), descriptor.size); buffers[i].Unmap(); } // Check the content of the buffers. for (uint32_t i = 0; i < kBuffers; ++i) { EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffers[i], 0, kDataSize); } } // Test that the map offset isn't updated when the call is an error. TEST_P(BufferMappingTests, OffsetNotUpdatedOnError) { uint32_t data[3] = {0xCA7, 0xB0A7, 0xBA7}; wgpu::Buffer buffer = CreateMapReadBuffer(sizeof(data)); queue.WriteBuffer(buffer, 0, data, sizeof(data)); // Map the buffer but do not wait on the result yet. bool done1 = false; bool done2 = false; buffer.MapAsync( wgpu::MapMode::Read, 8, 4, [](WGPUBufferMapAsyncStatus status, void* userdata) { ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status); *static_cast(userdata) = true; }, &done1); // Call MapAsync another time, the callback will be rejected with error status // (but doesn't produce a validation error) and mMapOffset is not updated // because the buffer is already being mapped and it doesn't allow multiple // MapAsync requests. buffer.MapAsync( wgpu::MapMode::Read, 0, 4, [](WGPUBufferMapAsyncStatus status, void* userdata) { ASSERT_EQ(WGPUBufferMapAsyncStatus_Error, status); *static_cast(userdata) = true; }, &done2); while (!done1 || !done2) { WaitABit(); } // mMapOffset has not been updated so it should still be 4, which is data[1] ASSERT_EQ(0, memcmp(buffer.GetConstMappedRange(8), &data[2], sizeof(uint32_t))); } // Test that Get(Const)MappedRange work inside map-write callback. TEST_P(BufferMappingTests, MapWrite_InCallbackDefault) { wgpu::Buffer buffer = CreateMapWriteBuffer(4); static constexpr uint32_t myData = 2934875; static constexpr size_t kSize = sizeof(myData); struct UserData { bool done; wgpu::Buffer buffer; }; UserData user{false, buffer}; buffer.MapAsync( wgpu::MapMode::Write, 0, kSize, [](WGPUBufferMapAsyncStatus status, void* userdata) { UserData* user = static_cast(userdata); EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status); if (status == WGPUBufferMapAsyncStatus_Success) { EXPECT_NE(nullptr, user->buffer.GetConstMappedRange()); void* ptr = user->buffer.GetMappedRange(); EXPECT_NE(nullptr, ptr); if (ptr != nullptr) { uint32_t data = myData; memcpy(ptr, &data, kSize); } user->buffer.Unmap(); } user->done = true; }, &user); while (!user.done) { WaitABit(); } EXPECT_BUFFER_U32_EQ(myData, buffer, 0); } // Test that Get(Const)MappedRange with range work inside map-write callback. TEST_P(BufferMappingTests, MapWrite_InCallbackRange) { wgpu::Buffer buffer = CreateMapWriteBuffer(4); static constexpr uint32_t myData = 2934875; static constexpr size_t kSize = sizeof(myData); struct UserData { bool done; wgpu::Buffer buffer; }; UserData user{false, buffer}; buffer.MapAsync( wgpu::MapMode::Write, 0, kSize, [](WGPUBufferMapAsyncStatus status, void* userdata) { UserData* user = static_cast(userdata); EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status); if (status == WGPUBufferMapAsyncStatus_Success) { EXPECT_NE(nullptr, user->buffer.GetConstMappedRange(0, kSize)); void* ptr = user->buffer.GetMappedRange(0, kSize); EXPECT_NE(nullptr, ptr); if (ptr != nullptr) { uint32_t data = myData; memcpy(ptr, &data, kSize); } user->buffer.Unmap(); } user->done = true; }, &user); while (!user.done) { WaitABit(); } EXPECT_BUFFER_U32_EQ(myData, buffer, 0); } // Regression test for crbug.com/dawn/969 where this test // produced invalid barriers. TEST_P(BufferMappingTests, MapWrite_ZeroSizedTwice) { wgpu::Buffer buffer = CreateMapWriteBuffer(0); MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize); buffer.Unmap(); MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize); } // Regression test for crbug.com/1421170 where dropping a buffer which needs // padding bytes initialization resulted in a use-after-free. TEST_P(BufferMappingTests, RegressChromium1421170) { // Create a mappable buffer of size 7. It will be internally // aligned such that the padding bytes need to be zero initialized. wgpu::BufferDescriptor descriptor; descriptor.size = 7; descriptor.usage = wgpu::BufferUsage::MapWrite; descriptor.mappedAtCreation = false; wgpu::Buffer buffer = device.CreateBuffer(&descriptor); // Drop the buffer. The pending commands to zero initialize the // padding bytes should stay valid. buffer = nullptr; // Flush pending commands. device.Tick(); } DAWN_INSTANTIATE_TEST(BufferMappingTests, D3D11Backend(), D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()); class BufferMappingCallbackTests : public BufferMappingTests { protected: void SubmitCommandBuffer(wgpu::Buffer buffer) { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); { // Record enough commands to make sure the submission cannot be completed by GPU too // quick. constexpr int kRepeatCount = 50; constexpr int kBufferSize = 1024 * 1024 * 10; wgpu::Buffer tempWriteBuffer = CreateMapWriteBuffer(kBufferSize); wgpu::Buffer tempReadBuffer = CreateMapReadBuffer(kBufferSize); for (int i = 0; i < kRepeatCount; ++i) { encoder.CopyBufferToBuffer(tempWriteBuffer, 0, tempReadBuffer, 0, kBufferSize); } } if (buffer) { if (buffer.GetUsage() & wgpu::BufferUsage::CopyDst) { encoder.ClearBuffer(buffer); } else { wgpu::Buffer tempBuffer = CreateMapReadBuffer(buffer.GetSize()); encoder.CopyBufferToBuffer(buffer, 0, tempBuffer, 0, buffer.GetSize()); } } wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); } void Wait(std::vector& done) { do { WaitABit(); } while (std::any_of(done.begin(), done.end(), [](bool done) { return !done; })); } }; TEST_P(BufferMappingCallbackTests, EmptySubmissionAndThenMap) { wgpu::Buffer buffer = CreateMapWriteBuffer(4); MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize); buffer.Unmap(); std::vector done = {false, false}; // 1. submission without using buffer. SubmitCommandBuffer({}); queue.OnSubmittedWorkDone( 0, [](WGPUQueueWorkDoneStatus status, void* userdata) { EXPECT_EQ(status, WGPUQueueWorkDoneStatus_Success); auto& done = *static_cast*>(userdata); done[0] = true; // Step 2 callback should be called first, this is the second. const std::vector kExpected = {true, true}; EXPECT_EQ(done, kExpected); }, &done); // 2. buffer.MapAsync( wgpu::MapMode::Write, 0, wgpu::kWholeMapSize, [](WGPUBufferMapAsyncStatus status, void* userdata) { EXPECT_EQ(status, WGPUBufferMapAsyncStatus_Success); auto& done = *static_cast*>(userdata); done[1] = true; // The buffer is not used by step 1, so this callback is called first. const std::vector kExpected = {false, true}; EXPECT_EQ(done, kExpected); }, &done); Wait(done); } TEST_P(BufferMappingCallbackTests, UseTheBufferAndThenMap) { wgpu::Buffer buffer = CreateMapWriteBuffer(4); MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize); buffer.Unmap(); std::vector done = {false, false}; // 1. Submit a command buffer which uses the buffer SubmitCommandBuffer(buffer); queue.OnSubmittedWorkDone( 0, [](WGPUQueueWorkDoneStatus status, void* userdata) { EXPECT_EQ(status, WGPUQueueWorkDoneStatus_Success); auto& done = *static_cast*>(userdata); done[0] = true; // This callback should be called first const std::vector kExpected = {true, false}; EXPECT_EQ(done, kExpected); }, &done); // 2. buffer.MapAsync( wgpu::MapMode::Write, 0, wgpu::kWholeMapSize, [](WGPUBufferMapAsyncStatus status, void* userdata) { EXPECT_EQ(status, WGPUBufferMapAsyncStatus_Success); auto& done = *static_cast*>(userdata); done[1] = true; // The buffer is used by step 1, so this callback is called second. const std::vector kExpected = {true, true}; EXPECT_EQ(done, kExpected); }, &done); Wait(done); buffer.Unmap(); } TEST_P(BufferMappingCallbackTests, EmptySubmissionWriteAndThenMap) { wgpu::Buffer buffer = CreateMapReadBuffer(4); MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, wgpu::kWholeMapSize); buffer.Unmap(); std::vector done = {false, false}; // 1. submission without using buffer. SubmitCommandBuffer({}); queue.OnSubmittedWorkDone( 0, [](WGPUQueueWorkDoneStatus status, void* userdata) { EXPECT_EQ(status, WGPUQueueWorkDoneStatus_Success); auto& done = *static_cast*>(userdata); done[0] = true; // Step 2 callback should be called first, this is the second. const std::vector kExpected = {true, false}; EXPECT_EQ(done, kExpected); }, &done); int32_t data = 0x12345678; queue.WriteBuffer(buffer, 0, &data, sizeof(data)); // 2. buffer.MapAsync( wgpu::MapMode::Read, 0, wgpu::kWholeMapSize, [](WGPUBufferMapAsyncStatus status, void* userdata) { EXPECT_EQ(status, WGPUBufferMapAsyncStatus_Success); auto& done = *static_cast*>(userdata); done[1] = true; // The buffer is not used by step 1, so this callback is called first. const std::vector kExpected = {true, true}; EXPECT_EQ(done, kExpected); }, &done); Wait(done); buffer.Unmap(); } DAWN_INSTANTIATE_TEST(BufferMappingCallbackTests, D3D11Backend(), D3D12Backend(), MetalBackend(), VulkanBackend()); class BufferMappedAtCreationTests : public DawnTest { protected: static void MapCallback(WGPUBufferMapAsyncStatus status, void* userdata) { EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status); *static_cast(userdata) = true; } const void* MapAsyncAndWait(const wgpu::Buffer& buffer, wgpu::MapMode mode, size_t size) { bool done = false; buffer.MapAsync(mode, 0, size, MapCallback, &done); while (!done) { WaitABit(); } return buffer.GetConstMappedRange(0, size); } void UnmapBuffer(const wgpu::Buffer& buffer) { buffer.Unmap(); } wgpu::Buffer BufferMappedAtCreation(wgpu::BufferUsage usage, uint64_t size) { wgpu::BufferDescriptor descriptor; descriptor.size = size; descriptor.usage = usage; descriptor.mappedAtCreation = true; return device.CreateBuffer(&descriptor); } wgpu::Buffer BufferMappedAtCreationWithData(wgpu::BufferUsage usage, const std::vector& data) { size_t byteLength = data.size() * sizeof(uint32_t); wgpu::Buffer buffer = BufferMappedAtCreation(usage, byteLength); memcpy(buffer.GetMappedRange(), data.data(), byteLength); return buffer; } }; // Test that the simplest mappedAtCreation works for MapWrite buffers. TEST_P(BufferMappedAtCreationTests, MapWriteUsageSmall) { uint32_t myData = 230502; wgpu::Buffer buffer = BufferMappedAtCreationWithData( wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData}); UnmapBuffer(buffer); EXPECT_BUFFER_U32_EQ(myData, buffer, 0); } // Test that the simplest mappedAtCreation works for MapRead buffers. TEST_P(BufferMappedAtCreationTests, MapReadUsageSmall) { uint32_t myData = 230502; wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::MapRead, {myData}); UnmapBuffer(buffer); const void* mappedData = MapAsyncAndWait(buffer, wgpu::MapMode::Read, 4); ASSERT_EQ(myData, *reinterpret_cast(mappedData)); UnmapBuffer(buffer); } // Test that the simplest mappedAtCreation works for non-mappable buffers. TEST_P(BufferMappedAtCreationTests, NonMappableUsageSmall) { uint32_t myData = 4239; wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::CopySrc, {myData}); UnmapBuffer(buffer); EXPECT_BUFFER_U32_EQ(myData, buffer, 0); } // Test mappedAtCreation for a large MapWrite buffer TEST_P(BufferMappedAtCreationTests, MapWriteUsageLarge) { constexpr uint64_t kDataSize = 1000 * 1000; std::vector myData; for (uint32_t i = 0; i < kDataSize; ++i) { myData.push_back(i); } wgpu::Buffer buffer = BufferMappedAtCreationWithData( wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData}); UnmapBuffer(buffer); EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffer, 0, kDataSize); } // Test mappedAtCreation for a large MapRead buffer TEST_P(BufferMappedAtCreationTests, MapReadUsageLarge) { constexpr uint64_t kDataSize = 1000 * 1000; std::vector myData; for (uint32_t i = 0; i < kDataSize; ++i) { myData.push_back(i); } wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::MapRead, myData); UnmapBuffer(buffer); const void* mappedData = MapAsyncAndWait(buffer, wgpu::MapMode::Read, kDataSize * sizeof(uint32_t)); ASSERT_EQ(0, memcmp(mappedData, myData.data(), kDataSize * sizeof(uint32_t))); UnmapBuffer(buffer); } // Test mappedAtCreation for a large non-mappable buffer TEST_P(BufferMappedAtCreationTests, NonMappableUsageLarge) { constexpr uint64_t kDataSize = 1000 * 1000; std::vector myData; for (uint32_t i = 0; i < kDataSize; ++i) { myData.push_back(i); } wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::CopySrc, {myData}); UnmapBuffer(buffer); EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffer, 0, kDataSize); } // Test destroying a non-mappable buffer mapped at creation. // This is a regression test for an issue where the D3D12 backend thought the buffer was actually // mapped and tried to unlock the heap residency (when actually the buffer was using a staging // buffer) TEST_P(BufferMappedAtCreationTests, DestroyNonMappableWhileMappedForCreation) { wgpu::Buffer buffer = BufferMappedAtCreation(wgpu::BufferUsage::CopySrc, 4); buffer.Destroy(); } // Test destroying a mappable buffer mapped at creation. TEST_P(BufferMappedAtCreationTests, DestroyMappableWhileMappedForCreation) { wgpu::Buffer buffer = BufferMappedAtCreation(wgpu::BufferUsage::MapRead, 4); buffer.Destroy(); } // Test that mapping a buffer is valid after mappedAtCreation and Unmap TEST_P(BufferMappedAtCreationTests, CreateThenMapSuccess) { static uint32_t myData = 230502; wgpu::Buffer buffer = BufferMappedAtCreationWithData( wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData}); UnmapBuffer(buffer); EXPECT_BUFFER_U32_EQ(myData, buffer, 0); bool done = false; buffer.MapAsync( wgpu::MapMode::Write, 0, 4, [](WGPUBufferMapAsyncStatus status, void* userdata) { ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status); *static_cast(userdata) = true; }, &done); while (!done) { WaitABit(); } UnmapBuffer(buffer); } // Test that is is invalid to map a buffer twice when using mappedAtCreation TEST_P(BufferMappedAtCreationTests, CreateThenMapBeforeUnmapFailure) { uint32_t myData = 230502; wgpu::Buffer buffer = BufferMappedAtCreationWithData( wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData}); ASSERT_DEVICE_ERROR([&]() { bool done = false; buffer.MapAsync( wgpu::MapMode::Write, 0, 4, [](WGPUBufferMapAsyncStatus status, void* userdata) { ASSERT_EQ(WGPUBufferMapAsyncStatus_Error, status); *static_cast(userdata) = true; }, &done); while (!done) { WaitABit(); } }()); // mappedAtCreation is unaffected by the MapWrite error. UnmapBuffer(buffer); } // Test that creating a zero-sized buffer mapped is allowed. TEST_P(BufferMappedAtCreationTests, ZeroSized) { wgpu::BufferDescriptor descriptor; descriptor.size = 0; descriptor.usage = wgpu::BufferUsage::Vertex; descriptor.mappedAtCreation = true; wgpu::Buffer buffer = device.CreateBuffer(&descriptor); ASSERT_NE(nullptr, buffer.GetMappedRange()); // Check that unmapping the buffer works too. UnmapBuffer(buffer); } // Test that creating a zero-sized mapppable buffer mapped. (it is a different code path) TEST_P(BufferMappedAtCreationTests, ZeroSizedMappableBuffer) { wgpu::BufferDescriptor descriptor; descriptor.size = 0; descriptor.usage = wgpu::BufferUsage::MapWrite; descriptor.mappedAtCreation = true; wgpu::Buffer buffer = device.CreateBuffer(&descriptor); ASSERT_NE(nullptr, buffer.GetMappedRange()); // Check that unmapping the buffer works too. UnmapBuffer(buffer); } // Test that creating a zero-sized error buffer mapped. (it is a different code path) TEST_P(BufferMappedAtCreationTests, ZeroSizedErrorBuffer) { DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation")); wgpu::BufferDescriptor descriptor; descriptor.size = 0; descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Storage; descriptor.mappedAtCreation = true; wgpu::Buffer buffer; ASSERT_DEVICE_ERROR(buffer = device.CreateBuffer(&descriptor)); ASSERT_NE(nullptr, buffer.GetMappedRange()); } // Test the result of GetMappedRange when mapped at creation. TEST_P(BufferMappedAtCreationTests, GetMappedRange) { wgpu::BufferDescriptor descriptor; descriptor.size = 4; descriptor.usage = wgpu::BufferUsage::CopyDst; descriptor.mappedAtCreation = true; wgpu::Buffer buffer = device.CreateBuffer(&descriptor); ASSERT_EQ(buffer.GetMappedRange(), buffer.GetConstMappedRange()); ASSERT_NE(buffer.GetMappedRange(), nullptr); buffer.Unmap(); } // Test the result of GetMappedRange when mapped at creation for a zero-sized buffer. TEST_P(BufferMappedAtCreationTests, GetMappedRangeZeroSized) { wgpu::BufferDescriptor descriptor; descriptor.size = 0; descriptor.usage = wgpu::BufferUsage::CopyDst; descriptor.mappedAtCreation = true; wgpu::Buffer buffer = device.CreateBuffer(&descriptor); ASSERT_EQ(buffer.GetMappedRange(), buffer.GetConstMappedRange()); ASSERT_NE(buffer.GetMappedRange(), nullptr); buffer.Unmap(); } DAWN_INSTANTIATE_TEST(BufferMappedAtCreationTests, D3D11Backend(), D3D12Backend(), D3D12Backend({}, {"use_d3d12_resource_heap_tier2"}), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()); class BufferTests : public DawnTest {}; // Test that creating a zero-buffer is allowed. TEST_P(BufferTests, ZeroSizedBuffer) { wgpu::BufferDescriptor desc; desc.size = 0; desc.usage = wgpu::BufferUsage::CopyDst; device.CreateBuffer(&desc); } // Test that creating a very large buffers fails gracefully. TEST_P(BufferTests, CreateBufferOOM) { // TODO(http://crbug.com/dawn/749): Missing support. DAWN_TEST_UNSUPPORTED_IF(IsOpenGL()); DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); DAWN_TEST_UNSUPPORTED_IF(IsAsan()); wgpu::BufferDescriptor descriptor; descriptor.usage = wgpu::BufferUsage::CopyDst; descriptor.size = std::numeric_limits::max(); 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; ASSERT_DEVICE_ERROR(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. TEST_P(BufferTests, BufferMappedAtCreationOOM) { // TODO(crbug.com/dawn/1506): new (std::nothrow) crashes on OOM on Mac ARM64 because libunwind // doesn't see the previous catchall try-catch. DAWN_SUPPRESS_TEST_IF(DAWN_PLATFORM_IS(MACOS) && DAWN_PLATFORM_IS(ARM64)); // TODO(http://crbug.com/dawn/749): Missing support. DAWN_TEST_UNSUPPORTED_IF(IsOpenGL()); DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); DAWN_TEST_UNSUPPORTED_IF(IsAsan()); // Test non-mappable buffer { wgpu::BufferDescriptor descriptor; descriptor.size = 4; descriptor.usage = wgpu::BufferUsage::CopyDst; descriptor.mappedAtCreation = true; // Control: test a small buffer works. device.CreateBuffer(&descriptor); // Test an enormous buffer fails descriptor.size = std::numeric_limits::max(); 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; if (UsesWire()) { wgpu::Buffer buffer = device.CreateBuffer(&descriptor); ASSERT_EQ(nullptr, buffer.Get()); } else { ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor)); } } // Test mappable buffer { wgpu::BufferDescriptor descriptor; descriptor.size = 4; descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite; descriptor.mappedAtCreation = true; // Control: test a small buffer works. device.CreateBuffer(&descriptor); // Test an enormous buffer fails descriptor.size = std::numeric_limits::max(); 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; if (UsesWire()) { wgpu::Buffer buffer = device.CreateBuffer(&descriptor); ASSERT_EQ(nullptr, buffer.Get()); } else { ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor)); } } } // Test that mapping an OOM buffer fails gracefully TEST_P(BufferTests, CreateBufferOOMMapAsync) { // TODO(http://crbug.com/dawn/749): Missing support. DAWN_TEST_UNSUPPORTED_IF(IsOpenGL()); DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); DAWN_TEST_UNSUPPORTED_IF(IsAsan()); auto RunTest = [this](const wgpu::BufferDescriptor& descriptor) { wgpu::Buffer buffer; ASSERT_DEVICE_ERROR(buffer = device.CreateBuffer(&descriptor)); bool done = false; ASSERT_DEVICE_ERROR(buffer.MapAsync( wgpu::MapMode::Write, 0, 4, [](WGPUBufferMapAsyncStatus status, void* userdata) { EXPECT_EQ(status, WGPUBufferMapAsyncStatus_Error); *static_cast(userdata) = true; }, &done)); while (!done) { WaitABit(); } }; wgpu::BufferDescriptor descriptor; descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite; // Test an enormous buffer descriptor.size = std::numeric_limits::max(); RunTest(descriptor); // UINT64_MAX may be special cased. Test a smaller, but really large buffer also fails descriptor.size = 1ull << 50; RunTest(descriptor); } DAWN_INSTANTIATE_TEST(BufferTests, D3D11Backend(), D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()); class BufferNoSuballocationTests : public DawnTest {}; // Regression test for crbug.com/1313172 // This tests a buffer. It then performs writeBuffer and immediately destroys // it. Though writeBuffer references a destroyed buffer, it should not crash. TEST_P(BufferNoSuballocationTests, WriteBufferThenDestroy) { uint32_t myData = 0x01020304; wgpu::BufferDescriptor desc; desc.size = 1024; desc.usage = wgpu::BufferUsage::CopyDst; wgpu::Buffer buffer = device.CreateBuffer(&desc); // Enqueue a pending write into the buffer. constexpr size_t kSize = sizeof(myData); queue.WriteBuffer(buffer, 0, &myData, kSize); // Destroy the buffer. buffer.Destroy(); // Flush and wait for all commands. queue.Submit(0, nullptr); WaitForAllOperations(); } DAWN_INSTANTIATE_TEST(BufferNoSuballocationTests, D3D11Backend({"disable_resource_suballocation"}), D3D12Backend({"disable_resource_suballocation"}), MetalBackend({"disable_resource_suballocation"}), OpenGLBackend({"disable_resource_suballocation"}), OpenGLESBackend({"disable_resource_suballocation"}), VulkanBackend({"disable_resource_suballocation"}));