From c093db250e26012fd478dffcd93744800308e529 Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Thu, 25 Feb 2021 13:17:01 +0000 Subject: [PATCH] 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 Reviewed-by: Corentin Wallez Commit-Queue: Corentin Wallez --- dawn.json | 24 +++++ dawn_wire.json | 11 +++ src/dawn_native/Queue.cpp | 56 +++++++++++ src/dawn_native/Queue.h | 5 + src/dawn_wire/client/ClientDoers.cpp | 13 ++- src/dawn_wire/client/Fence.cpp | 3 +- src/dawn_wire/client/Queue.cpp | 54 ++++++++++ src/dawn_wire/client/Queue.h | 19 ++++ src/dawn_wire/server/Server.h | 8 ++ src/dawn_wire/server/ServerQueue.cpp | 28 ++++++ src/tests/BUILD.gn | 2 + src/tests/end2end/DeviceLostTests.cpp | 34 +++++++ src/tests/end2end/QueueTimelineTests.cpp | 49 ++++++++- ...ueueOnSubmittedWorkDoneValidationTests.cpp | 58 +++++++++++ src/tests/unittests/wire/WireQueueTests.cpp | 99 +++++++++++++++++++ 15 files changed, 457 insertions(+), 6 deletions(-) create mode 100644 src/tests/unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp create mode 100644 src/tests/unittests/wire/WireQueueTests.cpp diff --git a/dawn.json b/dawn.json index d81172ed3f..7162078c9e 100644 --- a/dawn.json +++ b/dawn.json @@ -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", diff --git a/dawn_wire.json b/dawn_wire.json index 98b45965c5..88d26e0c40 100644 --- a/dawn_wire.json +++ b/dawn_wire.json @@ -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" ], diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp index fd65fbd65a..53bf294bf0 100644 --- a/src/dawn_native/Queue.cpp +++ b/src/dawn_native/Queue.cpp @@ -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 task = + std::make_unique(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 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)); diff --git a/src/dawn_native/Queue.h b/src/dawn_native/Queue.h index a36d912489..bdc2007d30 100644 --- a/src/dawn_native/Queue.h +++ b/src/dawn_native/Queue.h @@ -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, diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp index a5ef7f608e..2c6e23fbb1 100644 --- a/src/dawn_wire/client/ClientDoers.cpp +++ b/src/dawn_wire/client/ClientDoers.cpp @@ -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, diff --git a/src/dawn_wire/client/Fence.cpp b/src/dawn_wire/client/Fence.cpp index d9a858a245..563f13f1fe 100644 --- a/src/dawn_wire/client/Fence.cpp +++ b/src/dawn_wire/client/Fence.cpp @@ -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++; diff --git a/src/dawn_wire/client/Queue.cpp b/src/dawn_wire/client/Queue.cpp index f5f68f782b..8c9f78b53b 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 @@ 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 diff --git a/src/dawn_wire/client/Queue.h b/src/dawn_wire/client/Queue.h index 91c93935a7..f14fae1440 100644 --- a/src/dawn_wire/client/Queue.h +++ b/src/dawn_wire/client/Queue.h @@ -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 mOnWorkDoneRequests; }; }} // namespace dawn_wire::client diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h index bcedeaa2db..1124cba210 100644 --- a/src/dawn_wire/server/Server.h +++ b/src/dawn_wire/server/Server.h @@ -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, diff --git a/src/dawn_wire/server/ServerQueue.cpp b/src/dawn_wire/server/ServerQueue.cpp index d798d413dd..9ab8bc0bbb 100644 --- a/src/dawn_wire/server/ServerQueue.cpp +++ b/src/dawn_wire/server/ServerQueue.cpp @@ -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(); + userdata->queue = ObjectHandle{queueId, queue->generation}; + userdata->requestSerial = requestSerial; + + mProcs.queueOnSubmittedWorkDone( + queue->handle, signalValue, + ForwardToServer::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 0c3ffc3f76..a0baaf2eae 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -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", diff --git a/src/tests/end2end/DeviceLostTests.cpp b/src/tests/end2end/DeviceLostTests.cpp index a15ba940e3..1e7b539f0b 100644 --- a/src/tests/end2end/DeviceLostTests.cpp +++ b/src/tests/end2end/DeviceLostTests.cpp @@ -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; +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(); mockFenceOnCompletionCallback = std::make_unique(); + mockQueueWorkDoneCallback = std::make_unique(); } 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. diff --git a/src/tests/end2end/QueueTimelineTests.cpp b/src/tests/end2end/QueueTimelineTests.cpp index d125b18581..823a667c5a 100644 --- a/src/tests/end2end/QueueTimelineTests.cpp +++ b/src/tests/end2end/QueueTimelineTests.cpp @@ -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; +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(); mockFenceOnCompletionCallback = std::make_unique(); + mockQueueWorkDoneCallback = std::make_unique(); 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)) diff --git a/src/tests/unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp b/src/tests/unittests/validation/QueueOnSubmittedWorkDoneValidationTests.cpp new file mode 100644 index 0000000000..0b2fc9a66d --- /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 + +using namespace testing; + +class MockQueueWorkDoneCallback { + public: + MOCK_METHOD(void, Call, (WGPUQueueWorkDoneStatus status, void* userdata)); +}; + +static std::unique_ptr 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(); + } + + 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 0000000000..0dd340cf68 --- /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; +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(); + } + + 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); +}