dawn_wire: Add Reserve/InjectDevice

Now that the wire does enough tracking to prevent a malicious client
from freeing a device before its child objects, and the device is no
longer a "special" object with regard to reference/release, it is
safe to support multiple devices on the wire. The simplest way to
use this in WebGPU (to fix createReadyRenderPipeline validation)
is to add a reserve/inject device API similar to the one we use for
swapchain textures.

Bug: dawn:565
Change-Id: Ie956aff528c5610c9ecc5c189dab2d22185cb572
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/37800
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
This commit is contained in:
Austin Eng
2021-01-19 19:27:52 +00:00
committed by Commit Bot service account
parent b830da7d6e
commit 8bcde8e394
18 changed files with 377 additions and 45 deletions

View File

@@ -223,6 +223,7 @@ test("dawn_unittests") {
"unittests/wire/WireErrorCallbackTests.cpp",
"unittests/wire/WireExtensionTests.cpp",
"unittests/wire/WireFenceTests.cpp",
"unittests/wire/WireInjectDeviceTests.cpp",
"unittests/wire/WireInjectTextureTests.cpp",
"unittests/wire/WireMemoryTransferServiceTests.cpp",
"unittests/wire/WireMultipleDeviceTests.cpp",

View File

@@ -36,10 +36,19 @@ TEST_F(WireDestroyObjectTests, DestroyDeviceDestroysChildren) {
// The device and child objects should be released.
EXPECT_CALL(api, CommandEncoderRelease(apiEncoder)).InSequence(s1);
EXPECT_CALL(api, QueueRelease(apiQueue)).InSequence(s2);
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, DeviceRelease(apiDevice)).InSequence(s1, s2);
FlushClient();
// Signal that we already released and cleared callbacks for |apiDevice|
DefaultApiDeviceWasReleased();
// Using the command encoder should be an error.
wgpuCommandEncoderFinish(encoder, nullptr);
FlushClient(false);
@@ -82,8 +91,17 @@ TEST_F(WireDestroyObjectTests, ImplicitInjectErrorAfterDestroyDevice) {
// The device and child objects alre also released.
EXPECT_CALL(api, BufferRelease(apiBuffer)).InSequence(s1);
EXPECT_CALL(api, QueueRelease(apiQueue)).InSequence(s2);
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, DeviceRelease(apiDevice)).InSequence(s1, s2);
FlushClient();
// Signal that we already released and cleared callbacks for |apiDevice|
DefaultApiDeviceWasReleased();
}
}

View File

@@ -149,6 +149,15 @@ TEST_F(WireDisconnectTests, DeleteClientDestroysObjects) {
EXPECT_CALL(api, QueueRelease(apiQueue)).Times(1).InSequence(s1);
EXPECT_CALL(api, CommandEncoderRelease(apiCommandEncoder)).Times(1).InSequence(s2);
EXPECT_CALL(api, SamplerRelease(apiSampler)).Times(1).InSequence(s3);
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, DeviceRelease(apiDevice)).Times(1).InSequence(s1, s2, s3);
FlushClient();
// Signal that we already released and cleared callbacks for |apiDevice|
DefaultApiDeviceWasReleased();
}

View File

