dawn_wire: Add an API to reclaim reserved devices and textures

Dawn Wire has a way to reserve an ID and generation on the client side,
but if these reservations are never injected on the server, then
it will be impossible to reclaim the in-use ObjectIDs.

Bug: dawn:565
Change-Id: I751fce237c881e8cbdeaba18ad0ec1e124bd7ac2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/38281
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Stephen White <senorblanco@chromium.org>
diff --git a/src/dawn_wire/WireClient.cpp b/src/dawn_wire/WireClient.cpp
index 0dcea37..d20fdc9 100644
--- a/src/dawn_wire/WireClient.cpp
+++ b/src/dawn_wire/WireClient.cpp
@@ -41,6 +41,14 @@
         return mImpl->ReserveDevice();
     }
 
+    void WireClient::ReclaimTextureReservation(const ReservedTexture& reservation) {
+        mImpl->ReclaimTextureReservation(reservation);
+    }
+
+    void WireClient::ReclaimDeviceReservation(const ReservedDevice& reservation) {
+        mImpl->ReclaimDeviceReservation(reservation);
+    }
+
     void WireClient::Disconnect() {
         mImpl->Disconnect();
     }
diff --git a/src/dawn_wire/client/Client.cpp b/src/dawn_wire/client/Client.cpp
index b566529..7665a70 100644
--- a/src/dawn_wire/client/Client.cpp
+++ b/src/dawn_wire/client/Client.cpp
@@ -118,6 +118,14 @@
         return result;
     }
 
+    void Client::ReclaimTextureReservation(const ReservedTexture& reservation) {
+        TextureAllocator().Free(FromAPI(reservation.texture));
+    }
+
+    void Client::ReclaimDeviceReservation(const ReservedDevice& reservation) {
+        DeviceAllocator().Free(FromAPI(reservation.device));
+    }
+
     void Client::Disconnect() {
         mDisconnected = true;
         mSerializer = ChunkedCommandSerializer(NoopCommandSerializer::GetInstance());
diff --git a/src/dawn_wire/client/Client.h b/src/dawn_wire/client/Client.h
index dd7ac76..d0e3d5a 100644
--- a/src/dawn_wire/client/Client.h
+++ b/src/dawn_wire/client/Client.h
@@ -48,6 +48,9 @@
         ReservedTexture ReserveTexture(WGPUDevice device);
         ReservedDevice ReserveDevice();
 
+        void ReclaimTextureReservation(const ReservedTexture& reservation);
+        void ReclaimDeviceReservation(const ReservedDevice& reservation);
+
         template <typename Cmd>
         void SerializeCommand(const Cmd& cmd) {
             mSerializer.SerializeCommand(cmd, *this);
diff --git a/src/include/dawn_wire/WireClient.h b/src/include/dawn_wire/WireClient.h
index b8f1247..5f8b479 100644
--- a/src/include/dawn_wire/WireClient.h
+++ b/src/include/dawn_wire/WireClient.h
@@ -61,6 +61,9 @@
         ReservedTexture ReserveTexture(WGPUDevice device);
         ReservedDevice ReserveDevice();
 
+        void ReclaimTextureReservation(const ReservedTexture& reservation);
+        void ReclaimDeviceReservation(const ReservedDevice& reservation);
+
         // Disconnects the client.
         // Commands allocated after this point will not be sent.
         void Disconnect();
diff --git a/src/tests/unittests/wire/WireInjectDeviceTests.cpp b/src/tests/unittests/wire/WireInjectDeviceTests.cpp
index 897af4d..f1cad6d 100644
--- a/src/tests/unittests/wire/WireInjectDeviceTests.cpp
+++ b/src/tests/unittests/wire/WireInjectDeviceTests.cpp
@@ -228,3 +228,29 @@
     EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, nullptr, nullptr)).Times(1);
     EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, nullptr, nullptr)).Times(1);
 }
+
+// Test that a device reservation can be reclaimed. This is necessary to
+// avoid leaking ObjectIDs for reservations that are never injected.
+TEST_F(WireInjectDeviceTests, ReclaimDeviceReservation) {
+    // Test that doing a reservation and full release is an error.
+    {
+        ReservedDevice reservation = GetWireClient()->ReserveDevice();
+        wgpuDeviceRelease(reservation.device);
+        FlushClient(false);
+    }
+
+    // Test that doing a reservation and then reclaiming it recycles the ID.
+    {
+        ReservedDevice reservation1 = GetWireClient()->ReserveDevice();
+        GetWireClient()->ReclaimDeviceReservation(reservation1);
+
+        ReservedDevice reservation2 = GetWireClient()->ReserveDevice();
+
+        // 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();
+    }
+}
diff --git a/src/tests/unittests/wire/WireInjectTextureTests.cpp b/src/tests/unittests/wire/WireInjectTextureTests.cpp
index c6a1f2c..d8df43a 100644
--- a/src/tests/unittests/wire/WireInjectTextureTests.cpp
+++ b/src/tests/unittests/wire/WireInjectTextureTests.cpp
@@ -86,3 +86,29 @@
     DeleteServer();
     Mock::VerifyAndClearExpectations(&api);
 }
+
+// Test that a texture reservation can be reclaimed. This is necessary to
+// avoid leaking ObjectIDs for reservations that are never injected.
+TEST_F(WireInjectTextureTests, ReclaimTextureReservation) {
+    // Test that doing a reservation and full release is an error.
+    {
+        ReservedTexture reservation = GetWireClient()->ReserveTexture(device);
+        wgpuTextureRelease(reservation.texture);
+        FlushClient(false);
+    }
+
+    // Test that doing a reservation and then reclaiming it recycles the ID.
+    {
+        ReservedTexture reservation1 = GetWireClient()->ReserveTexture(device);
+        GetWireClient()->ReclaimTextureReservation(reservation1);
+
+        ReservedTexture reservation2 = GetWireClient()->ReserveTexture(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();
+    }
+}