Add Reserve/InjectBuffer to Wire
Adds reserve and inject functionality for buffers to the wire. Includes basic tests. This is necessary for Chromium to utilize SharedBufferMemory.
Bug: dawn:2382
Change-Id: I9599c1bff32815a0437c21e8810dc0cb5a6623bb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/186825
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Brandon1 Jones <brandon1.jones@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/include/dawn/wire/WireClient.h b/include/dawn/wire/WireClient.h
index a1e268a..82cb2d7 100644
--- a/include/dawn/wire/WireClient.h
+++ b/include/dawn/wire/WireClient.h
@@ -43,6 +43,12 @@
DAWN_WIRE_EXPORT const DawnProcTable& GetProcs();
} // namespace client
+struct ReservedBuffer {
+ WGPUBuffer buffer;
+ Handle handle;
+ Handle deviceHandle;
+};
+
struct ReservedTexture {
WGPUTexture texture;
Handle handle;
@@ -77,11 +83,13 @@
const volatile char* HandleCommands(const volatile char* commands, size_t size) override;
+ ReservedBuffer ReserveBuffer(WGPUDevice device, const WGPUBufferDescriptor* descriptor);
ReservedTexture ReserveTexture(WGPUDevice device, const WGPUTextureDescriptor* descriptor);
ReservedSwapChain ReserveSwapChain(WGPUDevice device,
const WGPUSwapChainDescriptor* descriptor);
ReservedInstance ReserveInstance(const WGPUInstanceDescriptor* descriptor = nullptr);
+ void ReclaimBufferReservation(const ReservedBuffer& reservation);
void ReclaimTextureReservation(const ReservedTexture& reservation);
void ReclaimSwapChainReservation(const ReservedSwapChain& reservation);
void ReclaimDeviceReservation(const ReservedDevice& reservation);
diff --git a/include/dawn/wire/WireServer.h b/include/dawn/wire/WireServer.h
index a3b9972..eb1a68a 100644
--- a/include/dawn/wire/WireServer.h
+++ b/include/dawn/wire/WireServer.h
@@ -54,6 +54,7 @@
const volatile char* HandleCommands(const volatile char* commands, size_t size) override;
+ bool InjectBuffer(WGPUBuffer buffer, const Handle& handle, const Handle& deviceHandle);
bool InjectTexture(WGPUTexture texture, const Handle& handle, const Handle& deviceHandle);
bool InjectSwapChain(WGPUSwapChain swapchain, const Handle& handle, const Handle& deviceHandle);
bool InjectInstance(WGPUInstance instance, const Handle& handle);
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index e858d83..6158b5d 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -437,6 +437,7 @@
"unittests/wire/WireExtensionTests.cpp",
"unittests/wire/WireFutureTest.cpp",
"unittests/wire/WireFutureTest.h",
+ "unittests/wire/WireInjectBufferTests.cpp",
"unittests/wire/WireInjectInstanceTests.cpp",
"unittests/wire/WireInjectSwapChainTests.cpp",
"unittests/wire/WireInjectTextureTests.cpp",
diff --git a/src/dawn/tests/unittests/wire/WireInjectBufferTests.cpp b/src/dawn/tests/unittests/wire/WireInjectBufferTests.cpp
new file mode 100644
index 0000000..f6abb29
--- /dev/null
+++ b/src/dawn/tests/unittests/wire/WireInjectBufferTests.cpp
@@ -0,0 +1,181 @@
+// Copyright 2024 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 "dawn/tests/unittests/wire/WireTest.h"
+
+#include "dawn/wire/WireClient.h"
+#include "dawn/wire/WireServer.h"
+
+namespace dawn::wire {
+namespace {
+
+using testing::Mock;
+using testing::Return;
+
+class WireInjectBufferTests : public WireTest {
+ public:
+ WireInjectBufferTests() {}
+ ~WireInjectBufferTests() override = default;
+
+ // A placeholder buffer format for ReserveBuffer. The data in it doesn't matter as long as
+ // we don't call buffer reflection methods.
+ WGPUBufferDescriptor placeholderDesc = {};
+};
+
+// Test that reserving and injecting a buffer makes calls on the client object forward to the
+// server object correctly.
+TEST_F(WireInjectBufferTests, CallAfterReserveInject) {
+ auto reserved = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+
+ WGPUBuffer apiBuffer = api.GetNewBuffer();
+ EXPECT_CALL(api, BufferAddRef(apiBuffer));
+ ASSERT_TRUE(GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+
+ wgpuBufferDestroy(reserved.buffer);
+ EXPECT_CALL(api, BufferDestroy(apiBuffer));
+ FlushClient();
+}
+
+// Test that reserve correctly returns different IDs each time.
+TEST_F(WireInjectBufferTests, ReserveDifferentIDs) {
+ auto reserved1 = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+ auto reserved2 = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+
+ ASSERT_NE(reserved1.handle.id, reserved2.handle.id);
+ ASSERT_NE(reserved1.buffer, reserved2.buffer);
+}
+
+// Test that injecting the same id without a destroy first fails.
+TEST_F(WireInjectBufferTests, InjectExistingID) {
+ auto reserved = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+
+ WGPUBuffer apiBuffer = api.GetNewBuffer();
+ EXPECT_CALL(api, BufferAddRef(apiBuffer));
+ ASSERT_TRUE(GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+
+ // ID already in use, call fails.
+ ASSERT_FALSE(GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+}
+
+// Test that injecting the same id without a destroy first fails.
+TEST_F(WireInjectBufferTests, ReuseIDAndGeneration) {
+ // Do this loop multiple times since the first time, we can't test `generation - 1` since
+ // generation == 0.
+ ReservedBuffer reserved;
+ WGPUBuffer apiBuffer = nullptr;
+ for (int i = 0; i < 2; ++i) {
+ reserved = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+
+ apiBuffer = api.GetNewBuffer();
+ EXPECT_CALL(api, BufferAddRef(apiBuffer));
+ ASSERT_TRUE(
+ GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+
+ // Release the buffer. It should be possible to reuse the ID now, but not the generation
+ wgpuBufferRelease(reserved.buffer);
+ EXPECT_CALL(api, BufferRelease(apiBuffer));
+ FlushClient();
+
+ // Invalid to inject with the same ID and generation.
+ ASSERT_FALSE(
+ GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+ if (i > 0) {
+ EXPECT_GE(reserved.handle.generation, 1u);
+
+ // Invalid to inject with the same ID and lesser generation.
+ reserved.handle.generation -= 1;
+ ASSERT_FALSE(
+ GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+ }
+ }
+
+ // Valid to inject with the same ID and greater generation.
+ EXPECT_CALL(api, BufferAddRef(apiBuffer));
+ reserved.handle.generation += 2;
+ ASSERT_TRUE(GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+}
+
+// Test that the server only borrows the buffer and does a single reference-release
+TEST_F(WireInjectBufferTests, InjectedBufferLifetime) {
+ auto reserved = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+
+ // Injecting the buffer adds a reference
+ WGPUBuffer apiBuffer = api.GetNewBuffer();
+ EXPECT_CALL(api, BufferAddRef(apiBuffer));
+ ASSERT_TRUE(GetWireServer()->InjectBuffer(apiBuffer, reserved.handle, reserved.deviceHandle));
+
+ // Releasing the buffer removes a single reference.
+ wgpuBufferRelease(reserved.buffer);
+ EXPECT_CALL(api, BufferRelease(apiBuffer));
+ FlushClient();
+
+ // Deleting the server doesn't release a second reference.
+ DeleteServer();
+ Mock::VerifyAndClearExpectations(&api);
+}
+
+// Test that a buffer reservation can be reclaimed. This is necessary to
+// avoid leaking ObjectIDs for reservations that are never injected.
+TEST_F(WireInjectBufferTests, ReclaimBufferReservation) {
+ // Test that doing a reservation and full release is an error.
+ {
+ auto reserved = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+ wgpuBufferRelease(reserved.buffer);
+ FlushClient(false);
+ }
+
+ // Test that doing a reservation and then reclaiming it recycles the ID.
+ {
+ auto reserved1 = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+ GetWireClient()->ReclaimBufferReservation(reserved1);
+
+ auto reserved2 = GetWireClient()->ReserveBuffer(device, &placeholderDesc);
+
+ // The ID is the same, but the generation is still different.
+ ASSERT_EQ(reserved1.handle.id, reserved2.handle.id);
+ ASSERT_NE(reserved1.handle.generation, reserved2.handle.generation);
+
+ // No errors should occur.
+ FlushClient();
+ }
+}
+
+// Test the reflection of buffer creation parameters for reserved buffer.
+TEST_F(WireInjectBufferTests, ReservedBufferReflection) {
+ WGPUBufferDescriptor desc = {};
+ desc.size = 10;
+ desc.usage = WGPUBufferUsage_Storage;
+
+ auto reserved = GetWireClient()->ReserveBuffer(device, &desc);
+ WGPUBuffer buffer = reserved.buffer;
+
+ ASSERT_EQ(desc.size, wgpuBufferGetSize(buffer));
+ ASSERT_EQ(desc.usage, wgpuBufferGetUsage(buffer));
+}
+
+} // anonymous namespace
+} // namespace dawn::wire
diff --git a/src/dawn/wire/WireClient.cpp b/src/dawn/wire/WireClient.cpp
index aa15aa2..e616232 100644
--- a/src/dawn/wire/WireClient.cpp
+++ b/src/dawn/wire/WireClient.cpp
@@ -41,6 +41,11 @@
return mImpl->HandleCommands(commands, size);
}
+ReservedBuffer WireClient::ReserveBuffer(WGPUDevice device,
+ const WGPUBufferDescriptor* descriptor) {
+ return mImpl->ReserveBuffer(device, descriptor);
+}
+
ReservedTexture WireClient::ReserveTexture(WGPUDevice device,
const WGPUTextureDescriptor* descriptor) {
return mImpl->ReserveTexture(device, descriptor);
@@ -55,6 +60,10 @@
return mImpl->ReserveInstance(descriptor);
}
+void WireClient::ReclaimBufferReservation(const ReservedBuffer& reservation) {
+ mImpl->ReclaimBufferReservation(reservation);
+}
+
void WireClient::ReclaimTextureReservation(const ReservedTexture& reservation) {
mImpl->ReclaimTextureReservation(reservation);
}
diff --git a/src/dawn/wire/WireServer.cpp b/src/dawn/wire/WireServer.cpp
index 9437e54..9f34ae0 100644
--- a/src/dawn/wire/WireServer.cpp
+++ b/src/dawn/wire/WireServer.cpp
@@ -43,6 +43,10 @@
return mImpl->HandleCommands(commands, size);
}
+bool WireServer::InjectBuffer(WGPUBuffer buffer, const Handle& handle, const Handle& deviceHandle) {
+ return mImpl->InjectBuffer(buffer, handle, deviceHandle) == WireResult::Success;
+}
+
bool WireServer::InjectTexture(WGPUTexture texture,
const Handle& handle,
const Handle& deviceHandle) {
diff --git a/src/dawn/wire/client/Client.cpp b/src/dawn/wire/client/Client.cpp
index 3d9aac3..3cc08d6 100644
--- a/src/dawn/wire/client/Client.cpp
+++ b/src/dawn/wire/client/Client.cpp
@@ -99,6 +99,16 @@
}
}
+ReservedBuffer Client::ReserveBuffer(WGPUDevice device, const WGPUBufferDescriptor* descriptor) {
+ Buffer* buffer = Make<Buffer>(FromAPI(device)->GetEventManagerHandle(), descriptor);
+
+ ReservedBuffer result;
+ result.buffer = ToAPI(buffer);
+ result.handle = buffer->GetWireHandle();
+ result.deviceHandle = FromAPI(device)->GetWireHandle();
+ return result;
+}
+
ReservedTexture Client::ReserveTexture(WGPUDevice device, const WGPUTextureDescriptor* descriptor) {
Texture* texture = Make<Texture>(descriptor);
@@ -137,6 +147,10 @@
return result;
}
+void Client::ReclaimBufferReservation(const ReservedBuffer& reservation) {
+ Free(FromAPI(reservation.buffer));
+}
+
void Client::ReclaimTextureReservation(const ReservedTexture& reservation) {
Free(FromAPI(reservation.texture));
}
diff --git a/src/dawn/wire/client/Client.h b/src/dawn/wire/client/Client.h
index 0140680..d389bd5 100644
--- a/src/dawn/wire/client/Client.h
+++ b/src/dawn/wire/client/Client.h
@@ -83,11 +83,13 @@
MemoryTransferService* GetMemoryTransferService() const { return mMemoryTransferService; }
+ ReservedBuffer ReserveBuffer(WGPUDevice device, const WGPUBufferDescriptor* descriptor);
ReservedTexture ReserveTexture(WGPUDevice device, const WGPUTextureDescriptor* descriptor);
ReservedSwapChain ReserveSwapChain(WGPUDevice device,
const WGPUSwapChainDescriptor* descriptor);
ReservedInstance ReserveInstance(const WGPUInstanceDescriptor* descriptor);
+ void ReclaimBufferReservation(const ReservedBuffer& reservation);
void ReclaimTextureReservation(const ReservedTexture& reservation);
void ReclaimSwapChainReservation(const ReservedSwapChain& reservation);
void ReclaimDeviceReservation(const ReservedDevice& reservation);
diff --git a/src/dawn/wire/server/Server.cpp b/src/dawn/wire/server/Server.cpp
index c835a3f..b363c19 100644
--- a/src/dawn/wire/server/Server.cpp
+++ b/src/dawn/wire/server/Server.cpp
@@ -61,6 +61,30 @@
DestroyAllObjects(mProcs);
}
+WireResult Server::InjectBuffer(WGPUBuffer buffer,
+ const Handle& handle,
+ const Handle& deviceHandle) {
+ DAWN_ASSERT(buffer != nullptr);
+ Known<WGPUDevice> device;
+ WIRE_TRY(Objects<WGPUDevice>().Get(deviceHandle.id, &device));
+ if (device->generation != deviceHandle.generation) {
+ return WireResult::FatalError;
+ }
+
+ Reserved<WGPUBuffer> data;
+ WIRE_TRY(Objects<WGPUBuffer>().Allocate(&data, handle));
+
+ data->handle = buffer;
+ data->generation = handle.generation;
+ data->state = AllocationState::Allocated;
+
+ // The Buffer 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.bufferAddRef(buffer);
+
+ return WireResult::Success;
+}
+
WireResult Server::InjectTexture(WGPUTexture texture,
const Handle& handle,
const Handle& deviceHandle) {
diff --git a/src/dawn/wire/server/Server.h b/src/dawn/wire/server/Server.h
index ba1b07f..2d29945 100644
--- a/src/dawn/wire/server/Server.h
+++ b/src/dawn/wire/server/Server.h
@@ -180,6 +180,7 @@
// ChunkedCommandHandler implementation
const volatile char* HandleCommandsImpl(const volatile char* commands, size_t size) override;
+ WireResult InjectBuffer(WGPUBuffer buffer, const Handle& handle, const Handle& deviceHandle);
WireResult InjectTexture(WGPUTexture texture, const Handle& handle, const Handle& deviceHandle);
WireResult InjectSwapChain(WGPUSwapChain swapchain,
const Handle& handle,