@@ -0,0 +1,184 @@
// 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 WireInjectDeviceTests : public WireTest {
public:
WireInjectDeviceTests() {
}
~WireInjectDeviceTests() override = default;
};
// Test that reserving and injecting a device makes calls on the client object forward to the
// server object correctly.
TEST_F(WireInjectDeviceTests, CallAfterReserveInject) {
ReservedDevice reservation = GetWireClient()->ReserveDevice();
WGPUDevice serverDevice = api.GetNewDevice();
EXPECT_CALL(api, DeviceReference(serverDevice));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
ASSERT_TRUE(
GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
WGPUBufferDescriptor bufferDesc = {};
wgpuDeviceCreateBuffer(reservation.device, &bufferDesc);
WGPUBuffer serverBuffer = api.GetNewBuffer();
EXPECT_CALL(api, DeviceCreateBuffer(serverDevice, _)).WillOnce(Return(serverBuffer));
FlushClient();
// Called on shutdown.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr))
.Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr))
.Times(Exactly(1));
}
// Test that reserve correctly returns different IDs each time.
TEST_F(WireInjectDeviceTests, ReserveDifferentIDs) {
ReservedDevice reservation1 = GetWireClient()->ReserveDevice();
ReservedDevice reservation2 = GetWireClient()->ReserveDevice();
ASSERT_NE(reservation1.id, reservation2.id);
ASSERT_NE(reservation1.device, reservation2.device);
}
// Test that injecting the same id without a destroy first fails.
TEST_F(WireInjectDeviceTests, InjectExistingID) {
ReservedDevice reservation = GetWireClient()->ReserveDevice();
WGPUDevice serverDevice = api.GetNewDevice();
EXPECT_CALL(api, DeviceReference(serverDevice));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
ASSERT_TRUE(
GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
// ID already in use, call fails.
ASSERT_FALSE(
GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
// Called on shutdown.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr))
.Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr))
.Times(Exactly(1));
}
// Test that the server only borrows the device and does a single reference-release
TEST_F(WireInjectDeviceTests, InjectedDeviceLifetime) {
ReservedDevice reservation = GetWireClient()->ReserveDevice();
// Injecting the device adds a reference
WGPUDevice serverDevice = api.GetNewDevice();
EXPECT_CALL(api, DeviceReference(serverDevice));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
ASSERT_TRUE(
GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
// Releasing the device removes a single reference and clears its error callbacks.
wgpuDeviceRelease(reservation.device);
EXPECT_CALL(api, DeviceRelease(serverDevice));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr)).Times(1);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr)).Times(1);
FlushClient();
// Deleting the server doesn't release a second reference.
DeleteServer();
Mock::VerifyAndClearExpectations(&api);
}
// Test that it is an error to get the default queue of a device before it has been
// injected on the server.
TEST_F(WireInjectDeviceTests, GetQueueBeforeInject) {
ReservedDevice reservation = GetWireClient()->ReserveDevice();
wgpuDeviceGetDefaultQueue(reservation.device);
FlushClient(false);
}
// Test that it is valid to get the default queue of a device after it has been
// injected on the server.
TEST_F(WireInjectDeviceTests, GetQueueAfterInject) {
ReservedDevice reservation = GetWireClient()->ReserveDevice();
WGPUDevice serverDevice = api.GetNewDevice();
EXPECT_CALL(api, DeviceReference(serverDevice));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _));
ASSERT_TRUE(
GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation));
wgpuDeviceGetDefaultQueue(reservation.device);
WGPUQueue apiQueue = api.GetNewQueue();
EXPECT_CALL(api, DeviceGetDefaultQueue(serverDevice)).WillOnce(Return(apiQueue));
FlushClient();
// Called on shutdown.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr))
.Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr))
.Times(Exactly(1));
}
// Test that the list of live devices can be reflected using GetDevice.
TEST_F(WireInjectDeviceTests, ReflectLiveDevices) {
// Reserve two devices.
ReservedDevice reservation1 = GetWireClient()->ReserveDevice();
ReservedDevice reservation2 = GetWireClient()->ReserveDevice();
// Inject both devices.
WGPUDevice serverDevice1 = api.GetNewDevice();
EXPECT_CALL(api, DeviceReference(serverDevice1));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, _, _));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, _, _));
ASSERT_TRUE(
GetWireServer()->InjectDevice(serverDevice1, reservation1.id, reservation1.generation));
WGPUDevice serverDevice2 = api.GetNewDevice();
EXPECT_CALL(api, DeviceReference(serverDevice2));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, _, _));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, _, _));
ASSERT_TRUE(
GetWireServer()->InjectDevice(serverDevice2, reservation2.id, reservation2.generation));
// Test that both devices can be reflected.
ASSERT_EQ(serverDevice1, GetWireServer()->GetDevice(reservation1.id, reservation1.generation));
ASSERT_EQ(serverDevice2, GetWireServer()->GetDevice(reservation2.id, reservation2.generation));
// Release the first device
wgpuDeviceRelease(reservation1.device);
EXPECT_CALL(api, DeviceRelease(serverDevice1));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, nullptr, nullptr)).Times(1);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, nullptr, nullptr)).Times(1);
FlushClient();
// The first device should no longer reflect, but the second should
ASSERT_EQ(nullptr, GetWireServer()->GetDevice(reservation1.id, reservation1.generation));
ASSERT_EQ(serverDevice2, GetWireServer()->GetDevice(reservation2.id, reservation2.generation));
// Called on shutdown.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, nullptr, nullptr)).Times(1);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, nullptr, nullptr)).Times(1);
}

View File

@@ -83,9 +83,10 @@ class WireMultipleDeviceTests : public testing::Test {
// These are called on server destruction to clear the callbacks. They must not be
// called after the server is destroyed.
EXPECT_CALL(mApi, OnDeviceSetUncapturedErrorCallback(_, nullptr, nullptr))
EXPECT_CALL(mApi, OnDeviceSetUncapturedErrorCallback(mServerDevice, nullptr, nullptr))
.Times(Exactly(1));
EXPECT_CALL(mApi, OnDeviceSetDeviceLostCallback(mServerDevice, nullptr, nullptr))
.Times(Exactly(1));
EXPECT_CALL(mApi, OnDeviceSetDeviceLostCallback(_, nullptr, nullptr)).Times(Exactly(1));
mWireServer = nullptr;
}

View File

@@ -88,15 +88,23 @@ void WireTest::TearDown() {
api.IgnoreAllReleaseCalls();
mWireClient = nullptr;
if (mWireServer) {
if (mWireServer && apiDevice) {
// These are called on server destruction to clear the callbacks. They must not be
// called after the server is destroyed.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(_, nullptr, nullptr)).Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(_, nullptr, nullptr)).Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
.Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
.Times(Exactly(1));
}
mWireServer = nullptr;
}
// This should be called if |apiDevice| is no longer exists on the wire.
// This signals that expectations in |TearDowb| shouldn't be added.
void WireTest::DefaultApiDeviceWasReleased() {
apiDevice = nullptr;
}
void WireTest::FlushClient(bool success) {
ASSERT_EQ(mC2sBuf->Flush(), success);
@@ -123,8 +131,10 @@ void WireTest::DeleteServer() {
if (mWireServer) {
// These are called on server destruction to clear the callbacks. They must not be
// called after the server is destroyed.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(_, nullptr, nullptr)).Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(_, nullptr, nullptr)).Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
.Times(Exactly(1));
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
.Times(Exactly(1));
}
mWireServer = nullptr;
}

View File

@@ -123,6 +123,8 @@ class WireTest : public testing::Test {
void FlushClient(bool success = true);
void FlushServer(bool success = true);
void DefaultApiDeviceWasReleased();
testing::StrictMock<MockProcTable> api;
WGPUDevice apiDevice;
WGPUQueue apiQueue;