Implement Queue::OnSubmittedWorkDone
This is the replacement for Fence in the single-queue WebGPU world. To
keep this CL focused, it doesn't deprecate the fences yet.
Bug: chromium:1177476
Change-Id: I09d60732ec67bc1deb49f7a9d57699c049475acf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/41723
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index fd65fbd6..53bf294 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -122,6 +122,27 @@
return uploadHandle;
}
+ struct SubmittedWorkDone : QueueBase::TaskInFlight {
+ SubmittedWorkDone(WGPUQueueWorkDoneCallback callback, void* userdata)
+ : mCallback(callback), mUserdata(userdata) {
+ }
+ void Finish() override {
+ ASSERT(mCallback != nullptr);
+ mCallback(WGPUQueueWorkDoneStatus_Success, mUserdata);
+ mCallback = nullptr;
+ }
+ void HandleDeviceLoss() override {
+ ASSERT(mCallback != nullptr);
+ mCallback(WGPUQueueWorkDoneStatus_DeviceLost, mUserdata);
+ mCallback = nullptr;
+ }
+ ~SubmittedWorkDone() override = default;
+
+ private:
+ WGPUQueueWorkDoneCallback mCallback = nullptr;
+ void* mUserdata;
+ };
+
class ErrorQueue : public QueueBase {
public:
ErrorQueue(DeviceBase* device) : QueueBase(device, ObjectBase::kError) {
@@ -176,6 +197,26 @@
fence->UpdateFenceOnComplete(fence, signalValue);
}
+ void QueueBase::OnSubmittedWorkDone(uint64_t signalValue,
+ WGPUQueueWorkDoneCallback callback,
+ void* userdata) {
+ // The error status depends on the type of error so we let the validation function choose it
+ WGPUQueueWorkDoneStatus status;
+ if (GetDevice()->ConsumedError(ValidateOnSubmittedWorkDone(signalValue, &status))) {
+ callback(status, userdata);
+ return;
+ }
+
+ std::unique_ptr<SubmittedWorkDone> task =
+ std::make_unique<SubmittedWorkDone>(callback, userdata);
+
+ // Technically we only need to wait for previously submitted work but OnSubmittedWorkDone is
+ // also used to make sure ALL queue work is finished in tests, so we also wait for pending
+ // commands (this is non-observable outside of tests so it's ok to do deviate a bit from the
+ // spec).
+ TrackTask(std::move(task), GetDevice()->GetPendingCommandSerial());
+ }
+
void QueueBase::TrackTask(std::unique_ptr<TaskInFlight> task, ExecutionSerial serial) {
mTasksInFlight.Enqueue(std::move(task), serial);
GetDevice()->AddFutureSerial(serial);
@@ -387,6 +428,21 @@
return {};
}
+ MaybeError QueueBase::ValidateOnSubmittedWorkDone(uint64_t signalValue,
+ WGPUQueueWorkDoneStatus* status) const {
+ *status = WGPUQueueWorkDoneStatus_DeviceLost;
+ DAWN_TRY(GetDevice()->ValidateIsAlive());
+
+ *status = WGPUQueueWorkDoneStatus_Error;
+ DAWN_TRY(GetDevice()->ValidateObject(this));
+
+ if (signalValue != 0) {
+ return DAWN_VALIDATION_ERROR("SignalValue must currently be 0.");
+ }
+
+ return {};
+ }
+
MaybeError QueueBase::ValidateCreateFence(const FenceDescriptor* descriptor) const {
DAWN_TRY(GetDevice()->ValidateIsAlive());
DAWN_TRY(GetDevice()->ValidateObject(this));
diff --git a/src/dawn_native/Queue.h b/src/dawn_native/Queue.h
index a36d912..bdc2007 100644
--- a/src/dawn_native/Queue.h
+++ b/src/dawn_native/Queue.h
@@ -40,6 +40,9 @@
void Submit(uint32_t commandCount, CommandBufferBase* const* commands);
void Signal(Fence* fence, uint64_t signalValue);
Fence* CreateFence(const FenceDescriptor* descriptor);
+ void OnSubmittedWorkDone(uint64_t signalValue,
+ WGPUQueueWorkDoneCallback callback,
+ void* userdata);
void WriteBuffer(BufferBase* buffer, uint64_t bufferOffset, const void* data, size_t size);
void WriteTexture(const TextureCopyView* destination,
const void* data,
@@ -87,6 +90,8 @@
MaybeError ValidateSubmit(uint32_t commandCount, CommandBufferBase* const* commands) const;
MaybeError ValidateSignal(const Fence* fence, FenceAPISerial signalValue) const;
+ MaybeError ValidateOnSubmittedWorkDone(uint64_t signalValue,
+ WGPUQueueWorkDoneStatus* status) const;
MaybeError ValidateCreateFence(const FenceDescriptor* descriptor) const;
MaybeError ValidateWriteBuffer(const BufferBase* buffer,
uint64_t bufferOffset,
diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp
index a5ef7f6..2c6e23f 100644
--- a/src/dawn_wire/client/ClientDoers.cpp
+++ b/src/dawn_wire/client/ClientDoers.cpp
@@ -66,7 +66,6 @@
if (buffer == nullptr) {
return true;
}
-
return buffer->OnMapAsyncCallback(requestSerial, status, readInitialDataInfoLength,
readInitialDataInfo);
}
@@ -88,9 +87,17 @@
if (fence == nullptr) {
return true;
}
+ return fence->OnCompletionCallback(requestSerial, status);
+ }
- fence->OnCompletionCallback(requestSerial, status);
- return true;
+ bool Client::DoQueueWorkDoneCallback(Queue* queue,
+ uint64_t requestSerial,
+ WGPUQueueWorkDoneStatus status) {
+ // The queue might have been deleted or recreated so this isn't an error.
+ if (queue == nullptr) {
+ return true;
+ }
+ return queue->OnWorkDoneCallback(requestSerial, status);
}
bool Client::DoDeviceCreateComputePipelineAsyncCallback(Device* device,
diff --git a/src/dawn_wire/client/Fence.cpp b/src/dawn_wire/client/Fence.cpp
index d9a858a..563f13f1 100644
--- a/src/dawn_wire/client/Fence.cpp
+++ b/src/dawn_wire/client/Fence.cpp
@@ -46,7 +46,8 @@
WGPUFenceOnCompletionCallback callback,
void* userdata) {
if (client->IsDisconnected()) {
- return callback(WGPUFenceCompletionStatus_DeviceLost, userdata);
+ callback(WGPUFenceCompletionStatus_DeviceLost, userdata);
+ return;
}
uint32_t serial = mOnCompletionRequestSerial++;
diff --git a/src/dawn_wire/client/Queue.cpp b/src/dawn_wire/client/Queue.cpp
index f5f68f7..8c9f78b 100644
--- a/src/dawn_wire/client/Queue.cpp
+++ b/src/dawn_wire/client/Queue.cpp
@@ -19,6 +19,47 @@
namespace dawn_wire { namespace client {
+ Queue::~Queue() {
+ ClearAllCallbacks(WGPUQueueWorkDoneStatus_Unknown);
+ }
+
+ bool Queue::OnWorkDoneCallback(uint64_t requestSerial, WGPUQueueWorkDoneStatus status) {
+ auto requestIt = mOnWorkDoneRequests.find(requestSerial);
+ if (requestIt == mOnWorkDoneRequests.end()) {
+ return false;
+ }
+
+ // Remove the request data so that the callback cannot be called again.
+ // ex.) inside the callback: if the queue is deleted (when there are multiple queues),
+ // all callbacks reject.
+ OnWorkDoneData request = std::move(requestIt->second);
+ mOnWorkDoneRequests.erase(requestIt);
+
+ request.callback(status, request.userdata);
+ return true;
+ }
+
+ void Queue::OnSubmittedWorkDone(uint64_t signalValue,
+ WGPUQueueWorkDoneCallback callback,
+ void* userdata) {
+ if (client->IsDisconnected()) {
+ callback(WGPUQueueWorkDoneStatus_DeviceLost, userdata);
+ return;
+ }
+
+ uint32_t serial = mOnWorkDoneSerial++;
+ ASSERT(mOnWorkDoneRequests.find(serial) == mOnWorkDoneRequests.end());
+
+ QueueOnSubmittedWorkDoneCmd cmd;
+ cmd.queueId = this->id;
+ cmd.signalValue = signalValue;
+ cmd.requestSerial = serial;
+
+ mOnWorkDoneRequests[serial] = {callback, userdata};
+
+ client->SerializeCommand(cmd);
+ }
+
WGPUFence Queue::CreateFence(WGPUFenceDescriptor const* descriptor) {
auto* allocation = client->FenceAllocator().New(client);
@@ -65,4 +106,17 @@
client->SerializeCommand(cmd);
}
+ void Queue::CancelCallbacksForDisconnect() {
+ ClearAllCallbacks(WGPUQueueWorkDoneStatus_DeviceLost);
+ }
+
+ void Queue::ClearAllCallbacks(WGPUQueueWorkDoneStatus status) {
+ for (auto& it : mOnWorkDoneRequests) {
+ if (it.second.callback) {
+ it.second.callback(status, it.second.userdata);
+ }
+ }
+ mOnWorkDoneRequests.clear();
+ }
+
}} // namespace dawn_wire::client
diff --git a/src/dawn_wire/client/Queue.h b/src/dawn_wire/client/Queue.h
index 91c9393..f14fae1 100644
--- a/src/dawn_wire/client/Queue.h
+++ b/src/dawn_wire/client/Queue.h
@@ -27,7 +27,14 @@
class Queue final : public ObjectBase {
public:
using ObjectBase::ObjectBase;
+ ~Queue();
+ bool OnWorkDoneCallback(uint64_t requestSerial, WGPUQueueWorkDoneStatus status);
+
+ // Dawn API
+ void OnSubmittedWorkDone(uint64_t signalValue,
+ WGPUQueueWorkDoneCallback callback,
+ void* userdata);
WGPUFence CreateFence(const WGPUFenceDescriptor* descriptor);
void WriteBuffer(WGPUBuffer cBuffer, uint64_t bufferOffset, const void* data, size_t size);
void WriteTexture(const WGPUTextureCopyView* destination,
@@ -35,6 +42,18 @@
size_t dataSize,
const WGPUTextureDataLayout* dataLayout,
const WGPUExtent3D* writeSize);
+
+ private:
+ void CancelCallbacksForDisconnect() override;
+
+ void ClearAllCallbacks(WGPUQueueWorkDoneStatus status);
+
+ struct OnWorkDoneData {
+ WGPUQueueWorkDoneCallback callback = nullptr;
+ void* userdata = nullptr;
+ };
+ uint64_t mOnWorkDoneSerial = 0;
+ std::map<uint64_t, OnWorkDoneData> mOnWorkDoneRequests;
};
}} // namespace dawn_wire::client
diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h
index bcedeaa..1124cba 100644
--- a/src/dawn_wire/server/Server.h
+++ b/src/dawn_wire/server/Server.h
@@ -141,6 +141,13 @@
uint64_t requestSerial;
};
+ struct QueueWorkDoneUserdata : CallbackUserdata {
+ using CallbackUserdata::CallbackUserdata;
+
+ ObjectHandle queue;
+ uint64_t requestSerial;
+ };
+
struct CreatePipelineAsyncUserData : CallbackUserdata {
using CallbackUserdata::CallbackUserdata;
@@ -202,6 +209,7 @@
FenceCompletionUserdata* userdata);
void OnFenceOnCompletion(WGPUFenceCompletionStatus status,
FenceOnCompletionUserdata* userdata);
+ void OnQueueWorkDone(WGPUQueueWorkDoneStatus status, QueueWorkDoneUserdata* userdata);
void OnCreateComputePipelineAsyncCallback(WGPUCreatePipelineAsyncStatus status,
WGPUComputePipeline pipeline,
const char* message,
diff --git a/src/dawn_wire/server/ServerQueue.cpp b/src/dawn_wire/server/ServerQueue.cpp
index d798d41..9ab8bc0 100644
--- a/src/dawn_wire/server/ServerQueue.cpp
+++ b/src/dawn_wire/server/ServerQueue.cpp
@@ -40,6 +40,34 @@
return true;
}
+ void Server::OnQueueWorkDone(WGPUQueueWorkDoneStatus status, QueueWorkDoneUserdata* data) {
+ ReturnQueueWorkDoneCallbackCmd cmd;
+ cmd.queue = data->queue;
+ cmd.requestSerial = data->requestSerial;
+ cmd.status = status;
+
+ SerializeCommand(cmd);
+ }
+
+ bool Server::DoQueueOnSubmittedWorkDone(ObjectId queueId,
+ uint64_t signalValue,
+ uint64_t requestSerial) {
+ auto* queue = QueueObjects().Get(queueId);
+ if (queue == nullptr) {
+ return false;
+ }
+
+ auto userdata = MakeUserdata<QueueWorkDoneUserdata>();
+ userdata->queue = ObjectHandle{queueId, queue->generation};
+ userdata->requestSerial = requestSerial;
+
+ mProcs.queueOnSubmittedWorkDone(
+ queue->handle, signalValue,
+ ForwardToServer<decltype(&Server::OnQueueWorkDone)>::Func<&Server::OnQueueWorkDone>(),
+ userdata.release());
+ return true;
+ }
+
bool Server::DoQueueWriteBufferInternal(ObjectId queueId,
ObjectId bufferId,
uint64_t bufferOffset,
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 0c3ffc3..a0baaf2 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -198,6 +198,7 @@
"unittests/validation/MinimumBufferSizeValidationTests.cpp",
"unittests/validation/MultipleDeviceTests.cpp",
"unittests/validation/QueryValidationTests.cpp",
+ "unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp",
"unittests/validation/QueueSubmitValidationTests.cpp",
"unittests/validation/QueueWriteBufferValidationTests.cpp",
"unittests/validation/QueueWriteTextureValidationTests.cpp",
@@ -231,6 +232,7 @@
"unittests/wire/WireInjectTextureTests.cpp",
"unittests/wire/WireMemoryTransferServiceTests.cpp",
"unittests/wire/WireOptionalTests.cpp",
+ "unittests/wire/WireQueueTests.cpp",
"unittests/wire/WireTest.cpp",
"unittests/wire/WireTest.h",
"unittests/wire/WireWGPUDevicePropertiesTests.cpp",
diff --git a/src/tests/end2end/DeviceLostTests.cpp b/src/tests/end2end/DeviceLostTests.cpp
index a15ba94..1e7b539 100644
--- a/src/tests/end2end/DeviceLostTests.cpp
+++ b/src/tests/end2end/DeviceLostTests.cpp
@@ -52,6 +52,16 @@
mockFenceOnCompletionCallback = nullptr;
}
+class MockQueueWorkDoneCallback {
+ public:
+ MOCK_METHOD(void, Call, (WGPUQueueWorkDoneStatus status, void* userdata));
+};
+
+static std::unique_ptr<MockQueueWorkDoneCallback> mockQueueWorkDoneCallback;
+static void ToMockQueueWorkDone(WGPUQueueWorkDoneStatus status, void* userdata) {
+ mockQueueWorkDoneCallback->Call(status, userdata);
+}
+
static const int fakeUserData = 0;
class DeviceLostTest : public DawnTest {
@@ -61,11 +71,13 @@
DAWN_SKIP_TEST_IF(UsesWire());
mockDeviceLostCallback = std::make_unique<MockDeviceLostCallback>();
mockFenceOnCompletionCallback = std::make_unique<MockFenceOnCompletionCallback>();
+ mockQueueWorkDoneCallback = std::make_unique<MockQueueWorkDoneCallback>();
}
void TearDown() override {
mockDeviceLostCallback = nullptr;
mockFenceOnCompletionCallback = nullptr;
+ mockQueueWorkDoneCallback = nullptr;
DawnTest::TearDown();
}
@@ -473,6 +485,28 @@
EXPECT_EQ(fence.GetCompletedValue(), 2u);
}
+// Test that QueueOnSubmittedWorkDone fails after device is lost.
+TEST_P(DeviceLostTest, QueueOnSubmittedWorkDoneFails) {
+ SetCallbackAndLoseForTesting();
+
+ // callback should have device lost status
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, nullptr))
+ .Times(1);
+ ASSERT_DEVICE_ERROR(queue.OnSubmittedWorkDone(0, ToMockQueueWorkDone, nullptr));
+ ASSERT_DEVICE_ERROR(device.Tick());
+}
+
+// Test that QueueOnSubmittedWorkDone when the device is lost after calling OnSubmittedWorkDone
+TEST_P(DeviceLostTest, QueueOnSubmittedWorkDoneBeforeLossFails) {
+ // callback should have device lost status
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, nullptr))
+ .Times(1);
+ queue.OnSubmittedWorkDone(0, ToMockQueueWorkDone, nullptr);
+
+ SetCallbackAndLoseForTesting();
+ ASSERT_DEVICE_ERROR(device.Tick());
+}
+
// Regression test for the Null backend not properly setting the completedSerial when
// WaitForIdleForDestruction is called, causing the fence signaling to not be retired and an
// ASSERT to fire.
diff --git a/src/tests/end2end/QueueTimelineTests.cpp b/src/tests/end2end/QueueTimelineTests.cpp
index d125b18..823a667 100644
--- a/src/tests/end2end/QueueTimelineTests.cpp
+++ b/src/tests/end2end/QueueTimelineTests.cpp
@@ -39,6 +39,16 @@
mockFenceOnCompletionCallback->Call(status, userdata);
}
+class MockQueueWorkDoneCallback {
+ public:
+ MOCK_METHOD(void, Call, (WGPUQueueWorkDoneStatus status, void* userdata));
+};
+
+static std::unique_ptr<MockQueueWorkDoneCallback> mockQueueWorkDoneCallback;
+static void ToMockQueueWorkDone(WGPUQueueWorkDoneStatus status, void* userdata) {
+ mockQueueWorkDoneCallback->Call(status, userdata);
+}
+
class QueueTimelineTests : public DawnTest {
protected:
void SetUp() override {
@@ -46,6 +56,7 @@
mockMapCallback = std::make_unique<MockMapCallback>();
mockFenceOnCompletionCallback = std::make_unique<MockFenceOnCompletionCallback>();
+ mockQueueWorkDoneCallback = std::make_unique<MockQueueWorkDoneCallback>();
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
@@ -54,8 +65,9 @@
}
void TearDown() override {
- mockFenceOnCompletionCallback = nullptr;
mockMapCallback = nullptr;
+ mockFenceOnCompletionCallback = nullptr;
+ mockQueueWorkDoneCallback = nullptr;
DawnTest::TearDown();
}
@@ -82,8 +94,24 @@
mMapReadBuffer.Unmap();
}
+// Test that mMapReadBuffer.MapAsync callback happens before queue.OnWorkDone callback
+// when queue.OnSubmittedWorkDone is called after mMapReadBuffer.MapAsync. The callback order should
+// happen in the order the functions are called.
+TEST_P(QueueTimelineTests, MapRead_OnWorkDone) {
+ testing::InSequence sequence;
+ EXPECT_CALL(*mockMapCallback, Call(WGPUBufferMapAsyncStatus_Success, this)).Times(1);
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Success, this)).Times(1);
+
+ mMapReadBuffer.MapAsync(wgpu::MapMode::Read, 0, 0, ToMockMapCallback, this);
+
+ queue.OnSubmittedWorkDone(0u, ToMockQueueWorkDone, this);
+
+ WaitForAllOperations();
+ mMapReadBuffer.Unmap();
+}
+
// Test that fence.OnCompletion callback happens before mMapReadBuffer.MapAsync callback when
-// queue.Signal is called before mMapReadBuffer.MapAsync. The callback order should
+// queue.OnSubmittedWorkDone is called before mMapReadBuffer.MapAsync. The callback order should
// happen in the order the functions are called.
TEST_P(QueueTimelineTests, SignalMapReadOnComplete) {
testing::InSequence sequence;
@@ -101,6 +129,22 @@
mMapReadBuffer.Unmap();
}
+// Test that queue.OnWorkDone callback happens before mMapReadBuffer.MapAsync callback when
+// queue.Signal is called before mMapReadBuffer.MapAsync. The callback order should
+// happen in the order the functions are called.
+TEST_P(QueueTimelineTests, OnWorkDone_MapRead) {
+ testing::InSequence sequence;
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Success, this)).Times(1);
+ EXPECT_CALL(*mockMapCallback, Call(WGPUBufferMapAsyncStatus_Success, this)).Times(1);
+
+ queue.OnSubmittedWorkDone(0u, ToMockQueueWorkDone, this);
+
+ mMapReadBuffer.MapAsync(wgpu::MapMode::Read, 0, 0, ToMockMapCallback, this);
+
+ WaitForAllOperations();
+ mMapReadBuffer.Unmap();
+}
+
// Test that fence.OnCompletion callback happens before mMapReadBuffer.MapAsync callback when
// queue.Signal is called before mMapReadBuffer.MapAsync. The callback order should
// happen in the order the functions are called
@@ -121,6 +165,7 @@
mMapReadBuffer.Unmap();
}
+// Test a complicated case with many signals surrounding a buffer mapping.
TEST_P(QueueTimelineTests, SurroundWithFenceSignals) {
testing::InSequence sequence;
EXPECT_CALL(*mockFenceOnCompletionCallback, Call(WGPUFenceCompletionStatus_Success, this + 0))
diff --git a/src/tests/unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp b/src/tests/unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp
new file mode 100644
index 0000000..0b2fc9a
--- /dev/null
+++ b/src/tests/unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp
@@ -0,0 +1,58 @@
+// 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/validation/ValidationTest.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+
+class MockQueueWorkDoneCallback {
+ public:
+ MOCK_METHOD(void, Call, (WGPUQueueWorkDoneStatus status, void* userdata));
+};
+
+static std::unique_ptr<MockQueueWorkDoneCallback> mockQueueWorkDoneCallback;
+static void ToMockQueueWorkDone(WGPUQueueWorkDoneStatus status, void* userdata) {
+ mockQueueWorkDoneCallback->Call(status, userdata);
+}
+
+class QueueOnSubmittedWorkDoneValidationTests : public ValidationTest {
+ protected:
+ void SetUp() override {
+ ValidationTest::SetUp();
+ mockQueueWorkDoneCallback = std::make_unique<MockQueueWorkDoneCallback>();
+ }
+
+ void TearDown() override {
+ mockQueueWorkDoneCallback = nullptr;
+ ValidationTest::TearDown();
+ }
+};
+
+// Test that OnSubmittedWorkDone can be called as soon as the queue is created.
+TEST_F(QueueOnSubmittedWorkDoneValidationTests, CallBeforeSubmits) {
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Success, this)).Times(1);
+ device.GetQueue().OnSubmittedWorkDone(0u, ToMockQueueWorkDone, this);
+
+ WaitForAllOperations(device);
+}
+
+// Test that OnSubmittedWorkDone is an error if signalValue isn't 0.
+TEST_F(QueueOnSubmittedWorkDoneValidationTests, SignaledValueNotZeroIsInvalid) {
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Error, this)).Times(1);
+ ASSERT_DEVICE_ERROR(device.GetQueue().OnSubmittedWorkDone(1u, ToMockQueueWorkDone, this));
+
+ WaitForAllOperations(device);
+}
diff --git a/src/tests/unittests/wire/WireQueueTests.cpp b/src/tests/unittests/wire/WireQueueTests.cpp
new file mode 100644
index 0000000..0dd340c
--- /dev/null
+++ b/src/tests/unittests/wire/WireQueueTests.cpp
@@ -0,0 +1,99 @@
+// 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"
+
+using namespace testing;
+using namespace dawn_wire;
+
+class MockQueueWorkDoneCallback {
+ public:
+ MOCK_METHOD(void, Call, (WGPUQueueWorkDoneStatus status, void* userdata));
+};
+
+static std::unique_ptr<MockQueueWorkDoneCallback> mockQueueWorkDoneCallback;
+static void ToMockQueueWorkDone(WGPUQueueWorkDoneStatus status, void* userdata) {
+ mockQueueWorkDoneCallback->Call(status, userdata);
+}
+
+class WireQueueTests : public WireTest {
+ protected:
+ void SetUp() override {
+ WireTest::SetUp();
+ mockQueueWorkDoneCallback = std::make_unique<MockQueueWorkDoneCallback>();
+ }
+
+ void TearDown() override {
+ mockQueueWorkDoneCallback = nullptr;
+ WireTest::TearDown();
+ }
+
+ void FlushServer() {
+ WireTest::FlushServer();
+ Mock::VerifyAndClearExpectations(&mockQueueWorkDoneCallback);
+ }
+};
+
+// Test that a successful OnSubmittedWorkDone call is forwarded to the client.
+TEST_F(WireQueueTests, OnSubmittedWorkDoneSuccess) {
+ wgpuQueueOnSubmittedWorkDone(queue, 0u, ToMockQueueWorkDone, this);
+ EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, 0u, _, _))
+ .WillOnce(InvokeWithoutArgs([&]() {
+ api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Success);
+ }));
+ FlushClient();
+
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Success, this)).Times(1);
+ FlushServer();
+}
+
+// Test that an error OnSubmittedWorkDone call is forwarded as an error to the client.
+TEST_F(WireQueueTests, OnSubmittedWorkDoneError) {
+ wgpuQueueOnSubmittedWorkDone(queue, 0u, ToMockQueueWorkDone, this);
+ EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, 0u, _, _))
+ .WillOnce(InvokeWithoutArgs([&]() {
+ api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Error);
+ }));
+ FlushClient();
+
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Error, this)).Times(1);
+ FlushServer();
+}
+
+// Test registering an OnSubmittedWorkDone then disconnecting the wire calls the callback with
+// device loss
+TEST_F(WireQueueTests, OnSubmittedWorkDoneBeforeDisconnect) {
+ wgpuQueueOnSubmittedWorkDone(queue, 0u, ToMockQueueWorkDone, this);
+ EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, 0u, _, _))
+ .WillOnce(InvokeWithoutArgs([&]() {
+ api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Error);
+ }));
+ FlushClient();
+
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, this))
+ .Times(1);
+ GetWireClient()->Disconnect();
+}
+
+// Test registering an OnSubmittedWorkDone after disconnecting the wire calls the callback with
+// device loss
+TEST_F(WireQueueTests, OnSubmittedWorkDoneAfterDisconnect) {
+ GetWireClient()->Disconnect();
+
+ EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, this))
+ .Times(1);
+ wgpuQueueOnSubmittedWorkDone(queue, 0u, ToMockQueueWorkDone, this);
+}