Add dawn_wire entrypoints to inject texture in the wire

This allows reserving a texture ID in the client and injecting textures
in the wire, so that the WebGPU control channel can create WebGPU
textures backed by SharedImages in Chromium.

BUG=941543

Change-Id: I1efcfe3dce024bb2d3592f22225407a97b641c1f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/5820
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index f58ef64..3ad34dc 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -593,6 +593,7 @@
     "src/tests/unittests/wire/WireBufferMappingTests.cpp",
     "src/tests/unittests/wire/WireCallbackTests.cpp",
     "src/tests/unittests/wire/WireFenceTests.cpp",
+    "src/tests/unittests/wire/WireInjectTextureTests.cpp",
     "src/tests/unittests/wire/WireOptionalTests.cpp",
     "src/tests/unittests/wire/WireTest.cpp",
     "src/tests/unittests/wire/WireTest.h",
diff --git a/src/dawn_wire/WireClient.cpp b/src/dawn_wire/WireClient.cpp
index c4e4088..ceb1c68 100644
--- a/src/dawn_wire/WireClient.cpp
+++ b/src/dawn_wire/WireClient.cpp
@@ -36,4 +36,8 @@
         return mImpl->HandleCommands(commands, size);
     }
 
+    ReservedTexture WireClient::ReserveTexture(DawnDevice device) {
+        return mImpl->ReserveTexture(device);
+    }
+
 }  // namespace dawn_wire
diff --git a/src/dawn_wire/WireServer.cpp b/src/dawn_wire/WireServer.cpp
index 79706e0..bfa186f 100644
--- a/src/dawn_wire/WireServer.cpp
+++ b/src/dawn_wire/WireServer.cpp
@@ -31,4 +31,8 @@
         return mImpl->HandleCommands(commands, size);
     }
 
+    bool WireServer::InjectTexture(DawnTexture texture, uint32_t id, uint32_t generation) {
+        return mImpl->InjectTexture(texture, id, generation);
+    }
+
 }  // namespace dawn_wire
diff --git a/src/dawn_wire/client/Client.cpp b/src/dawn_wire/client/Client.cpp
index ca2fe94..195c55f 100644
--- a/src/dawn_wire/client/Client.cpp
+++ b/src/dawn_wire/client/Client.cpp
@@ -27,4 +27,15 @@
         DeviceAllocator().Free(mDevice);
     }
 
+    ReservedTexture Client::ReserveTexture(DawnDevice cDevice) {
+        Device* device = reinterpret_cast<Device*>(cDevice);
+        ObjectAllocator<Texture>::ObjectAndSerial* allocation = TextureAllocator().New(device);
+
+        ReservedTexture result;
+        result.texture = reinterpret_cast<DawnTexture>(allocation->object.get());
+        result.id = allocation->object->id;
+        result.generation = allocation->serial;
+        return result;
+    }
+
 }}  // namespace dawn_wire::client
diff --git a/src/dawn_wire/client/Client.h b/src/dawn_wire/client/Client.h
index 7ea6279..5fd74ce 100644
--- a/src/dawn_wire/client/Client.h
+++ b/src/dawn_wire/client/Client.h
@@ -17,6 +17,7 @@
 
 #include <dawn_wire/Wire.h>
 
+#include "dawn_wire/WireClient.h"
 #include "dawn_wire/WireCmd_autogen.h"
 #include "dawn_wire/WireDeserializeAllocator.h"
 #include "dawn_wire/client/ClientBase_autogen.h"
@@ -31,6 +32,7 @@
         ~Client();
 
         const char* HandleCommands(const char* commands, size_t size);
+        ReservedTexture ReserveTexture(DawnDevice device);
 
         void* GetCmdSpace(size_t size) {
             return mSerializer->GetCmdSpace(size);
diff --git a/src/dawn_wire/server/Server.cpp b/src/dawn_wire/server/Server.cpp
index 752b60f..6d420ba 100644
--- a/src/dawn_wire/server/Server.cpp
+++ b/src/dawn_wire/server/Server.cpp
@@ -24,7 +24,7 @@
         deviceData->valid = true;
 
         auto userdata = static_cast<DawnCallbackUserdata>(reinterpret_cast<intptr_t>(this));
-        procs.deviceSetErrorCallback(device, ForwardDeviceError, userdata);
+        mProcs.deviceSetErrorCallback(device, ForwardDeviceError, userdata);
     }
 
     Server::~Server() {
@@ -35,4 +35,22 @@
         return mSerializer->GetCmdSpace(size);
     }
 
+    bool Server::InjectTexture(DawnTexture texture, uint32_t id, uint32_t generation) {
+        ObjectData<DawnTexture>* data = TextureObjects().Allocate(id);
+        if (data == nullptr) {
+            return false;
+        }
+
+        data->handle = texture;
+        data->serial = generation;
+        data->valid = true;
+        data->allocated = true;
+
+        // 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.textureReference(texture);
+
+        return true;
+    }
+
 }}  // namespace dawn_wire::server
diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h
index ac622ea..2081f55 100644
--- a/src/dawn_wire/server/Server.h
+++ b/src/dawn_wire/server/Server.h
@@ -42,6 +42,8 @@
 
         const char* HandleCommands(const char* commands, size_t size);
 
+        bool InjectTexture(DawnTexture texture, uint32_t id, uint32_t generation);
+
       private:
         void* GetCmdSpace(size_t size);
 
diff --git a/src/include/dawn_wire/WireClient.h b/src/include/dawn_wire/WireClient.h
index d0d9277..4e9e56a 100644
--- a/src/include/dawn_wire/WireClient.h
+++ b/src/include/dawn_wire/WireClient.h
@@ -25,6 +25,12 @@
         class Client;
     }
 
