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>
This commit is contained in:
Corentin Wallez 2021-02-25 13:17:01 +00:00 committed by Commit Bot service account
parent 0a295c027d
commit c093db250e
15 changed files with 457 additions and 6 deletions

View File

@ -1126,6 +1126,14 @@
{"name": "descriptor", "type": "fence descriptor", "annotation": "const*", "optional": true}
]
},
{
"name": "on submitted work done",
"args": [
{"name": "signal value", "type": "uint64_t"},
{"name": "callback", "type": "queue work done callback"},
{"name": "userdata", "type": "void", "annotation": "*"}
]
},
{
"name": "write buffer",
"args": [
@ -1157,6 +1165,22 @@
}
]
},
"queue work done callback": {
"category": "callback",
"args": [
{"name": "status", "type": "queue work done status"},
{"name": "userdata", "type": "void", "annotation": "*"}
]
},
"queue work done status": {
"category": "enum",
"values": [
{"value": 0, "name": "success"},
{"value": 1, "name": "error"},
{"value": 2, "name": "unknown"},
{"value": 3, "name": "device lost"}
]
},
"rasterization state descriptor": {
"category": "structure",

View File

@ -61,6 +61,11 @@
{ "name": "value", "type": "uint64_t" },
{ "name": "request serial", "type": "uint64_t" }
],
"queue on submitted work done": [
{ "name": "queue id", "type": "ObjectId" },
{ "name": "signal value", "type": "uint64_t" },
{ "name": "request serial", "type": "uint64_t" }
],
"queue write buffer internal": [
{"name": "queue id", "type": "ObjectId" },
{"name": "buffer id", "type": "ObjectId" },
@ -120,6 +125,11 @@
"fence update completed value": [
{ "name": "fence", "type": "ObjectHandle", "handle_type": "fence" },
{ "name": "value", "type": "uint64_t" }
],
"queue work done callback": [
{ "name": "queue", "type": "ObjectHandle", "handle_type": "queue" },
{ "name": "request serial", "type": "uint64_t" },
{ "name": "status", "type": "queue work done status" }
]
},
"special items": {
@ -140,6 +150,7 @@
"DeviceSetUncapturedErrorCallback",
"FenceGetCompletedValue",
"FenceOnCompletion",
"QueueOnSubmittedWorkDone",
"QueueWriteBuffer",
"QueueWriteTexture"
],

View File

@ -122,6 +122,27 @@ namespace dawn_native {
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 @@ namespace dawn_native {
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 @@ namespace dawn_native {
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));

View File

@ -40,6 +40,9 @@ namespace dawn_native {
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 @@ namespace dawn_native {
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,

View File

@ -66,7 +66,6 @@ namespace dawn_wire { namespace client {
if (buffer == nullptr) {
return true;
}
return buffer->OnMapAsyncCallback(requestSerial, status, readInitialDataInfoLength,
readInitialDataInfo);
}
@ -88,9 +87,17 @@ namespace dawn_wire { namespace client {
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,

View File

@ -46,7 +46,8 @@ namespace dawn_wire { namespace client {
WGPUFenceOnCompletionCallback callback,
void* userdata) {
if (client->IsDisconnected()) {
return callback(WGPUFenceCompletionStatus_DeviceLost, userdata);
callback(WGPUFenceCompletionStatus_DeviceLost, userdata);
return;
}
uint32_t serial = mOnCompletionRequestSerial++;

View File

@ -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 @@ namespace dawn_wire { namespace client {
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

View File

@ -27,7 +27,14 @@ namespace dawn_wire { namespace client {
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 @@ namespace dawn_wire { namespace client {
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

View File

@ -141,6 +141,13 @@ namespace dawn_wire { namespace server {
uint64_t requestSerial;
};
struct QueueWorkDoneUserdata : CallbackUserdata {
using CallbackUserdata::CallbackUserdata;
ObjectHandle queue;
uint64_t requestSerial;
};
struct CreatePipelineAsyncUserData : CallbackUserdata {
using CallbackUserdata::CallbackUserdata;
@ -202,6 +209,7 @@ namespace dawn_wire { namespace server {
FenceCompletionUserdata* userdata);
void OnFenceOnCompletion(WGPUFenceCompletionStatus status,
FenceOnCompletionUserdata* userdata);
void OnQueueWorkDone(WGPUQueueWorkDoneStatus status, QueueWorkDoneUserdata* userdata);
void OnCreateComputePipelineAsyncCallback(WGPUCreatePipelineAsyncStatus status,
WGPUComputePipeline pipeline,
const char* message,

View File

@ -40,6 +40,34 @@ namespace dawn_wire { namespace server {
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,

View File

@ -198,6 +198,7 @@ test("dawn_unittests") {
"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 @@ test("dawn_unittests") {
"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",

View File

@ -52,6 +52,16 @@ static void ToMockFenceOnCompletionSucceeds(WGPUFenceCompletionStatus status, vo
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 @@ class DeviceLostTest : public DawnTest {
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 @@ TEST_P(DeviceLostTest, FenceOnCompletionBeforeLossFails) {
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.

View File

@ -39,6 +39,16 @@ static void ToMockFenceOnCompletion(WGPUFenceCompletionStatus status, void* user
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 @@ class QueueTimelineTests : public DawnTest {
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 @@ class QueueTimelineTests : public DawnTest {
}
void TearDown() override {
mockFenceOnCompletionCallback = nullptr;
mockMapCallback = nullptr;
mockFenceOnCompletionCallback = nullptr;
mockQueueWorkDoneCallback = nullptr;
DawnTest::TearDown();
}
@ -82,8 +94,24 @@ TEST_P(QueueTimelineTests, MapReadSignalOnComplete) {
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 @@ TEST_P(QueueTimelineTests, SignalMapReadOnComplete) {
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 @@ TEST_P(QueueTimelineTests, SignalOnCompleteMapRead) {
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))

View File

@ -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);
}

View File

@ -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);
}