| // Copyright 2019 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include <limits> | |
| #include <memory> | |
| #include "dawn/common/Assert.h" | |
| #include "dawn/tests/unittests/wire/WireFutureTest.h" | |
| #include "dawn/tests/unittests/wire/WireTest.h" | |
| #include "dawn/wire/WireClient.h" | |
| // Define a stream operator for WGPUMapMode outside namespace scope so that it can be found on | |
| // resolution for test name generation. | |
| // TODO(dawn:2205) Remove this in favor of custom serializer. | |
| std::ostream& operator<<(std::ostream& os, const WGPUMapMode& param) { | |
| switch (param) { | |
| case WGPUMapMode_Read: | |
| os << "Read"; | |
| break; | |
| case WGPUMapMode_Write: | |
| os << "Write"; | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| return os; | |
| } | |
| namespace dawn::wire { | |
| namespace { | |
| using testing::_; | |
| using testing::InvokeWithoutArgs; | |
| using testing::Return; | |
| // For the buffer tests, we make passing a map mode optional to reuse the same test fixture for | |
| // tests that test multiple modes and tests that are mode specific. By making it an optional, it | |
| // allows us to determine whether the map mode is necessary when generating the test names. | |
| using MapMode = std::optional<WGPUMapMode>; | |
| DAWN_WIRE_FUTURE_TEST_PARAM_STRUCT(WireBufferParam, MapMode); | |
| using WireBufferMappingTestBase = WireFutureTestWithParams<WGPUBufferMapCallback, | |
| WGPUBufferMapCallbackInfo, | |
| wgpuBufferMapAsync, | |
| wgpuBufferMapAsyncF, | |
| WireBufferParam>; | |
| // General mapping tests that either do not care about the specific mapping mode, or apply to both. | |
| class WireBufferMappingTests : public WireBufferMappingTestBase { | |
| protected: | |
| void SetUp() override { | |
| WireBufferMappingTestBase::SetUp(); | |
| apiBuffer = api.GetNewBuffer(); | |
| } | |
| // Overridden version of wgpuBufferMapAsync that defers to the API call based on the | |
| // test callback mode. | |
| void BufferMapAsync(WGPUBuffer b, | |
| WGPUMapMode mode, | |
| size_t offset, | |
| size_t size, | |
| void* userdata = nullptr) { | |
| CallImpl(userdata, b, mode, offset, size); | |
| } | |
| WGPUMapMode GetMapMode() { | |
| DAWN_ASSERT(GetParam().mMapMode); | |
| return *GetParam().mMapMode; | |
| } | |
| void SetupBuffer(WGPUMapMode mapMode) { | |
| WGPUBufferUsageFlags usage = WGPUBufferUsage_MapRead; | |
| if (mapMode == WGPUMapMode_Read) { | |
| usage = WGPUBufferUsage_MapRead; | |
| } else if (mapMode == WGPUMapMode_Write) { | |
| usage = WGPUBufferUsage_MapWrite; | |
| } | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.size = kBufferSize; | |
| descriptor.usage = usage; | |
| buffer = wgpuDeviceCreateBuffer(device, &descriptor); | |
| EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)) | |
| .WillOnce(Return(apiBuffer)) | |
| .RetiresOnSaturation(); | |
| FlushClient(); | |
| } | |
| // Sets up the correct mapped range call expectations given the map mode. | |
| void ExpectMappedRangeCall(uint64_t bufferSize, void* bufferContent) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| if (mapMode == WGPUMapMode_Read) { | |
| EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, bufferSize)) | |
| .WillOnce(Return(bufferContent)); | |
| } else if (mapMode == WGPUMapMode_Write) { | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, bufferSize)) | |
| .WillOnce(Return(bufferContent)); | |
| } | |
| } | |
| // Test to exercise client functions that should override server response for callbacks. | |
| template <typename ExpFn> | |
| void TestEarlyMapCancelled(void (*cancelFn)(WGPUBuffer), | |
| ExpFn cancelExp, | |
| WGPUBufferMapAsyncStatus expected) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 31337; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); | |
| })); | |
| ExpectMappedRangeCall(kBufferSize, &bufferContent); | |
| cancelExp(); | |
| // The callback should get called with the expected status, regardless if the server has | |
| // responded. | |
| if (IsSpontaneous()) { | |
| // In spontaneous mode, the callback gets called as a part of the cancel function. | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(expected, _)).Times(1); | |
| cancelFn(buffer); | |
| }); | |
| FlushClient(); | |
| FlushCallbacks(); | |
| } else { | |
| // Otherwise, the callback will fire when we flush them. | |
| cancelFn(buffer); | |
| FlushClient(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(expected, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| } | |
| } | |
| // Test to exercise client functions that should override server error response for callbacks. | |
| template <typename ExpFn> | |
| void TestEarlyMapErrorCancelled(void (*cancelFn)(WGPUBuffer), | |
| ExpFn cancelExp, | |
| WGPUBufferMapAsyncStatus expected) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError); | |
| })); | |
| // Ensure that the server had a chance to respond if relevant. | |
| FlushClient(); | |
| FlushFutures(); | |
| cancelExp(); | |
| // The callback should get called with the expected status status, not server-side error, | |
| // even if the request fails on the server side. | |
| if (IsSpontaneous()) { | |
| // In spontaneous mode, the callback gets called as a part of the cancel function. | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(expected, _)).Times(1); | |
| cancelFn(buffer); | |
| }); | |
| FlushClient(); | |
| FlushCallbacks(); | |
| } else { | |
| // Otherwise, the callback will fire when we flush them. | |
| cancelFn(buffer); | |
| FlushClient(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(expected, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| } | |
| } | |
| // Test to exercise client functions that would cancel callbacks don't cause the callback to be | |
| // fired twice. | |
| template <typename ExpFn> | |
| void TestCancelInCallback(void (*cancelFn)(WGPUBuffer), ExpFn cancelExp) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 31337; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); | |
| })); | |
| ExpectMappedRangeCall(kBufferSize, &bufferContent); | |
| // Ensure that the server had a chance to respond if relevant. | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() { | |
| cancelFn(buffer); | |
| }); | |
| FlushCallbacks(); | |
| }); | |
| // Make sure that the cancel function is called and flush more callbacks to ensure that | |
| // nothing else happens. | |
| cancelExp(); | |
| FlushClient(); | |
| FlushFutures(); | |
| FlushCallbacks(); | |
| } | |
| static constexpr uint64_t kBufferSize = sizeof(uint32_t); | |
| // A successfully created buffer | |
| WGPUBuffer buffer; | |
| WGPUBuffer apiBuffer; | |
| }; | |
| DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappingTests, {WGPUMapMode_Read, WGPUMapMode_Write}); | |
| // Check that things work correctly when a validation error happens when mapping the buffer. | |
| TEST_P(WireBufferMappingTests, ErrorWhileMapping) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError); | |
| })); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| EXPECT_EQ(nullptr, wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)); | |
| } | |
| // Check the map callback is called with "UnmappedBeforeCallback" when the map request would have | |
| // worked, but Unmap() was called. | |
| TEST_P(WireBufferMappingTests, UnmapCalledTooEarly) { | |
| TestEarlyMapCancelled( | |
| &wgpuBufferUnmap, [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); }, | |
| WGPUBufferMapAsyncStatus_UnmappedBeforeCallback); | |
| } | |
| // Check that if Unmap() was called early client-side, we disregard server-side validation errors. | |
| TEST_P(WireBufferMappingTests, UnmapCalledTooEarlyServerSideError) { | |
| TestEarlyMapErrorCancelled( | |
| &wgpuBufferUnmap, [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); }, | |
| WGPUBufferMapAsyncStatus_UnmappedBeforeCallback); | |
| } | |
| // Check the map callback is called with "DestroyedBeforeCallback" when the map request would have | |
| // worked, but Destroy() was called. | |
| TEST_P(WireBufferMappingTests, DestroyCalledTooEarly) { | |
| TestEarlyMapCancelled( | |
| &wgpuBufferDestroy, [&]() { EXPECT_CALL(api, BufferDestroy(apiBuffer)); }, | |
| WGPUBufferMapAsyncStatus_DestroyedBeforeCallback); | |
| } | |
| // Check that if Destroy() was called early client-side, we disregard server-side validation errors. | |
| TEST_P(WireBufferMappingTests, DestroyCalledTooEarlyServerSideError) { | |
| TestEarlyMapErrorCancelled( | |
| &wgpuBufferDestroy, [&]() { EXPECT_CALL(api, BufferDestroy(apiBuffer)); }, | |
| WGPUBufferMapAsyncStatus_DestroyedBeforeCallback); | |
| } | |
| // Test that the callback isn't fired twice when Unmap() is called inside the callback. | |
| TEST_P(WireBufferMappingTests, UnmapInsideMapCallback) { | |
| TestCancelInCallback(&wgpuBufferUnmap, [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); }); | |
| } | |
| // Test that the callback isn't fired twice when Destroy() is called inside the callback. | |
| TEST_P(WireBufferMappingTests, DestroyInsideMapCallback) { | |
| TestCancelInCallback(&wgpuBufferDestroy, [&]() { EXPECT_CALL(api, BufferDestroy(apiBuffer)); }); | |
| } | |
| // Test that the callback isn't fired twice when Release() is called inside the callback with the | |
| // last ref. | |
| TEST_P(WireBufferMappingTests, ReleaseInsideMapCallback) { | |
| // TODO(dawn:1621): Suppressed because the mapping handling still touches the buffer after it is | |
| // destroyed triggering an ASAN error when in MapWrite mode. | |
| DAWN_SKIP_TEST_IF(GetMapMode() == WGPUMapMode_Write); | |
| TestCancelInCallback(&wgpuBufferRelease, [&]() { EXPECT_CALL(api, BufferRelease(apiBuffer)); }); | |
| } | |
| // Tests specific to mapping for reading. | |
| class WireBufferMappingReadTests : public WireBufferMappingTests { | |
| protected: | |
| void SetUp() override { | |
| WireBufferMappingTests::SetUp(); | |
| SetupBuffer(WGPUMapMode_Read); | |
| } | |
| }; | |
| DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappingReadTests); | |
| // Check mapping for reading a succesfully created buffer. | |
| TEST_P(WireBufferMappingReadTests, MappingSuccess) { | |
| BufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 31337; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&bufferContent)); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| EXPECT_EQ(bufferContent, | |
| *static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize))); | |
| wgpuBufferUnmap(buffer); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Check that an error map read while a buffer is already mapped won't changed the result of get | |
| // mapped range. | |
| TEST_P(WireBufferMappingReadTests, MappingErrorWhileAlreadyMapped) { | |
| // Successful map | |
| BufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 31337; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&bufferContent)); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| // Map failure while the buffer is already mapped | |
| BufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, nullptr); | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError); | |
| })); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| EXPECT_EQ(bufferContent, | |
| *static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize))); | |
| } | |
| // Tests specific to mapping for writing. | |
| class WireBufferMappingWriteTests : public WireBufferMappingTests { | |
| protected: | |
| void SetUp() override { | |
| WireBufferMappingTests::SetUp(); | |
| SetupBuffer(WGPUMapMode_Write); | |
| } | |
| }; | |
| DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappingWriteTests); | |
| // Check mapping for writing a succesfully created buffer. | |
| TEST_P(WireBufferMappingWriteTests, MappingSuccess) { | |
| BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr); | |
| uint32_t serverBufferContent = 31337; | |
| uint32_t updatedContent = 4242; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&serverBufferContent)); | |
| // The map write callback always gets a buffer full of zeroes. | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| uint32_t* lastMapWritePointer = | |
| static_cast<uint32_t*>(wgpuBufferGetMappedRange(buffer, 0, kBufferSize)); | |
| ASSERT_EQ(0u, *lastMapWritePointer); | |
| // Write something to the mapped pointer | |
| *lastMapWritePointer = updatedContent; | |
| wgpuBufferUnmap(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); | |
| } | |
| // Check that an error map write while a buffer is already mapped. | |
| TEST_P(WireBufferMappingWriteTests, MappingErrorWhileAlreadyMapped) { | |
| // Successful map | |
| BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 31337; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&bufferContent)); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| // Map failure while the buffer is already mapped | |
| BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr); | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError); | |
| })); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| EXPECT_NE(nullptr, | |
| static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize))); | |
| } | |
| // Tests specific to mapped at creation. | |
| class WireBufferMappedAtCreationTests : public WireBufferMappingTests {}; | |
| DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireBufferMappedAtCreationTests); | |
| // Test successful buffer creation with mappedAtCreation=true | |
| TEST_F(WireBufferMappedAtCreationTests, Success) { | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.size = 4; | |
| descriptor.mappedAtCreation = true; | |
| WGPUBuffer apiBuffer = api.GetNewBuffer(); | |
| uint32_t apiBufferData = 1234; | |
| WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor); | |
| EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer)); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData)); | |
| FlushClient(); | |
| wgpuBufferUnmap(buffer); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test that releasing a buffer mapped at creation does not call Unmap | |
| TEST_F(WireBufferMappedAtCreationTests, ReleaseBeforeUnmap) { | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.size = 4; | |
| descriptor.mappedAtCreation = true; | |
| WGPUBuffer apiBuffer = api.GetNewBuffer(); | |
| uint32_t apiBufferData = 1234; | |
| WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor); | |
| EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer)); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData)); | |
| FlushClient(); | |
| wgpuBufferRelease(buffer); | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test that it is valid to map a buffer after it is mapped at creation and unmapped. | |
| TEST_P(WireBufferMappedAtCreationTests, MapSuccess) { | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.size = 4; | |
| descriptor.usage = WGPUMapMode_Write; | |
| descriptor.mappedAtCreation = true; | |
| WGPUBuffer apiBuffer = api.GetNewBuffer(); | |
| uint32_t apiBufferData = 1234; | |
| WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor); | |
| EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer)); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData)); | |
| FlushClient(); | |
| wgpuBufferUnmap(buffer); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr); | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&apiBufferData)); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| } | |
| // Test that it is invalid to map a buffer after mappedAtCreation but before Unmap | |
| TEST_P(WireBufferMappedAtCreationTests, MapFailure) { | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.size = 4; | |
| descriptor.mappedAtCreation = true; | |
| WGPUBuffer apiBuffer = api.GetNewBuffer(); | |
| uint32_t apiBufferData = 1234; | |
| WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor); | |
| EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer)); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData)); | |
| FlushClient(); | |
| BufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, nullptr); | |
| // Note that the validation logic is entirely on the native side so we inject the validation | |
| // error here and flush the server response to mock the expected behavior. | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError); | |
| })); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| EXPECT_NE(nullptr, | |
| static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize))); | |
| wgpuBufferUnmap(buffer); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // 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(); | |
| // Check for CreateBufferMapped. | |
| { | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.usage = WGPUBufferUsage_CopySrc; | |
| descriptor.size = kOOMSize; | |
| descriptor.mappedAtCreation = true; | |
| wgpuDeviceCreateBuffer(device, &descriptor); | |
| FlushClient(); | |
| } | |
| // Check for MapRead usage. | |
| { | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.usage = WGPUBufferUsage_MapRead; | |
| descriptor.size = kOOMSize; | |
| wgpuDeviceCreateBuffer(device, &descriptor); | |
| EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice, _)).WillOnce(Return(apiBuffer)); | |
| FlushClient(); | |
| } | |
| // Check for MapWrite usage. | |
| { | |
| WGPUBufferDescriptor descriptor = {}; | |
| descriptor.usage = WGPUBufferUsage_MapWrite; | |
| descriptor.size = kOOMSize; | |
| wgpuDeviceCreateBuffer(device, &descriptor); | |
| EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice, _)).WillOnce(Return(apiBuffer)); | |
| FlushClient(); | |
| } | |
| } | |
| // Test that registering a callback then wire disconnect calls the callback with | |
| // DeviceLost. | |
| TEST_P(WireBufferMappingTests, MapThenDisconnect) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 0; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| ExpectMappedRangeCall(kBufferSize, &bufferContent); | |
| FlushClient(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DeviceLost, _)).Times(1); | |
| GetWireClient()->Disconnect(); | |
| }); | |
| } | |
| // Test that registering a callback after wire disconnect calls the callback with | |
| // DeviceLost. | |
| TEST_P(WireBufferMappingTests, MapAfterDisconnect) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| GetWireClient()->Disconnect(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DeviceLost, _)).Times(1); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| }); | |
| } | |
| // Test that mapping again while pending map cause an error on the callback. | |
| TEST_P(WireBufferMappingTests, PendingMapImmediateError) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| // Calls for the first successful map. | |
| uint32_t bufferContent = 0; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| ExpectMappedRangeCall(kBufferSize, &bufferContent); | |
| if (IsSpontaneous()) { | |
| // In spontaneous mode, the second map on the pending immediately calls the callback. | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _)).Times(1); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| }); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| } else { | |
| // Otherwise, the callback will fire alongside the success one when we flush the callbacks. | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| FlushClient(); | |
| FlushFutures(); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _)).Times(1); | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| } | |
| } | |
| // Test that GetMapState() returns map state as expected | |
| TEST_P(WireBufferMappingTests, GetMapState) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| uint32_t bufferContent = 31337; | |
| // Server-side success case | |
| { | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); | |
| })); | |
| ExpectMappedRangeCall(kBufferSize, &bufferContent); | |
| // Map state should initially be unmapped. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| // Map state should become pending immediately after map async call. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending); | |
| FlushClient(); | |
| // Map state should be pending until receiving a response from server. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending); | |
| FlushFutures(); | |
| // Map state should still be pending until the callback has been called. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() { | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Mapped); | |
| }); | |
| FlushCallbacks(); | |
| }); | |
| // Mapping succeeded. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Mapped); | |
| } | |
| wgpuBufferUnmap(buffer); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| // Server-side error case | |
| { | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_ValidationError); | |
| })); | |
| // Map state should initially be unmapped. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| // Map state should become pending immediately after map async call. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending); | |
| FlushClient(); | |
| // Map state should be pending until receiving a response from server. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending); | |
| FlushFutures(); | |
| // Map state should still be pending until the callback has been called. | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Pending); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_ValidationError, _)).WillOnce([&]() { | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped); | |
| }); | |
| FlushCallbacks(); | |
| }); | |
| // mapping failed | |
| ASSERT_EQ(wgpuBufferGetMapState(buffer), WGPUBufferMapState_Unmapped); | |
| } | |
| } | |
| // Test that requests inside user callbacks before disconnect are called. | |
| TEST_P(WireBufferMappingTests, MapInsideCallbackBeforeDisconnect) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 0; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| ExpectMappedRangeCall(kBufferSize, &bufferContent); | |
| FlushClient(); | |
| static constexpr size_t kNumRequests = 10; | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DeviceLost, _)) | |
| .Times(kNumRequests + 1) | |
| .WillOnce([&]() { | |
| for (size_t i = 0; i < kNumRequests; i++) { | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| } | |
| }) | |
| .WillRepeatedly(Return()); | |
| GetWireClient()->Disconnect(); | |
| }); | |
| } | |
| // Test that requests inside user callbacks before buffer destroy are called. | |
| TEST_P(WireBufferMappingTests, MapInsideCallbackBeforeDestroy) { | |
| WGPUMapMode mapMode = GetMapMode(); | |
| SetupBuffer(mapMode); | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| uint32_t bufferContent = 0; | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, mapMode, 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs( | |
| [&] { api.CallBufferMapAsyncCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success); })); | |
| ExpectMappedRangeCall(kBufferSize, &bufferContent); | |
| FlushClient(); | |
| FlushFutures(); | |
| static constexpr size_t kNumRequests = 10; | |
| if (IsSpontaneous()) { | |
| // In spontaneous mode, when the success callback fires, the first MapAsync request | |
| // generated by the callback is queued, then all subsequent requests' callbacks are | |
| // immediately called with MappingAlreadyPending. Finally, when we call Destroy, the queued | |
| // request's callback is then called with DestroyedBeforeCallback. | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() { | |
| for (size_t i = 0; i < kNumRequests; i++) { | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| } | |
| }); | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _)) | |
| .Times(kNumRequests - 1); | |
| FlushCallbacks(); | |
| }); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _)).Times(1); | |
| wgpuBufferDestroy(buffer); | |
| }); | |
| FlushCallbacks(); | |
| } else { | |
| // In non-spontaneous modes, the first callback doesn't trigger any other immediate | |
| // callbacks, but internally, all but the first MapAsync call's callback is set to be ready | |
| // with MappingAlreadyPending. When we call Destroy, the first pending request is then | |
| // marked ready with DestroyedBeforeCallback. The callbacks all run when we flush them. | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_Success, _)).WillOnce([&]() { | |
| for (size_t i = 0; i < kNumRequests; i++) { | |
| BufferMapAsync(buffer, mapMode, 0, kBufferSize, nullptr); | |
| } | |
| }); | |
| FlushCallbacks(); | |
| }); | |
| wgpuBufferDestroy(buffer); | |
| ExpectWireCallbacksWhen([&](auto& mockCb) { | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_MappingAlreadyPending, _)) | |
| .Times(kNumRequests - 1); | |
| EXPECT_CALL(mockCb, Call(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, _)).Times(1); | |
| FlushCallbacks(); | |
| }); | |
| } | |
| } | |
| } // anonymous namespace | |
| } // namespace dawn::wire |