+    struct ReservedTexture {
+        DawnTexture texture;
+        uint32_t id;
+        uint32_t generation;
+    };
+
     class DAWN_WIRE_EXPORT WireClient : public CommandHandler {
       public:
         WireClient(CommandSerializer* serializer);
@@ -34,6 +40,8 @@
         DawnProcTable GetProcs() const;
         const char* HandleCommands(const char* commands, size_t size) override final;
 
+        ReservedTexture ReserveTexture(DawnDevice device);
+
       private:
         std::unique_ptr<client::Client> mImpl;
     };
diff --git a/src/include/dawn_wire/WireServer.h b/src/include/dawn_wire/WireServer.h
index 04220f8..084ab54 100644
--- a/src/include/dawn_wire/WireServer.h
+++ b/src/include/dawn_wire/WireServer.h
@@ -32,6 +32,8 @@
 
         const char* HandleCommands(const char* commands, size_t size) override final;
 
+        bool InjectTexture(DawnTexture texture, uint32_t id, uint32_t generation);
+
       private:
         std::unique_ptr<server::Server> mImpl;
     };
diff --git a/src/tests/unittests/wire/WireInjectTextureTests.cpp b/src/tests/unittests/wire/WireInjectTextureTests.cpp
new file mode 100644
index 0000000..7c3a83b
--- /dev/null
+++ b/src/tests/unittests/wire/WireInjectTextureTests.cpp
@@ -0,0 +1,83 @@
+// Copyright 2019 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 WireInjectTextureTests : public WireTest {
+  public:
+    WireInjectTextureTests() : WireTest(true) {
+    }
+    ~WireInjectTextureTests() override = default;
+};
+
+// Test that reserving and injecting a texture makes calls on the client object forward to the
+// server object correctly.
+TEST_F(WireInjectTextureTests, CallAfterReserveInject) {
+    ReservedTexture reservation = GetWireClient()->ReserveTexture(device);
+
+    DawnTexture apiTexture = api.GetNewTexture();
+    EXPECT_CALL(api, TextureReference(apiTexture));
+    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+
+    dawnTextureCreateDefaultTextureView(reservation.texture);
+    EXPECT_CALL(api, TextureCreateDefaultTextureView(apiTexture)).WillOnce(Return(nullptr));
+    FlushClient();
+}
+
+// Test that reserve correctly returns different IDs each time.
+TEST_F(WireInjectTextureTests, ReserveDifferentIDs) {
+    ReservedTexture reservation1 = GetWireClient()->ReserveTexture(device);
+    ReservedTexture reservation2 = GetWireClient()->ReserveTexture(device);
+
+    ASSERT_NE(reservation1.id, reservation2.id);
+    ASSERT_NE(reservation1.texture, reservation2.texture);
+}
+
+// Test that injecting the same id without a destroy first fails.
+TEST_F(WireInjectTextureTests, InjectExistingID) {
+    ReservedTexture reservation = GetWireClient()->ReserveTexture(device);
+
+    DawnTexture apiTexture = api.GetNewTexture();
+    EXPECT_CALL(api, TextureReference(apiTexture));
+    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+
+    // ID already in use, call fails.
+    ASSERT_FALSE(
+        GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+}
+
+// Test that the server only borrows the texture and does a single reference-release
+TEST_F(WireInjectTextureTests, InjectedTextureLifetime) {
+    ReservedTexture reservation = GetWireClient()->ReserveTexture(device);
+
+    // Injecting the texture adds a reference
+    DawnTexture apiTexture = api.GetNewTexture();
+    EXPECT_CALL(api, TextureReference(apiTexture));
+    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+
+    // Releasing the texture removes a single reference.
+    dawnTextureRelease(reservation.texture);
+    EXPECT_CALL(api, TextureRelease(apiTexture));
+    FlushClient();
+
+    // Deleting the server doesn't release a second reference.
+    DeleteServer();
+    Mock::VerifyAndClearExpectations(&api);
+}
diff --git a/src/tests/unittests/wire/WireTest.cpp b/src/tests/unittests/wire/WireTest.cpp
index 72b1236..7bc94e0 100644
--- a/src/tests/unittests/wire/WireTest.cpp
+++ b/src/tests/unittests/wire/WireTest.cpp
@@ -74,6 +74,18 @@
     ASSERT_TRUE(mS2cBuf->Flush());
 }
 
+dawn_wire::WireServer* WireTest::GetWireServer() {
+    return mWireServer.get();
+}
+
+dawn_wire::WireClient* WireTest::GetWireClient() {
+    return mWireClient.get();
+}
+
+void WireTest::DeleteServer() {
+    mWireServer = nullptr;
+}
+
 void WireTest::SetupIgnoredCallExpectations() {
     if (mIgnoreSetCallbackCalls) {
         EXPECT_CALL(api, OnBuilderSetErrorCallback(_, _, _, _)).Times(AnyNumber());
diff --git a/src/tests/unittests/wire/WireTest.h b/src/tests/unittests/wire/WireTest.h
index bddf613..3d32437 100644
--- a/src/tests/unittests/wire/WireTest.h
+++ b/src/tests/unittests/wire/WireTest.h
@@ -89,6 +89,11 @@
     DawnDevice apiDevice;
     DawnDevice device;
 
+    dawn_wire::WireServer* GetWireServer();
+    dawn_wire::WireClient* GetWireClient();
+
+    void DeleteServer();
+
   private:
     void SetupIgnoredCallExpectations();
     bool mIgnoreSetCallbackCalls = false;