diff --git a/src/dawn_wire/WireClient.cpp b/src/dawn_wire/WireClient.cpp index 481ced4377..aa8301379a 100644 --- a/src/dawn_wire/WireClient.cpp +++ b/src/dawn_wire/WireClient.cpp @@ -33,6 +33,10 @@ namespace dawn_wire { return mImpl->ReserveTexture(device); } + ReservedSwapChain WireClient::ReserveSwapChain(WGPUDevice device) { + return mImpl->ReserveSwapChain(device); + } + ReservedDevice WireClient::ReserveDevice() { return mImpl->ReserveDevice(); } @@ -41,6 +45,10 @@ namespace dawn_wire { mImpl->ReclaimTextureReservation(reservation); } + void WireClient::ReclaimSwapChainReservation(const ReservedSwapChain& reservation) { + mImpl->ReclaimSwapChainReservation(reservation); + } + void WireClient::ReclaimDeviceReservation(const ReservedDevice& reservation) { mImpl->ReclaimDeviceReservation(reservation); } diff --git a/src/dawn_wire/WireServer.cpp b/src/dawn_wire/WireServer.cpp index a3599f4649..bb3d7ba123 100644 --- a/src/dawn_wire/WireServer.cpp +++ b/src/dawn_wire/WireServer.cpp @@ -39,6 +39,14 @@ namespace dawn_wire { return mImpl->InjectTexture(texture, id, generation, deviceId, deviceGeneration); } + bool WireServer::InjectSwapChain(WGPUSwapChain swapchain, + uint32_t id, + uint32_t generation, + uint32_t deviceId, + uint32_t deviceGeneration) { + return mImpl->InjectSwapChain(swapchain, id, generation, deviceId, deviceGeneration); + } + bool WireServer::InjectDevice(WGPUDevice device, uint32_t id, uint32_t generation) { return mImpl->InjectDevice(device, id, generation); } diff --git a/src/dawn_wire/client/Client.cpp b/src/dawn_wire/client/Client.cpp index 3b29f9e7ee..a00bb5e90f 100644 --- a/src/dawn_wire/client/Client.cpp +++ b/src/dawn_wire/client/Client.cpp @@ -96,6 +96,18 @@ namespace dawn_wire { namespace client { return result; } + ReservedSwapChain Client::ReserveSwapChain(WGPUDevice device) { + auto* allocation = SwapChainAllocator().New(this); + + ReservedSwapChain result; + result.swapchain = ToAPI(allocation->object.get()); + result.id = allocation->object->id; + result.generation = allocation->generation; + result.deviceId = FromAPI(device)->id; + result.deviceGeneration = DeviceAllocator().GetGeneration(FromAPI(device)->id); + return result; + } + ReservedDevice Client::ReserveDevice() { auto* allocation = DeviceAllocator().New(this); @@ -110,6 +122,10 @@ namespace dawn_wire { namespace client { TextureAllocator().Free(FromAPI(reservation.texture)); } + void Client::ReclaimSwapChainReservation(const ReservedSwapChain& reservation) { + SwapChainAllocator().Free(FromAPI(reservation.swapchain)); + } + void Client::ReclaimDeviceReservation(const ReservedDevice& reservation) { DeviceAllocator().Free(FromAPI(reservation.device)); } diff --git a/src/dawn_wire/client/Client.h b/src/dawn_wire/client/Client.h index db9ed43119..3616e37215 100644 --- a/src/dawn_wire/client/Client.h +++ b/src/dawn_wire/client/Client.h @@ -44,9 +44,11 @@ namespace dawn_wire { namespace client { } ReservedTexture ReserveTexture(WGPUDevice device); + ReservedSwapChain ReserveSwapChain(WGPUDevice device); ReservedDevice ReserveDevice(); void ReclaimTextureReservation(const ReservedTexture& reservation); + void ReclaimSwapChainReservation(const ReservedSwapChain& reservation); void ReclaimDeviceReservation(const ReservedDevice& reservation); template diff --git a/src/dawn_wire/server/Server.cpp b/src/dawn_wire/server/Server.cpp index ea0d8fcfc5..24fbedabd2 100644 --- a/src/dawn_wire/server/Server.cpp +++ b/src/dawn_wire/server/Server.cpp @@ -72,6 +72,38 @@ namespace dawn_wire { namespace server { return true; } + bool Server::InjectSwapChain(WGPUSwapChain swapchain, + uint32_t id, + uint32_t generation, + uint32_t deviceId, + uint32_t deviceGeneration) { + ASSERT(swapchain != nullptr); + ObjectData* device = DeviceObjects().Get(deviceId); + if (device == nullptr || device->generation != deviceGeneration) { + return false; + } + + ObjectData* data = SwapChainObjects().Allocate(id); + if (data == nullptr) { + return false; + } + + data->handle = swapchain; + data->generation = generation; + data->state = AllocationState::Allocated; + data->deviceInfo = device->info.get(); + + if (!TrackDeviceChild(data->deviceInfo, ObjectType::SwapChain, id)) { + return false; + } + + // The texture is externally owned so it shouldn't be destroyed when we receive a destroy + // message from the client. Add a reference to counterbalance the eventual release. + mProcs.swapChainReference(swapchain); + + return true; + } + bool Server::InjectDevice(WGPUDevice device, uint32_t id, uint32_t generation) { ASSERT(device != nullptr); ObjectData* data = DeviceObjects().Allocate(id); diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h index 5baea199ea..1979d87d23 100644 --- a/src/dawn_wire/server/Server.h +++ b/src/dawn_wire/server/Server.h @@ -180,6 +180,12 @@ namespace dawn_wire { namespace server { uint32_t deviceId, uint32_t deviceGeneration); + bool InjectSwapChain(WGPUSwapChain swapchain, + uint32_t id, + uint32_t generation, + uint32_t deviceId, + uint32_t deviceGeneration); + bool InjectDevice(WGPUDevice device, uint32_t id, uint32_t generation); WGPUDevice GetDevice(uint32_t id, uint32_t generation); diff --git a/src/include/dawn_wire/WireClient.h b/src/include/dawn_wire/WireClient.h index 097fd1a599..87f2bab0d7 100644 --- a/src/include/dawn_wire/WireClient.h +++ b/src/include/dawn_wire/WireClient.h @@ -38,6 +38,14 @@ namespace dawn_wire { uint32_t deviceGeneration; }; + struct ReservedSwapChain { + WGPUSwapChain swapchain; + uint32_t id; + uint32_t generation; + uint32_t deviceId; + uint32_t deviceGeneration; + }; + struct ReservedDevice { WGPUDevice device; uint32_t id; @@ -58,9 +66,11 @@ namespace dawn_wire { size_t size) override final; ReservedTexture ReserveTexture(WGPUDevice device); + ReservedSwapChain ReserveSwapChain(WGPUDevice device); ReservedDevice ReserveDevice(); void ReclaimTextureReservation(const ReservedTexture& reservation); + void ReclaimSwapChainReservation(const ReservedSwapChain& reservation); void ReclaimDeviceReservation(const ReservedDevice& reservation); // Disconnects the client. diff --git a/src/include/dawn_wire/WireServer.h b/src/include/dawn_wire/WireServer.h index 59df6a4947..14d2354580 100644 --- a/src/include/dawn_wire/WireServer.h +++ b/src/include/dawn_wire/WireServer.h @@ -42,12 +42,16 @@ namespace dawn_wire { const volatile char* HandleCommands(const volatile char* commands, size_t size) override final; - // TODO(enga): Remove defaults after updating Chrome. bool InjectTexture(WGPUTexture texture, uint32_t id, uint32_t generation, - uint32_t deviceId = 1, - uint32_t deviceGeneration = 0); + uint32_t deviceId, + uint32_t deviceGeneration); + bool InjectSwapChain(WGPUSwapChain swapchain, + uint32_t id, + uint32_t generation, + uint32_t deviceId, + uint32_t deviceGeneration); bool InjectDevice(WGPUDevice device, uint32_t id, uint32_t generation); diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index 18d2fb536e..b025a632ba 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -230,6 +230,7 @@ test("dawn_unittests") { "unittests/wire/WireExtensionTests.cpp", "unittests/wire/WireFenceTests.cpp", "unittests/wire/WireInjectDeviceTests.cpp", + "unittests/wire/WireInjectSwapChainTests.cpp", "unittests/wire/WireInjectTextureTests.cpp", "unittests/wire/WireMemoryTransferServiceTests.cpp", "unittests/wire/WireOptionalTests.cpp", diff --git a/src/tests/unittests/wire/WireInjectSwapChainTests.cpp b/src/tests/unittests/wire/WireInjectSwapChainTests.cpp new file mode 100644 index 0000000000..6c3e104e7d --- /dev/null +++ b/src/tests/unittests/wire/WireInjectSwapChainTests.cpp @@ -0,0 +1,116 @@ +// Copyright 2021 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 "tests/unittests/wire/WireTest.h" + +#include "dawn_wire/WireClient.h" +#include "dawn_wire/WireServer.h" + +using namespace testing; +using namespace dawn_wire; + +class WireInjectSwapChainTests : public WireTest { + public: + WireInjectSwapChainTests() { + } + ~WireInjectSwapChainTests() override = default; +}; + +// Test that reserving and injecting a swapchain makes calls on the client object forward to the +// server object correctly. +TEST_F(WireInjectSwapChainTests, CallAfterReserveInject) { + ReservedSwapChain reservation = GetWireClient()->ReserveSwapChain(device); + + WGPUSwapChain apiSwapchain = api.GetNewSwapChain(); + EXPECT_CALL(api, SwapChainReference(apiSwapchain)); + ASSERT_TRUE(GetWireServer()->InjectSwapChain(apiSwapchain, reservation.id, + reservation.generation, reservation.deviceId, + reservation.deviceGeneration)); + + wgpuSwapChainPresent(reservation.swapchain); + EXPECT_CALL(api, SwapChainPresent(apiSwapchain)); + FlushClient(); +} + +// Test that reserve correctly returns different IDs each time. +TEST_F(WireInjectSwapChainTests, ReserveDifferentIDs) { + ReservedSwapChain reservation1 = GetWireClient()->ReserveSwapChain(device); + ReservedSwapChain reservation2 = GetWireClient()->ReserveSwapChain(device); + + ASSERT_NE(reservation1.id, reservation2.id); + ASSERT_NE(reservation1.swapchain, reservation2.swapchain); +} + +// Test that injecting the same id without a destroy first fails. +TEST_F(WireInjectSwapChainTests, InjectExistingID) { + ReservedSwapChain reservation = GetWireClient()->ReserveSwapChain(device); + + WGPUSwapChain apiSwapchain = api.GetNewSwapChain(); + EXPECT_CALL(api, SwapChainReference(apiSwapchain)); + ASSERT_TRUE(GetWireServer()->InjectSwapChain(apiSwapchain, reservation.id, + reservation.generation, reservation.deviceId, + reservation.deviceGeneration)); + + // ID already in use, call fails. + ASSERT_FALSE(GetWireServer()->InjectSwapChain(apiSwapchain, reservation.id, + reservation.generation, reservation.deviceId, + reservation.deviceGeneration)); +} + +// Test that the server only borrows the swapchain and does a single reference-release +TEST_F(WireInjectSwapChainTests, InjectedSwapChainLifetime) { + ReservedSwapChain reservation = GetWireClient()->ReserveSwapChain(device); + + // Injecting the swapchain adds a reference + WGPUSwapChain apiSwapchain = api.GetNewSwapChain(); + EXPECT_CALL(api, SwapChainReference(apiSwapchain)); + ASSERT_TRUE(GetWireServer()->InjectSwapChain(apiSwapchain, reservation.id, + reservation.generation, reservation.deviceId, + reservation.deviceGeneration)); + + // Releasing the swapchain removes a single reference. + wgpuSwapChainRelease(reservation.swapchain); + EXPECT_CALL(api, SwapChainRelease(apiSwapchain)); + FlushClient(); + + // Deleting the server doesn't release a second reference. + DeleteServer(); + Mock::VerifyAndClearExpectations(&api); +} + +// Test that a swapchain reservation can be reclaimed. This is necessary to +// avoid leaking ObjectIDs for reservations that are never injected. +TEST_F(WireInjectSwapChainTests, ReclaimSwapChainReservation) { + // Test that doing a reservation and full release is an error. + { + ReservedSwapChain reservation = GetWireClient()->ReserveSwapChain(device); + wgpuSwapChainRelease(reservation.swapchain); + FlushClient(false); + } + + // Test that doing a reservation and then reclaiming it recycles the ID. + { + ReservedSwapChain reservation1 = GetWireClient()->ReserveSwapChain(device); + GetWireClient()->ReclaimSwapChainReservation(reservation1); + + ReservedSwapChain reservation2 = GetWireClient()->ReserveSwapChain(device); + + // The ID is the same, but the generation is still different. + ASSERT_EQ(reservation1.id, reservation2.id); + ASSERT_NE(reservation1.generation, reservation2.generation); + + // No errors should occur. + FlushClient(); + } +}