| // 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 <memory> | |
| #include <tuple> | |
| #include <utility> | |
| #include "dawn/common/StringViewUtils.h" | |
| #include "dawn/tests/MockCallback.h" | |
| #include "dawn/tests/ParamGenerator.h" | |
| #include "dawn/tests/StringViewMatchers.h" | |
| #include "dawn/tests/unittests/wire/WireTest.h" | |
| #include "dawn/wire/client/ClientMemoryTransferService_mock.h" | |
| #include "dawn/wire/server/ServerMemoryTransferService_mock.h" | |
| namespace wgpu { | |
| // Define a stream operator for wgpu::MapMode so that it can be found on resolution for test name | |
| // generation. | |
| // TODO(dawn:2205) Remove this in favor of custom serializer. | |
| static std::ostream& operator<<(std::ostream& os, const MapMode& param) { | |
| switch (param) { | |
| case wgpu::MapMode::Read: | |
| os << "Read"; | |
| break; | |
| case wgpu::MapMode::Write: | |
| os << "Write"; | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| return os; | |
| } | |
| } // namespace wgpu | |
| namespace dawn::wire { | |
| namespace { | |
| using testing::_; | |
| using testing::InvokeWithoutArgs; | |
| using testing::MockCppCallback; | |
| using testing::Ne; | |
| using testing::NotNull; | |
| using testing::Return; | |
| using testing::SizedString; | |
| using testing::StrictMock; | |
| using testing::WithArg; | |
| using MapMode = wgpu::MapMode; | |
| using MappedAtCreation = bool; | |
| DAWN_TEST_PARAM_STRUCT_TYPES(MapModeParam, MapMode, MappedAtCreation); | |
| MATCHER_P(AsUint32Eq, value, "") { | |
| return *reinterpret_cast<const uint32_t*>(arg) == value; | |
| } | |
| using MockClientReadHandle = client::MockMemoryTransferService::MockReadHandle; | |
| using MockClientWriteHandle = client::MockMemoryTransferService::MockWriteHandle; | |
| using MockServerReadHandle = server::MockMemoryTransferService::MockReadHandle; | |
| using MockServerWriteHandle = server::MockMemoryTransferService::MockWriteHandle; | |
| using MockClientHandles = std::tuple<MockClientReadHandle*, MockClientWriteHandle*>; | |
| using MockServerHandles = std::tuple<MockServerReadHandle*, MockServerWriteHandle*>; | |
| // WireMemoryTransferServiceTests test the MemoryTransferService with buffer mapping. | |
| // They test the basic success and error cases for buffer mapping, and they test | |
| // mocked failures of each fallible MemoryTransferService method that an embedder | |
| // could implement. | |
| // The test harness defines multiple helpers for expecting operations on Read/Write handles | |
| // and for mocking failures. | |
| // There are tests which check for Success for every mapping operation which mock an entire | |
| // mapping operation from map to unmap, and add all MemoryTransferService expectations. Tests | |
| // which check for errors perform the same mapping operations but insert mocked failures for | |
| // various mapping or MemoryTransferService operations. | |
| class WireMemoryTransferServiceTestBase : public WireTest, | |
| public testing::WithParamInterface<MapModeParam> { | |
| protected: | |
| client::MemoryTransferService* GetClientMemoryTransferService() override { return &mClientMTS; } | |
| server::MemoryTransferService* GetServerMemoryTransferService() override { return &mServerMTS; } | |
| wgpu::BufferUsage GetUsage() { | |
| switch (GetParam().mMapMode) { | |
| case wgpu::MapMode::Read: | |
| return wgpu::BufferUsage::MapRead; | |
| case wgpu::MapMode::Write: | |
| return wgpu::BufferUsage::MapWrite; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| std::pair<WGPUBuffer, wgpu::Buffer> CreateBuffer() { | |
| wgpu::BufferUsage usage = GetUsage(); | |
| bool mappedAtCreation = GetParam().mMappedAtCreation; | |
| wgpu::BufferDescriptor descriptor = {}; | |
| descriptor.size = kBufferSize; | |
| descriptor.mappedAtCreation = mappedAtCreation; | |
| descriptor.usage = usage; | |
| WGPUBuffer apiBuffer = api.GetNewBuffer(); | |
| wgpu::Buffer buffer = device.CreateBuffer(&descriptor); | |
| EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer)); | |
| return std::make_pair(apiBuffer, buffer); | |
| } | |
| std::tuple<WGPUBuffer, wgpu::Buffer, MockClientHandles, MockServerHandles> CreateValidBuffer() { | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| // The client should create and serialize the appropriate handles on buffer creation. | |
| auto clientHandles = ExpectHandleCreation(true); | |
| ExpectHandleSerialization(clientHandles); | |
| std::tie(apiBuffer, buffer) = CreateBuffer(); | |
| // When the commands are flushed, the server should appropriately deserialize the handles. | |
| auto serverHandles = ExpectHandleDeserialization(true); | |
| if (GetParam().mMappedAtCreation) { | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&mServerBufferContent)); | |
| } | |
| FlushClient(); | |
| return std::make_tuple(apiBuffer, buffer, clientHandles, serverHandles); | |
| } | |
| MockClientHandles ExpectHandleCreation(bool success) { | |
| wgpu::MapMode mode = GetParam().mMapMode; | |
| bool mappedAtCreation = GetParam().mMappedAtCreation; | |
| switch (mode) { | |
| case wgpu::MapMode::Read: { | |
| auto* readHandle = success ? new StrictMock<MockClientReadHandle>() : nullptr; | |
| EXPECT_CALL(mClientMTS, CreateReadHandle(kBufferSize)).WillOnce(Return(readHandle)); | |
| if (!success) { | |
| return std::make_tuple(nullptr, nullptr); | |
| } | |
| if (mappedAtCreation) { | |
| auto* writeHandle = new StrictMock<MockClientWriteHandle>(); | |
| EXPECT_CALL(mClientMTS, CreateWriteHandle(kBufferSize)) | |
| .WillOnce(Return(writeHandle)); | |
| EXPECT_CALL(*writeHandle, GetData).WillOnce(Return(&mClientBufferContent)); | |
| return std::make_tuple(readHandle, writeHandle); | |
| } | |
| return std::make_tuple(readHandle, nullptr); | |
| } | |
| case wgpu::MapMode::Write: { | |
| auto* writeHandle = success ? new StrictMock<MockClientWriteHandle>() : nullptr; | |
| EXPECT_CALL(mClientMTS, CreateWriteHandle(kBufferSize)) | |
| .WillOnce(Return(writeHandle)); | |
| if (!success) { | |
| return std::make_tuple(nullptr, nullptr); | |
| } | |
| if (mappedAtCreation) { | |
| EXPECT_CALL(*writeHandle, GetData).WillOnce(Return(&mClientBufferContent)); | |
| } | |
| return std::make_tuple(nullptr, writeHandle); | |
| } | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| void ExpectHandleSerialization(MockClientHandles& clientHandles) { | |
| auto* readHandle = std::get<MockClientReadHandle*>(clientHandles); | |
| if (readHandle) { | |
| EXPECT_CALL(*readHandle, SerializeCreateSize).WillOnce(InvokeWithoutArgs([&] { | |
| return sizeof(mSerializeCreateInfo); | |
| })); | |
| EXPECT_CALL(*readHandle, SerializeCreate(_)) | |
| .WillOnce(WithArg<0>([&](void* serializePointer) { | |
| memcpy(serializePointer, &mSerializeCreateInfo, kDataSize); | |
| return kDataSize; | |
| })); | |
| } | |
| auto* writeHandle = std::get<MockClientWriteHandle*>(clientHandles); | |
| if (writeHandle) { | |
| EXPECT_CALL(*writeHandle, SerializeCreateSize).WillOnce(InvokeWithoutArgs([&] { | |
| return sizeof(mSerializeCreateInfo); | |
| })); | |
| EXPECT_CALL(*writeHandle, SerializeCreate(_)) | |
| .WillOnce(WithArg<0>([&](void* serializePointer) { | |
| memcpy(serializePointer, &mSerializeCreateInfo, kDataSize); | |
| return kDataSize; | |
| })); | |
| } | |
| } | |
| MockServerHandles ExpectHandleDeserialization(bool success) { | |
| wgpu::MapMode mode = GetParam().mMapMode; | |
| bool mappedAtCreation = GetParam().mMappedAtCreation; | |
| switch (mode) { | |
| case wgpu::MapMode::Read: { | |
| MockServerWriteHandle* writeHandle = nullptr; | |
| if (mappedAtCreation) { | |
| writeHandle = success ? new StrictMock<MockServerWriteHandle>() : nullptr; | |
| EXPECT_CALL(mServerMTS, DeserializeWriteHandle(AsUint32Eq(mSerializeCreateInfo), | |
| kDataSize, _)) | |
| .WillOnce( | |
| WithArg<2>([=](server::MemoryTransferService::WriteHandle** handle) { | |
| *handle = writeHandle; | |
| return success; | |
| })); | |
| if (!success) { | |
| return std::make_tuple(nullptr, nullptr); | |
| } | |
| } | |
| auto* readHandle = success ? new StrictMock<MockServerReadHandle>() : nullptr; | |
| EXPECT_CALL(mServerMTS, | |
| DeserializeReadHandle(AsUint32Eq(mSerializeCreateInfo), kDataSize, _)) | |
| .WillOnce(WithArg<2>([=](server::MemoryTransferService::ReadHandle** handle) { | |
| *handle = readHandle; | |
| return success; | |
| })); | |
| return std::make_tuple(readHandle, writeHandle); | |
| } | |
| case wgpu::MapMode::Write: { | |
| auto* writeHandle = success ? new StrictMock<MockServerWriteHandle>() : nullptr; | |
| EXPECT_CALL(mServerMTS, | |
| DeserializeWriteHandle(AsUint32Eq(mSerializeCreateInfo), kDataSize, _)) | |
| .WillOnce(WithArg<2>([=](server::MemoryTransferService::WriteHandle** handle) { | |
| *handle = writeHandle; | |
| return success; | |
| })); | |
| return std::make_tuple(nullptr, writeHandle); | |
| } | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| void ExpectClientSerializeData(MockClientHandles& clientHandles) { | |
| auto* clientHandle = std::get<MockClientWriteHandle*>(clientHandles); | |
| if (!clientHandle) { | |
| return; | |
| } | |
| EXPECT_CALL(*clientHandle, SizeOfSerializeDataUpdate(_, _)).WillOnce(Return(kDataSize)); | |
| EXPECT_CALL(*clientHandle, SerializeDataUpdate) | |
| .WillOnce(WithArg<0>([&](void* serializePointer) { | |
| memcpy(serializePointer, &mClientBufferContent, kBufferSize); | |
| })); | |
| } | |
| void ExpectServerSerializeData(MockServerHandles& serverHandles) { | |
| auto* serverHandle = std::get<MockServerReadHandle*>(serverHandles); | |
| if (!serverHandle) { | |
| return; | |
| } | |
| EXPECT_CALL(*serverHandle, SizeOfSerializeDataUpdate(_, _)).WillOnce(Return(kDataSize)); | |
| EXPECT_CALL(*serverHandle, SerializeDataUpdate) | |
| .WillOnce(WithArg<3>([&](void* serializePointer) { | |
| memcpy(serializePointer, &mServerBufferContent, kBufferSize); | |
| return kBufferSize; | |
| })); | |
| } | |
| void ExpectClientDeserializeData(bool success, MockClientHandles& clientHandles) { | |
| auto* clientHandle = std::get<MockClientReadHandle*>(clientHandles); | |
| if (!clientHandle) { | |
| return; | |
| } | |
| EXPECT_CALL(*clientHandle, DeserializeDataUpdate(_, kBufferSize, 0, kBufferSize)) | |
| .WillOnce(WithArg<0>([&, success](const void* deserializePointer) { | |
| if (success) { | |
| // Copy the data manually here. | |
| memcpy(&mClientBufferContent, deserializePointer, kBufferSize); | |
| } | |
| return success; | |
| })); | |
| } | |
| void ExpectServerDeserializeData(bool success, MockServerHandles& serverHandles) { | |
| auto* serverHandle = std::get<MockServerWriteHandle*>(serverHandles); | |
| if (!serverHandle) { | |
| return; | |
| } | |
| EXPECT_CALL(*serverHandle, DeserializeDataUpdate(_, kBufferSize, 0, kBufferSize)) | |
| .WillOnce(WithArg<0>([&, success](const void* deserializePointer) { | |
| if (success) { | |
| // Copy the data manually here. | |
| memcpy(&mServerBufferContent, deserializePointer, kBufferSize); | |
| } | |
| return success; | |
| })); | |
| } | |
| void ExpectClientHandleDestruction(MockClientHandles& clientHandles) { | |
| auto* readHandle = std::get<MockClientReadHandle*>(clientHandles); | |
| if (readHandle) { | |
| EXPECT_CALL(*readHandle, Destroy).Times(1); | |
| } | |
| auto* writeHandle = std::get<MockClientWriteHandle*>(clientHandles); | |
| if (writeHandle) { | |
| EXPECT_CALL(*writeHandle, Destroy).Times(1); | |
| } | |
| } | |
| void ExpectServerHandleDestruction(MockServerHandles& serverHandles) { | |
| auto* readHandle = std::get<MockServerReadHandle*>(serverHandles); | |
| if (readHandle) { | |
| EXPECT_CALL(*readHandle, Destroy).Times(1); | |
| } | |
| auto* writeHandle = std::get<MockServerWriteHandle*>(serverHandles); | |
| if (writeHandle) { | |
| EXPECT_CALL(*writeHandle, Destroy).Times(1); | |
| } | |
| } | |
| // Sets expectations for a successful map async call and verifies that the results match for the | |
| // MapMode. | |
| void ExpectSuccessfulMapAsync(WGPUBuffer apiBuffer, | |
| wgpu::Buffer buffer, | |
| MockClientHandles& clientHandles, | |
| MockServerHandles& serverHandles) { | |
| wgpu::MapMode mode = GetParam().mMapMode; | |
| // Mode independent expectations. | |
| EXPECT_CALL(api, | |
| OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mode), 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success, | |
| kEmptyOutputStringView); | |
| })); | |
| EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Success, _)).Times(1); | |
| switch (mode) { | |
| case wgpu::MapMode::Read: { | |
| auto* clientHandle = std::get<MockClientReadHandle*>(clientHandles); | |
| ASSERT_THAT(clientHandle, NotNull()); | |
| EXPECT_CALL(*clientHandle, GetData).WillOnce(Return(&mClientBufferContent)); | |
| EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&mServerBufferContent)); | |
| buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous, | |
| mMapAsyncCb.Callback()); | |
| // The server should serialize its buffer when the client flushes. | |
| ExpectServerSerializeData(serverHandles); | |
| FlushClient(); | |
| // The client should deserialize into its buffer when the server flushes. | |
| ExpectClientDeserializeData(true, clientHandles); | |
| FlushServer(); | |
| // The data between the server and the client should be the same now. | |
| EXPECT_EQ(mServerBufferContent, mClientBufferContent); | |
| break; | |
| } | |
| case wgpu::MapMode::Write: { | |
| auto* clientHandle = std::get<MockClientWriteHandle*>(clientHandles); | |
| ASSERT_THAT(clientHandle, NotNull()); | |
| EXPECT_CALL(*clientHandle, GetData).WillOnce(Return(&mClientBufferContent)); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&mClientBufferContent)); | |
| buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous, | |
| mMapAsyncCb.Callback()); | |
| FlushClient(); | |
| FlushServer(); | |
| break; | |
| } | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| // Note that we pass in the mode explicitly here to allow reuse for mappedAtCreation. | |
| void ExpectSuccessfulUnmap(wgpu::MapMode mode, | |
| WGPUBuffer apiBuffer, | |
| wgpu::Buffer buffer, | |
| MockClientHandles& clientHandles, | |
| MockServerHandles& serverHandles) { | |
| switch (mode) { | |
| case wgpu::MapMode::Read: { | |
| // Unmap the buffer. | |
| buffer.Unmap(); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| break; | |
| } | |
| case wgpu::MapMode::Write: { | |
| // The client should serialize its buffer when Unmap is called. | |
| ExpectClientSerializeData(clientHandles); | |
| buffer.Unmap(); | |
| // The server should deserialize into its buffer when the client flushes. | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| ExpectServerDeserializeData(true, serverHandles); | |
| FlushClient(); | |
| // The data between the server and the client should be the same now. | |
| EXPECT_EQ(mServerBufferContent, mClientBufferContent); | |
| break; | |
| } | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| void ExpectSuccessfulUnmapAtCreation(WGPUBuffer apiBuffer, | |
| wgpu::Buffer buffer, | |
| MockClientHandles& clientHandles, | |
| MockServerHandles& serverHandles) { | |
| ASSERT_TRUE(GetParam().mMappedAtCreation); | |
| // Ensure that the contents on the client and server side are different now as a part of the | |
| // test. | |
| mClientBufferContent = kDataGenerator++; | |
| mServerBufferContent = kDataGenerator++; | |
| ASSERT_THAT(mClientBufferContent, Ne(mServerBufferContent)); | |
| // If the map mode of the buffer was actually MapRead, we should expect destruction of the | |
| // WriteHandles, and setting the ReadHandles instead. | |
| bool isRead = GetParam().mMapMode == wgpu::MapMode::Read; | |
| if (isRead) { | |
| auto* clientWriteHandle = std::get<MockClientWriteHandle*>(clientHandles); | |
| ASSERT_THAT(clientWriteHandle, NotNull()); | |
| EXPECT_CALL(*clientWriteHandle, Destroy).Times(1); | |
| auto* clientReadHandle = std::get<MockClientReadHandle*>(clientHandles); | |
| ASSERT_THAT(clientReadHandle, NotNull()); | |
| EXPECT_CALL(*clientReadHandle, GetData).WillOnce(Return(&mClientBufferContent)); | |
| auto* serverHandle = std::get<MockServerWriteHandle*>(serverHandles); | |
| ASSERT_THAT(serverHandle, NotNull()); | |
| EXPECT_CALL(*serverHandle, Destroy).Times(1); | |
| } | |
| ExpectSuccessfulUnmap(wgpu::MapMode::Write, apiBuffer, buffer, clientHandles, | |
| serverHandles); | |
| // Update the handles accordingly if we are MapRead. | |
| if (isRead) { | |
| clientHandles = | |
| std::make_tuple(std::get<MockClientReadHandle*>(clientHandles), nullptr); | |
| serverHandles = | |
| std::make_tuple(std::get<MockServerReadHandle*>(serverHandles), nullptr); | |
| } | |
| } | |
| // Static atomic that's used to generate different values across tests. | |
| static std::atomic<uint32_t> kDataGenerator; | |
| uint32_t mClientBufferContent = 0; | |
| uint32_t mServerBufferContent = 0; | |
| static constexpr size_t kBufferSize = sizeof(uint32_t); | |
| uint32_t mSerializeCreateInfo = kDataGenerator++; | |
| static constexpr size_t kDataSize = sizeof(uint32_t); | |
| StrictMock<MockCppCallback<wgpu::BufferMapCallback<void>*>> mMapAsyncCb; | |
| StrictMock<wire::server::MockMemoryTransferService> mServerMTS; | |
| StrictMock<wire::client::MockMemoryTransferService> mClientMTS; | |
| }; | |
| std::atomic<uint32_t> WireMemoryTransferServiceTestBase::kDataGenerator = 1337; | |
| class WireMemoryTransferServiceBufferHandleTests : public WireMemoryTransferServiceTestBase {}; | |
| INSTANTIATE_TEST_SUITE_P( | |
| , | |
| WireMemoryTransferServiceBufferHandleTests, | |
| testing::ValuesIn(ParamGenerator<WireMemoryTransferServiceBufferHandleTests::ParamType, | |
| MapMode, | |
| MappedAtCreation>({wgpu::MapMode::Read, wgpu::MapMode::Write}, | |
| {true, false})), | |
| &TestParamToString<WireMemoryTransferServiceBufferHandleTests::ParamType>); | |
| // Test handle(s) destroy behavior. | |
| TEST_P(WireMemoryTransferServiceBufferHandleTests, Destroy) { | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| MockClientHandles clientHandles; | |
| MockServerHandles serverHandles; | |
| std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer(); | |
| // The client handles are destroyed on buffer.Destroy(). | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer.Destroy(); | |
| // The server handles are destroyed when the destroy command is flushed. | |
| ExpectServerHandleDestruction(serverHandles); | |
| EXPECT_CALL(api, BufferDestroy(apiBuffer)).Times(1); | |
| FlushClient(); | |
| // Releasing the buffer should be reflected on the server when flushed. | |
| buffer = nullptr; | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test handle(s) creation failure. | |
| TEST_P(WireMemoryTransferServiceBufferHandleTests, CreationFailure) { | |
| ExpectHandleCreation(false); | |
| wgpu::BufferDescriptor descriptor = {}; | |
| descriptor.size = kBufferSize; | |
| descriptor.usage = GetUsage(); | |
| WGPUBuffer apiErrorBuffer = api.GetNewBuffer(); | |
| wgpu::Buffer errorBuffer = device.CreateBuffer(&descriptor); | |
| EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice, _)).WillOnce(Return(apiErrorBuffer)); | |
| FlushClient(); | |
| // Releasing the buffer should be reflected on the server when flushed. | |
| errorBuffer = nullptr; | |
| EXPECT_CALL(api, BufferRelease(apiErrorBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test handle(s) deserialization (only the handles across the wire, not the data) failure. | |
| TEST_P(WireMemoryTransferServiceBufferHandleTests, DeserializationFailure) { | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| // The client should create and serialize the appropriate handles on buffer creation. | |
| auto clientHandles = ExpectHandleCreation(true); | |
| ExpectHandleSerialization(clientHandles); | |
| std::tie(apiBuffer, buffer) = CreateBuffer(); | |
| // When the commands are flushed, mock that the server fails to deserialize the handle. | |
| ExpectHandleDeserialization(false); | |
| FlushClient(false); | |
| // The client handles are destroyed when the buffer is released. | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer = nullptr; | |
| // Releasing the buffer should be reflected on the server when flushed. | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| class WireMemoryTransferServiceBufferMapAsyncTests : public WireMemoryTransferServiceTestBase {}; | |
| INSTANTIATE_TEST_SUITE_P( | |
| , | |
| WireMemoryTransferServiceBufferMapAsyncTests, | |
| testing::ValuesIn(ParamGenerator<WireMemoryTransferServiceBufferMapAsyncTests::ParamType, | |
| MapMode, | |
| MappedAtCreation>({wgpu::MapMode::Read, wgpu::MapMode::Write}, | |
| {true, false})), | |
| &TestParamToString<WireMemoryTransferServiceBufferMapAsyncTests::ParamType>); | |
| // Test successful mapping. | |
| TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, Success) { | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| MockClientHandles clientHandles; | |
| MockServerHandles serverHandles; | |
| std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer(); | |
| // If we were mappedAtCreation, successfully handle that initial unmapping now. | |
| if (GetParam().mMappedAtCreation) { | |
| ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles); | |
| } | |
| // Ensure that the contents on the client and server side are different now as a part of the | |
| // test. | |
| mClientBufferContent = kDataGenerator++; | |
| mServerBufferContent = kDataGenerator++; | |
| ASSERT_THAT(mClientBufferContent, Ne(mServerBufferContent)); | |
| ExpectSuccessfulMapAsync(apiBuffer, buffer, clientHandles, serverHandles); | |
| ExpectSuccessfulUnmap(GetParam().mMapMode, apiBuffer, buffer, clientHandles, serverHandles); | |
| // The client handles are destroyed when the buffer is released. | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer = nullptr; | |
| // The server handles are destroyed when the release command is flushed. | |
| ExpectServerHandleDestruction(serverHandles); | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test unsuccessful mapping with error. | |
| TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, Error) { | |
| wgpu::MapMode mode = GetParam().mMapMode; | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| MockClientHandles clientHandles; | |
| MockServerHandles serverHandles; | |
| std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer(); | |
| // If we were mappedAtCreation, successfully handle that initial unmapping now. | |
| if (GetParam().mMappedAtCreation) { | |
| ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles); | |
| } | |
| buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous, | |
| mMapAsyncCb.Callback()); | |
| // Make the server respond to the callback with an error. | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mode), 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Error, | |
| ToOutputStringView("Validation error")); | |
| })); | |
| FlushClient(); | |
| // The callback should happen when the server flushes the response. | |
| EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Error, SizedString("Validation error"))) | |
| .Times(1); | |
| FlushServer(); | |
| // Unmap the buffer. | |
| buffer.Unmap(); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| // The client handles are destroyed when the buffer is released. | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer = nullptr; | |
| // The server handles are destroyed when the release command is flushed. | |
| ExpectServerHandleDestruction(serverHandles); | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test DeserializeDataUpdate (actual data) failure w.r.t MapAsync. | |
| TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, DeserializeDataUpdateFailure) { | |
| wgpu::MapMode mode = GetParam().mMapMode; | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| MockClientHandles clientHandles; | |
| MockServerHandles serverHandles; | |
| std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer(); | |
| // If we were mappedAtCreation, successfully handle that initial unmapping now. | |
| if (GetParam().mMappedAtCreation) { | |
| ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles); | |
| } | |
| // Set mode independent expectations for the map async call now. | |
| EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mode), 0, kBufferSize, _)) | |
| .WillOnce(InvokeWithoutArgs([&] { | |
| api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success, | |
| kEmptyOutputStringView); | |
| })); | |
| switch (mode) { | |
| case wgpu::MapMode::Read: { | |
| EXPECT_CALL(mMapAsyncCb, Call(Ne(wgpu::MapAsyncStatus::Success), _)).Times(1); | |
| EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&mServerBufferContent)); | |
| buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous, | |
| mMapAsyncCb.Callback()); | |
| // The server should serialize its buffer when the client flushes. | |
| ExpectServerSerializeData(serverHandles); | |
| FlushClient(); | |
| // Mock that the client fails to deserialize into its buffer when the server flushes. | |
| ExpectClientDeserializeData(false, clientHandles); | |
| FlushServer(false); | |
| break; | |
| } | |
| case wgpu::MapMode::Write: { | |
| EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Success, _)).Times(1); | |
| EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize)) | |
| .WillOnce(Return(&mClientBufferContent)); | |
| auto* clientHandle = std::get<MockClientWriteHandle*>(clientHandles); | |
| ASSERT_THAT(clientHandle, NotNull()); | |
| EXPECT_CALL(*clientHandle, GetData).WillOnce(Return(&mClientBufferContent)); | |
| buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous, | |
| mMapAsyncCb.Callback()); | |
| FlushClient(); | |
| FlushServer(); | |
| // The client should serialize its buffer when Unmap is called. | |
| ExpectClientSerializeData(clientHandles); | |
| buffer.Unmap(); | |
| // Mock that the server fails to deserialize into its buffer when the client flushes. | |
| ExpectServerDeserializeData(false, serverHandles); | |
| FlushClient(false); | |
| break; | |
| } | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| // The client handles are destroyed when the buffer is released. | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer = nullptr; | |
| // The server handles are destroyed when the release command is flushed. | |
| ExpectServerHandleDestruction(serverHandles); | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test mapping and then destroying the buffer before unmapping on the client side. | |
| TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, DestroyBeforeUnmap) { | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| MockClientHandles clientHandles; | |
| MockServerHandles serverHandles; | |
| std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer(); | |
| // If we were mappedAtCreation, successfully handle that initial unmapping now. | |
| if (GetParam().mMappedAtCreation) { | |
| ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles); | |
| } | |
| ExpectSuccessfulMapAsync(apiBuffer, buffer, clientHandles, serverHandles); | |
| // THIS IS THE TEST: destroy the buffer before unmapping and check it destroyed the mapping | |
| // immediately, both in the client and server side. | |
| { | |
| // Destroying the buffer should immediately destroy the client handles. | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer.Destroy(); | |
| // Flushing the client should destroy the server handles. | |
| ExpectServerHandleDestruction(serverHandles); | |
| EXPECT_CALL(api, BufferDestroy(apiBuffer)).Times(1); | |
| FlushClient(); | |
| // The handle(s) are already destroyed so unmap only results in a server unmap call. | |
| buffer.Unmap(); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // The handle(s) are already destroyed so release only results in a server release call. | |
| buffer = nullptr; | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| class WireMemoryTransferServiceBufferMappedAtCreationTests | |
| : public WireMemoryTransferServiceTestBase {}; | |
| INSTANTIATE_TEST_SUITE_P( | |
| , | |
| WireMemoryTransferServiceBufferMappedAtCreationTests, | |
| testing::ValuesIn( | |
| ParamGenerator<WireMemoryTransferServiceBufferMappedAtCreationTests::ParamType, | |
| MapMode, | |
| MappedAtCreation>({wgpu::MapMode::Read, wgpu::MapMode::Write}, {true})), | |
| &TestParamToString<WireMemoryTransferServiceBufferMappedAtCreationTests::ParamType>); | |
| // Test DeserializeDataUpdate (actual data) failure w.r.t for the initial Unmap. | |
| TEST_P(WireMemoryTransferServiceBufferMappedAtCreationTests, DeserializeDataUpdateFailure) { | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| MockClientHandles clientHandles; | |
| MockServerHandles serverHandles; | |
| std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer(); | |
| // The client should serialize its buffer when Unmap is called. Additionally, if we are in read | |
| // mode, the client side WriteHandle used for mappedAtCreation should be destroyed now and it | |
| // should reset to use the ReadHandle. | |
| ExpectClientSerializeData(clientHandles); | |
| if (GetParam().mMapMode == wgpu::MapMode::Read) { | |
| auto* clientWriteHandle = std::get<MockClientWriteHandle*>(clientHandles); | |
| ASSERT_THAT(clientWriteHandle, NotNull()); | |
| EXPECT_CALL(*clientWriteHandle, Destroy).Times(1); | |
| auto* clientReadHandle = std::get<MockClientReadHandle*>(clientHandles); | |
| ASSERT_THAT(clientReadHandle, NotNull()); | |
| EXPECT_CALL(*clientReadHandle, GetData).WillOnce(Return(&mClientBufferContent)); | |
| clientHandles = std::make_tuple(std::get<MockClientReadHandle*>(clientHandles), nullptr); | |
| } | |
| buffer.Unmap(); | |
| // Mock that the server fails to deserialize into its buffer when the client flushes. | |
| ExpectServerDeserializeData(false, serverHandles); | |
| FlushClient(false); | |
| // The client handles are destroyed when the buffer is released. | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer = nullptr; | |
| // The server handles are destroyed when the release command is flushed. | |
| ExpectServerHandleDestruction(serverHandles); | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // Test mapping and then destroying the buffer before unmapping on the client side. | |
| TEST_P(WireMemoryTransferServiceBufferMappedAtCreationTests, DestroyBeforeUnmap) { | |
| WGPUBuffer apiBuffer; | |
| wgpu::Buffer buffer; | |
| MockClientHandles clientHandles; | |
| MockServerHandles serverHandles; | |
| std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer(); | |
| // THIS IS THE TEST: destroy the buffer before unmapping and check it destroyed the mapping | |
| // immediately, both in the client and server side. | |
| { | |
| // Destroying the buffer should immediately destroy the client handles. | |
| ExpectClientHandleDestruction(clientHandles); | |
| buffer.Destroy(); | |
| // Flushing the client should destroy the server handles. | |
| ExpectServerHandleDestruction(serverHandles); | |
| EXPECT_CALL(api, BufferDestroy(apiBuffer)).Times(1); | |
| FlushClient(); | |
| // The handle(s) are already destroyed so unmap only results in a server unmap call. | |
| buffer.Unmap(); | |
| EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| // The handle(s) are already destroyed so release only results in a server release call. | |
| buffer = nullptr; | |
| EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); | |
| FlushClient(); | |
| } | |
| } // anonymous namespace | |
| } // namespace dawn::wire |