From ae5f95044482d00f3660e155f8e6295badf9d02c Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Mon, 19 Oct 2020 01:56:08 +0000 Subject: [PATCH] Add the entry point of CreateReadyComputePipeline This patch adds the entry point of CreateReadyComputePipeline in both dawn_native and dawn_wire. TODOs: 1. Add more tests in dawn_unittests and dawn_end2end_tests. 2. Put the main logic of creating a pipeline into a separate thread. BUG=dawn:529 TEST=dawn_end2end_tests Change-Id: I7edd269a5422a8b85320a7f9173df925decba633 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/30060 Commit-Queue: Jiawei Shao Reviewed-by: Austin Eng --- dawn.json | 27 ++++ dawn_wire.json | 12 ++ generator/templates/mock_webgpu.cpp | 20 +++ generator/templates/mock_webgpu.h | 20 +++ src/dawn_native/BUILD.gn | 2 + src/dawn_native/CMakeLists.txt | 2 + .../CreateReadyPipelineTracker.cpp | 56 +++++++ src/dawn_native/CreateReadyPipelineTracker.h | 60 ++++++++ src/dawn_native/Device.cpp | 22 +++ src/dawn_native/Device.h | 5 + src/dawn_wire/client/ClientDoers.cpp | 7 + src/dawn_wire/client/Device.cpp | 59 +++++++- src/dawn_wire/client/Device.h | 15 ++ src/dawn_wire/server/Server.h | 14 ++ src/dawn_wire/server/ServerDevice.cpp | 52 +++++++ src/tests/BUILD.gn | 2 + .../end2end/CreateReadyPipelineTests.cpp | 137 ++++++++++++++++++ .../wire/WireCreateReadyPipelineTests.cpp | 127 ++++++++++++++++ 18 files changed, 638 insertions(+), 1 deletion(-) create mode 100644 src/dawn_native/CreateReadyPipelineTracker.cpp create mode 100644 src/dawn_native/CreateReadyPipelineTracker.h create mode 100644 src/tests/end2end/CreateReadyPipelineTests.cpp create mode 100644 src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp diff --git a/dawn.json b/dawn.json index bf8fb5430c..5e5fa34a66 100644 --- a/dawn.json +++ b/dawn.json @@ -501,6 +501,24 @@ {"name": "compute stage", "type": "programmable stage descriptor"} ] }, + "create ready compute pipeline callback": { + "category": "callback", + "args": [ + {"name": "status", "type": "create ready pipeline status"}, + {"name": "pipeline", "type": "compute pipeline"}, + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "create ready pipeline status": { + "category": "enum", + "values": [ + {"value": 0, "name": "success"}, + {"value": 1, "name": "error"}, + {"value": 2, "name": "device lost"}, + {"value": 3, "name": "unknown"} + ] + }, "cull mode": { "category": "enum", "values": [ @@ -552,6 +570,15 @@ {"name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"} ] }, + { + "name": "create ready compute pipeline", + "returns": "void", + "args": [ + {"name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"}, + {"name": "callback", "type": "create ready compute pipeline callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, { "name": "create pipeline layout", "returns": "pipeline layout", diff --git a/dawn_wire.json b/dawn_wire.json index a5b706469a..8870932876 100644 --- a/dawn_wire.json +++ b/dawn_wire.json @@ -39,6 +39,12 @@ { "name": "handle create info length", "type": "uint64_t" }, { "name": "handle create info", "type": "uint8_t", "annotation": "const*", "length": "handle create info length", "skip_serialize": true} ], + "device create ready compute pipeline": [ + { "name": "device", "type": "device" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"}, + { "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"} + ], "device pop error scope": [ { "name": "device", "type": "device" }, { "name": "request serial", "type": "uint64_t" } @@ -76,6 +82,11 @@ { "name": "read initial data info length", "type": "uint64_t" }, { "name": "read initial data info", "type": "uint8_t", "annotation": "const*", "length": "read initial data info length", "skip_serialize": true } ], + "device create ready compute pipeline callback": [ + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "create ready pipeline status" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen"} + ], "device uncaptured error callback": [ { "name": "type", "type": "error type"}, { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } @@ -109,6 +120,7 @@ "BufferGetConstMappedRange", "BufferGetMappedRange", "DeviceCreateBuffer", + "DeviceCreateReadyComputePipeline", "DevicePopErrorScope", "DeviceSetDeviceLostCallback", "DeviceSetUncapturedErrorCallback", diff --git a/generator/templates/mock_webgpu.cpp b/generator/templates/mock_webgpu.cpp index 801260bcf8..77f73be61c 100644 --- a/generator/templates/mock_webgpu.cpp +++ b/generator/templates/mock_webgpu.cpp @@ -100,6 +100,18 @@ void ProcTableAsClass::FenceOnCompletion(WGPUFence self, OnFenceOnCompletionCallback(self, value, callback, userdata); } +void ProcTableAsClass::DeviceCreateReadyComputePipeline( + WGPUDevice self, + WGPUComputePipelineDescriptor const * descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata) { + auto object = reinterpret_cast(self); + object->createReadyComputePipelineCallback = callback; + object->userdata = userdata; + + OnDeviceCreateReadyComputePipelineCallback(self, descriptor, callback, userdata); +} + void ProcTableAsClass::CallDeviceErrorCallback(WGPUDevice device, WGPUErrorType type, const char* message) { @@ -123,6 +135,14 @@ void ProcTableAsClass::CallFenceOnCompletionCallback(WGPUFence fence, object->fenceOnCompletionCallback(status, object->userdata); } +void ProcTableAsClass::CallDeviceCreateReadyComputePipelineCallback(WGPUDevice device, + WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message) { + auto object = reinterpret_cast(device); + object->createReadyComputePipelineCallback(status, pipeline, message, object->userdata); +} + {% for type in by_category["object"] %} {{as_cType(type.name)}} ProcTableAsClass::GetNew{{type.name.CamelCase()}}() { mObjects.emplace_back(new Object); diff --git a/generator/templates/mock_webgpu.h b/generator/templates/mock_webgpu.h index 502e403412..813f4bdd93 100644 --- a/generator/templates/mock_webgpu.h +++ b/generator/templates/mock_webgpu.h @@ -52,6 +52,10 @@ class ProcTableAsClass { {% endfor %} // Stores callback and userdata and calls the On* methods + void DeviceCreateReadyComputePipeline(WGPUDevice self, + WGPUComputePipelineDescriptor const * descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata); void DeviceSetUncapturedErrorCallback(WGPUDevice self, WGPUErrorCallback callback, void* userdata); @@ -71,6 +75,11 @@ class ProcTableAsClass { void* userdata); // Special cased mockable methods + virtual void OnDeviceCreateReadyComputePipelineCallback( + WGPUDevice device, + WGPUComputePipelineDescriptor const * descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata) = 0; virtual void OnDeviceSetUncapturedErrorCallback(WGPUDevice device, WGPUErrorCallback callback, void* userdata) = 0; @@ -89,6 +98,10 @@ class ProcTableAsClass { void* userdata) = 0; // Calls the stored callbacks + void CallDeviceCreateReadyComputePipelineCallback(WGPUDevice device, + WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message); void CallDeviceErrorCallback(WGPUDevice device, WGPUErrorType type, const char* message); void CallDeviceLostCallback(WGPUDevice device, const char* message); void CallMapAsyncCallback(WGPUBuffer buffer, WGPUBufferMapAsyncStatus status); @@ -97,6 +110,7 @@ class ProcTableAsClass { struct Object { ProcTableAsClass* procs = nullptr; WGPUErrorCallback deviceErrorCallback = nullptr; + WGPUCreateReadyComputePipelineCallback createReadyComputePipelineCallback = nullptr; WGPUDeviceLostCallback deviceLostCallback = nullptr; WGPUBufferMapCallback mapAsyncCallback = nullptr; WGPUFenceOnCompletionCallback fenceOnCompletionCallback = nullptr; @@ -130,6 +144,12 @@ class MockProcTable : public ProcTableAsClass { MOCK_METHOD(void, {{as_MethodSuffix(type.name, Name("release"))}}, ({{as_cType(type.name)}} self), (override)); {% endfor %} + MOCK_METHOD(void, + OnDeviceCreateReadyComputePipelineCallback, + (WGPUDevice device, WGPUComputePipelineDescriptor const * descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata), + (override)); MOCK_METHOD(void, OnDeviceSetUncapturedErrorCallback, (WGPUDevice device, WGPUErrorCallback callback, void* userdata), (override)); MOCK_METHOD(void, OnDeviceSetDeviceLostCallback, (WGPUDevice device, WGPUDeviceLostCallback callback, void* userdata), (override)); MOCK_METHOD(bool, OnDevicePopErrorScopeCallback, (WGPUDevice device, WGPUErrorCallback callback, void* userdata), (override)); diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn index 5387494dcb..a20ac2e7fe 100644 --- a/src/dawn_native/BUILD.gn +++ b/src/dawn_native/BUILD.gn @@ -183,6 +183,8 @@ source_set("dawn_native_sources") { "ComputePassEncoder.h", "ComputePipeline.cpp", "ComputePipeline.h", + "CreateReadyPipelineTracker.cpp", + "CreateReadyPipelineTracker.h", "Device.cpp", "Device.h", "DynamicUploader.cpp", diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt index ba0c94f71e..abcec978ab 100644 --- a/src/dawn_native/CMakeLists.txt +++ b/src/dawn_native/CMakeLists.txt @@ -66,6 +66,8 @@ target_sources(dawn_native PRIVATE "ComputePassEncoder.h" "ComputePipeline.cpp" "ComputePipeline.h" + "CreateReadyPipelineTracker.cpp", + "CreateReadyPipelineTracker.h", "Device.cpp" "Device.h" "DynamicUploader.cpp" diff --git a/src/dawn_native/CreateReadyPipelineTracker.cpp b/src/dawn_native/CreateReadyPipelineTracker.cpp new file mode 100644 index 0000000000..9d4ccc37d2 --- /dev/null +++ b/src/dawn_native/CreateReadyPipelineTracker.cpp @@ -0,0 +1,56 @@ +// Copyright 2020 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 "dawn_native/CreateReadyPipelineTracker.h" + +#include "dawn_native/Device.h" + +namespace dawn_native { + + CreateReadyComputePipelineTask::CreateReadyComputePipelineTask( + ComputePipelineBase* pipeline, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata) + : mPipeline(pipeline), mCallback(callback), mUserData(userdata) { + } + + CreateReadyComputePipelineTask::~CreateReadyComputePipelineTask() { + } + + void CreateReadyComputePipelineTask::Finish() { + mCallback(WGPUCreateReadyPipelineStatus_Success, + reinterpret_cast(mPipeline), "", mUserData); + } + + CreateReadyPipelineTracker::CreateReadyPipelineTracker(DeviceBase* device) : mDevice(device) { + } + + CreateReadyPipelineTracker::~CreateReadyPipelineTracker() { + ASSERT(mCreateReadyComputePipelineTasksInFlight.Empty()); + } + + void CreateReadyPipelineTracker::TrackTask(std::unique_ptr task, + ExecutionSerial serial) { + mCreateReadyComputePipelineTasksInFlight.Enqueue(std::move(task), serial); + mDevice->AddFutureSerial(serial); + } + + void CreateReadyPipelineTracker::Tick(ExecutionSerial finishedSerial) { + for (auto& task : mCreateReadyComputePipelineTasksInFlight.IterateUpTo(finishedSerial)) { + task->Finish(); + } + mCreateReadyComputePipelineTasksInFlight.ClearUpTo(finishedSerial); + } + +} // namespace dawn_native diff --git a/src/dawn_native/CreateReadyPipelineTracker.h b/src/dawn_native/CreateReadyPipelineTracker.h new file mode 100644 index 0000000000..b85c8c29fd --- /dev/null +++ b/src/dawn_native/CreateReadyPipelineTracker.h @@ -0,0 +1,60 @@ +// Copyright 2020 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. + +#ifndef DAWNNATIVE_CREATEREADYPIPELINETRACKER_H_ +#define DAWNNATIVE_CREATEREADYPIPELINETRACKER_H_ + +#include "common/SerialQueue.h" +#include "dawn/webgpu.h" +#include "dawn_native/IntegerTypes.h" + +#include + +namespace dawn_native { + + class ComputePipelineBase; + class DeviceBase; + + struct CreateReadyComputePipelineTask { + CreateReadyComputePipelineTask(ComputePipelineBase* pipeline, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata); + ~CreateReadyComputePipelineTask(); + + void Finish(); + + private: + ComputePipelineBase* mPipeline; + WGPUCreateReadyComputePipelineCallback mCallback; + void* mUserData; + }; + + class CreateReadyPipelineTracker { + public: + CreateReadyPipelineTracker(DeviceBase* device); + ~CreateReadyPipelineTracker(); + + void TrackTask(std::unique_ptr task, + ExecutionSerial serial); + void Tick(ExecutionSerial finishedSerial); + + private: + DeviceBase* mDevice; + SerialQueue> + mCreateReadyComputePipelineTasksInFlight; + }; + +} // namespace dawn_native + +#endif diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index b6dbed6dc3..5ae6e4673e 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -23,6 +23,7 @@ #include "dawn_native/CommandBuffer.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/ComputePipeline.h" +#include "dawn_native/CreateReadyPipelineTracker.h" #include "dawn_native/DynamicUploader.h" #include "dawn_native/ErrorData.h" #include "dawn_native/ErrorScope.h" @@ -102,6 +103,7 @@ namespace dawn_native { mCaches = std::make_unique(); mErrorScopeTracker = std::make_unique(this); mDynamicUploader = std::make_unique(this); + mCreateReadyPipelineTracker = std::make_unique(this); mDeprecationWarnings = std::make_unique(); // Starting from now the backend can start doing reentrant calls so the device is marked as @@ -149,6 +151,8 @@ namespace dawn_native { // pending callbacks. mErrorScopeTracker->Tick(GetCompletedCommandSerial()); GetDefaultQueue()->Tick(GetCompletedCommandSerial()); + mCreateReadyPipelineTracker->Tick(GetCompletedCommandSerial()); + // call TickImpl once last time to clean up resources // Ignore errors so that we can continue with destruction IgnoreErrors(TickImpl()); @@ -163,6 +167,7 @@ namespace dawn_native { } mErrorScopeTracker = nullptr; mDynamicUploader = nullptr; + mCreateReadyPipelineTracker = nullptr; mEmptyBindGroupLayout = nullptr; @@ -619,6 +624,22 @@ namespace dawn_native { return result; } + void DeviceBase::CreateReadyComputePipeline(const ComputePipelineDescriptor* descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata) { + ComputePipelineBase* result = nullptr; + MaybeError maybeError = CreateComputePipelineInternal(&result, descriptor); + if (maybeError.IsError()) { + std::unique_ptr error = maybeError.AcquireError(); + callback(WGPUCreateReadyPipelineStatus_Error, nullptr, error->GetMessage().c_str(), + userdata); + return; + } + + std::unique_ptr request = + std::make_unique(result, callback, userdata); + mCreateReadyPipelineTracker->TrackTask(std::move(request), GetPendingCommandSerial()); + } PipelineLayoutBase* DeviceBase::CreatePipelineLayout( const PipelineLayoutDescriptor* descriptor) { PipelineLayoutBase* result = nullptr; @@ -750,6 +771,7 @@ namespace dawn_native { mDynamicUploader->Deallocate(mCompletedSerial); mErrorScopeTracker->Tick(mCompletedSerial); GetDefaultQueue()->Tick(mCompletedSerial); + mCreateReadyPipelineTracker->Tick(mCompletedSerial); } return !IsDeviceIdle(); diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index bd76deea88..eab0763894 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -33,6 +33,7 @@ namespace dawn_native { class AttachmentState; class AttachmentStateBlueprint; class BindGroupLayoutBase; + class CreateReadyPipelineTracker; class DynamicUploader; class ErrorScope; class ErrorScopeTracker; @@ -145,6 +146,9 @@ namespace dawn_native { PipelineLayoutBase* CreatePipelineLayout(const PipelineLayoutDescriptor* descriptor); QuerySetBase* CreateQuerySet(const QuerySetDescriptor* descriptor); QueueBase* CreateQueue(); + void CreateReadyComputePipeline(const ComputePipelineDescriptor* descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata); RenderBundleEncoder* CreateRenderBundleEncoder( const RenderBundleEncoderDescriptor* descriptor); RenderPipelineBase* CreateRenderPipeline(const RenderPipelineDescriptor* descriptor); @@ -362,6 +366,7 @@ namespace dawn_native { std::unique_ptr mDynamicUploader; std::unique_ptr mErrorScopeTracker; + std::unique_ptr mCreateReadyPipelineTracker; Ref mDefaultQueue; struct DeprecationWarnings; diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp index c13f715622..338863eef5 100644 --- a/src/dawn_wire/client/ClientDoers.cpp +++ b/src/dawn_wire/client/ClientDoers.cpp @@ -82,4 +82,11 @@ namespace dawn_wire { namespace client { return true; } + bool Client::DoDeviceCreateReadyComputePipelineCallback(uint64_t requestSerial, + WGPUCreateReadyPipelineStatus status, + const char* message) { + mDevice->OnCreateReadyComputePipelineCallback(requestSerial, status, message); + return true; + } + }} // namespace dawn_wire::client diff --git a/src/dawn_wire/client/Device.cpp b/src/dawn_wire/client/Device.cpp index 40dc322d27..fd175fe0fc 100644 --- a/src/dawn_wire/client/Device.cpp +++ b/src/dawn_wire/client/Device.cpp @@ -15,7 +15,6 @@ #include "dawn_wire/client/Device.h" #include "common/Assert.h" -#include "dawn_wire/WireCmd_autogen.h" #include "dawn_wire/client/ApiObjects_autogen.h" #include "dawn_wire/client/Client.h" #include "dawn_wire/client/ObjectAllocator.h" @@ -44,6 +43,12 @@ namespace dawn_wire { namespace client { it.second.callback(WGPUErrorType_Unknown, "Device destroyed", it.second.userdata); } + auto createReadyComputePipelineRequests = std::move(mCreateReadyComputePipelineRequests); + for (const auto& it : createReadyComputePipelineRequests) { + it.second.callback(WGPUCreateReadyPipelineStatus_Unknown, nullptr, "Device destroyed", + it.second.userdata); + } + // Destroy the default queue DestroyObjectCmd cmd; cmd.objectType = ObjectType::Queue; @@ -158,4 +163,56 @@ namespace dawn_wire { namespace client { return ToAPI(mDefaultQueue); } + void Device::CreateReadyComputePipeline(WGPUComputePipelineDescriptor const* descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata) { + DeviceCreateReadyComputePipelineCmd cmd; + cmd.device = ToAPI(this); + cmd.descriptor = descriptor; + + uint64_t serial = mCreateReadyComputePipelineRequestSerial++; + ASSERT(mCreateReadyComputePipelineRequests.find(serial) == + mCreateReadyComputePipelineRequests.end()); + cmd.requestSerial = serial; + + auto* allocation = GetClient()->ComputePipelineAllocator().New(this); + CreateReadyComputePipelineRequest request = {}; + request.callback = callback; + request.userdata = userdata; + request.pipelineObjectID = allocation->object->id; + + cmd.pipelineObjectHandle = ObjectHandle{allocation->object->id, allocation->generation}; + GetClient()->SerializeCommand(cmd); + + mCreateReadyComputePipelineRequests[serial] = std::move(request); + } + + bool Device::OnCreateReadyComputePipelineCallback(uint64_t requestSerial, + WGPUCreateReadyPipelineStatus status, + const char* message) { + const auto& requestIt = mCreateReadyComputePipelineRequests.find(requestSerial); + if (requestIt == mCreateReadyComputePipelineRequests.end()) { + return false; + } + + CreateReadyComputePipelineRequest request = std::move(requestIt->second); + mCreateReadyComputePipelineRequests.erase(requestIt); + + auto pipelineAllocation = + GetClient()->ComputePipelineAllocator().GetObject(request.pipelineObjectID); + + // If the return status is a failure we should give a null pipeline to the callback and + // free the allocation both on the client side and the server side. + if (status != WGPUCreateReadyPipelineStatus_Success) { + GetClient()->ComputePipelineAllocator().Free(pipelineAllocation); + request.callback(status, nullptr, message, request.userdata); + return true; + } + + WGPUComputePipeline pipeline = reinterpret_cast(pipelineAllocation); + request.callback(status, pipeline, message, request.userdata); + + return true; + } + }} // namespace dawn_wire::client diff --git a/src/dawn_wire/client/Device.h b/src/dawn_wire/client/Device.h index 38d2762adf..087c72cba3 100644 --- a/src/dawn_wire/client/Device.h +++ b/src/dawn_wire/client/Device.h @@ -17,6 +17,7 @@ #include +#include "dawn_wire/WireCmd_autogen.h" #include "dawn_wire/client/ObjectBase.h" #include @@ -39,12 +40,18 @@ namespace dawn_wire { namespace client { bool PopErrorScope(WGPUErrorCallback callback, void* userdata); WGPUBuffer CreateBuffer(const WGPUBufferDescriptor* descriptor); WGPUBuffer CreateErrorBuffer(); + void CreateReadyComputePipeline(WGPUComputePipelineDescriptor const* descriptor, + WGPUCreateReadyComputePipelineCallback callback, + void* userdata); void HandleError(WGPUErrorType errorType, const char* message); void HandleDeviceLost(const char* message); bool OnPopErrorScopeCallback(uint64_t requestSerial, WGPUErrorType type, const char* message); + bool OnCreateReadyComputePipelineCallback(uint64_t requestSerial, + WGPUCreateReadyPipelineStatus status, + const char* message); WGPUQueue GetDefaultQueue(); @@ -57,6 +64,14 @@ namespace dawn_wire { namespace client { uint64_t mErrorScopeRequestSerial = 0; uint64_t mErrorScopeStackSize = 0; + struct CreateReadyComputePipelineRequest { + WGPUCreateReadyComputePipelineCallback callback = nullptr; + void* userdata = nullptr; + ObjectId pipelineObjectID; + }; + std::map mCreateReadyComputePipelineRequests; + uint64_t mCreateReadyComputePipelineRequestSerial = 0; + Client* mClient = nullptr; WGPUErrorCallback mErrorCallback = nullptr; WGPUDeviceLostCallback mDeviceLostCallback = nullptr; diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h index 73087dd4a5..acf321ad19 100644 --- a/src/dawn_wire/server/Server.h +++ b/src/dawn_wire/server/Server.h @@ -55,6 +55,12 @@ namespace dawn_wire { namespace server { uint64_t requestSerial; }; + struct CreateReadyPipelineUserData { + Server* server; + uint64_t requestSerial; + ObjectId pipelineObjectID; + }; + class Server : public ServerBase { public: Server(WGPUDevice device, @@ -89,6 +95,10 @@ namespace dawn_wire { namespace server { static void ForwardBufferMapAsync(WGPUBufferMapAsyncStatus status, void* userdata); static void ForwardFenceCompletedValue(WGPUFenceCompletionStatus status, void* userdata); static void ForwardFenceOnCompletion(WGPUFenceCompletionStatus status, void* userdata); + static void ForwardCreateReadyComputePipeline(WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message, + void* userdata); // Error callbacks void OnUncapturedError(WGPUErrorType type, const char* message); @@ -101,6 +111,10 @@ namespace dawn_wire { namespace server { FenceCompletionUserdata* userdata); void OnFenceOnCompletion(WGPUFenceCompletionStatus status, FenceOnCompletionUserdata* userdata); + void OnCreateReadyComputePipelineCallback(WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message, + CreateReadyPipelineUserData* userdata); #include "dawn_wire/server/ServerPrototypes_autogen.inc" diff --git a/src/dawn_wire/server/ServerDevice.cpp b/src/dawn_wire/server/ServerDevice.cpp index 64ad1bb0c5..997556200b 100644 --- a/src/dawn_wire/server/ServerDevice.cpp +++ b/src/dawn_wire/server/ServerDevice.cpp @@ -26,6 +26,16 @@ namespace dawn_wire { namespace server { server->OnDeviceLost(message); } + void Server::ForwardCreateReadyComputePipeline(WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message, + void* userdata) { + CreateReadyPipelineUserData* createReadyPipelineUserData = + static_cast(userdata); + createReadyPipelineUserData->server->OnCreateReadyComputePipelineCallback( + status, pipeline, message, createReadyPipelineUserData); + } + void Server::OnUncapturedError(WGPUErrorType type, const char* message) { ReturnDeviceUncapturedErrorCallbackCmd cmd; cmd.type = type; @@ -53,6 +63,48 @@ namespace dawn_wire { namespace server { return success; } + bool Server::DoDeviceCreateReadyComputePipeline( + WGPUDevice cDevice, + uint64_t requestSerial, + ObjectHandle pipelineObjectHandle, + const WGPUComputePipelineDescriptor* descriptor) { + auto* resultData = ComputePipelineObjects().Allocate(pipelineObjectHandle.id); + if (resultData == nullptr) { + return false; + } + + resultData->generation = pipelineObjectHandle.generation; + + std::unique_ptr userdata = + std::make_unique(); + userdata->server = this; + userdata->requestSerial = requestSerial; + userdata->pipelineObjectID = pipelineObjectHandle.id; + + mProcs.deviceCreateReadyComputePipeline( + cDevice, descriptor, ForwardCreateReadyComputePipeline, userdata.release()); + return true; + } + + void Server::OnCreateReadyComputePipelineCallback(WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message, + CreateReadyPipelineUserData* userdata) { + std::unique_ptr data(userdata); + if (status != WGPUCreateReadyPipelineStatus_Success) { + ComputePipelineObjects().Free(data->pipelineObjectID); + } else { + ComputePipelineObjects().Get(data->pipelineObjectID)->handle = pipeline; + } + + ReturnDeviceCreateReadyComputePipelineCallbackCmd cmd; + cmd.status = status; + cmd.requestSerial = data->requestSerial; + cmd.message = message; + + SerializeCommand(cmd); + } + // static void Server::ForwardPopErrorScope(WGPUErrorType type, const char* message, void* userdata) { auto* data = reinterpret_cast(userdata); diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index f50bf2d235..7f821fcf11 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -215,6 +215,7 @@ test("dawn_unittests") { "unittests/wire/WireArgumentTests.cpp", "unittests/wire/WireBasicTests.cpp", "unittests/wire/WireBufferMappingTests.cpp", + "unittests/wire/WireCreateReadyPipelineTests.cpp", "unittests/wire/WireDisconnectTests.cpp", "unittests/wire/WireErrorCallbackTests.cpp", "unittests/wire/WireExtensionTests.cpp", @@ -273,6 +274,7 @@ source_set("dawn_end2end_tests_sources") { "end2end/ComputeSharedMemoryTests.cpp", "end2end/ComputeStorageBufferBarrierTests.cpp", "end2end/CopyTests.cpp", + "end2end/CreateReadyPipelineTests.cpp", "end2end/CullingTests.cpp", "end2end/DebugMarkerTests.cpp", "end2end/DeprecatedAPITests.cpp", diff --git a/src/tests/end2end/CreateReadyPipelineTests.cpp b/src/tests/end2end/CreateReadyPipelineTests.cpp new file mode 100644 index 0000000000..89845fb0e2 --- /dev/null +++ b/src/tests/end2end/CreateReadyPipelineTests.cpp @@ -0,0 +1,137 @@ +// Copyright 2020 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/DawnTest.h" + +#include "utils/WGPUHelpers.h" + +namespace { + struct CreateReadyPipelineTask { + wgpu::ComputePipeline computePipeline; + bool isCompleted = false; + std::string message; + }; +} // anonymous namespace + +class CreateReadyPipelineTest : public DawnTest {}; + +// Verify the basic use of CreateReadyComputePipeline works on all backends. +TEST_P(CreateReadyPipelineTest, BasicUseOfCreateReadyComputePipeline) { + const char* computeShader = R"( + #version 450 + layout(std140, set = 0, binding = 0) buffer SSBO { uint value; } ssbo; + void main() { + ssbo.value = 1u; + })"; + + wgpu::ComputePipelineDescriptor csDesc; + csDesc.computeStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, computeShader); + csDesc.computeStage.entryPoint = "main"; + + CreateReadyPipelineTask task; + device.CreateReadyComputePipeline( + &csDesc, + [](WGPUCreateReadyPipelineStatus status, WGPUComputePipeline returnPipeline, + const char* message, void* userdata) { + ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Success, status); + + CreateReadyPipelineTask* task = static_cast(userdata); + task->computePipeline = wgpu::ComputePipeline::Acquire(returnPipeline); + task->isCompleted = true; + task->message = message; + }, + &task); + + wgpu::BufferDescriptor bufferDesc; + bufferDesc.size = sizeof(uint32_t); + bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc; + wgpu::Buffer ssbo = device.CreateBuffer(&bufferDesc); + + wgpu::CommandBuffer commands; + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); + + while (!task.isCompleted) { + WaitABit(); + } + ASSERT_TRUE(task.message.empty()); + ASSERT_NE(nullptr, task.computePipeline.Get()); + wgpu::BindGroup bindGroup = + utils::MakeBindGroup(device, task.computePipeline.GetBindGroupLayout(0), + { + {0, ssbo, 0, sizeof(uint32_t)}, + }); + pass.SetBindGroup(0, bindGroup); + pass.SetPipeline(task.computePipeline); + + pass.Dispatch(1); + pass.EndPass(); + + commands = encoder.Finish(); + } + + queue.Submit(1, &commands); + + constexpr uint32_t kExpected = 1u; + EXPECT_BUFFER_U32_EQ(kExpected, ssbo, 0); +} + +// Verify CreateReadyComputePipeline() works as expected when there is any error that happens during +// the creation of the compute pipeline. The SPEC requires that during the call of +// CreateReadyComputePipeline() any error won't be forwarded to the error scope / unhandled error +// callback. +TEST_P(CreateReadyPipelineTest, CreateComputePipelineFailed) { + DAWN_SKIP_TEST_IF(IsDawnValidationSkipped()); + + const char* computeShader = R"( + #version 450 + layout(std140, set = 0, binding = 0) buffer SSBO { uint value; } ssbo; + void main() { + ssbo.value = 1u; + })"; + + wgpu::ComputePipelineDescriptor csDesc; + csDesc.computeStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, computeShader); + csDesc.computeStage.entryPoint = "main0"; + + CreateReadyPipelineTask task; + device.CreateReadyComputePipeline( + &csDesc, + [](WGPUCreateReadyPipelineStatus status, WGPUComputePipeline returnPipeline, + const char* message, void* userdata) { + ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Error, status); + + CreateReadyPipelineTask* task = static_cast(userdata); + task->computePipeline = wgpu::ComputePipeline::Acquire(returnPipeline); + task->isCompleted = true; + task->message = message; + }, + &task); + + while (!task.isCompleted) { + WaitABit(); + } + + ASSERT_FALSE(task.message.empty()); + ASSERT_EQ(nullptr, task.computePipeline.Get()); +} + +DAWN_INSTANTIATE_TEST(CreateReadyPipelineTest, + D3D12Backend(), + MetalBackend(), + OpenGLBackend(), + VulkanBackend()); diff --git a/src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp b/src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp new file mode 100644 index 0000000000..6d699c0a06 --- /dev/null +++ b/src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp @@ -0,0 +1,127 @@ +// Copyright 2020 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" + +using namespace testing; +using namespace dawn_wire; + +namespace { + + // Mock class to add expectations on the wire calling callbacks + class MockCreateReadyComputePipelineCallback { + public: + MOCK_METHOD(void, + Call, + (WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message, + void* userdata)); + }; + + std::unique_ptr> + mockCreateReadyComputePipelineCallback; + void ToMockCreateReadyComputePipelineCallback(WGPUCreateReadyPipelineStatus status, + WGPUComputePipeline pipeline, + const char* message, + void* userdata) { + mockCreateReadyComputePipelineCallback->Call(status, pipeline, message, userdata); + } + +} // anonymous namespace + +class WireCreateReadyPipelineTest : public WireTest { + public: + void SetUp() override { + WireTest::SetUp(); + + mockCreateReadyComputePipelineCallback = + std::make_unique>(); + } + + void TearDown() override { + WireTest::TearDown(); + + // Delete mock so that expectations are checked + mockCreateReadyComputePipelineCallback = nullptr; + } + + void FlushClient() { + WireTest::FlushClient(); + Mock::VerifyAndClearExpectations(&mockCreateReadyComputePipelineCallback); + } + + void FlushServer() { + WireTest::FlushServer(); + Mock::VerifyAndClearExpectations(&mockCreateReadyComputePipelineCallback); + } +}; + +// Test when creating a compute pipeline with CreateReadyComputePipeline() successfully. +TEST_F(WireCreateReadyPipelineTest, CreateReadyComputePipelineSuccess) { + WGPUShaderModuleDescriptor csDescriptor{}; + WGPUShaderModule csModule = wgpuDeviceCreateShaderModule(device, &csDescriptor); + WGPUShaderModule apiCsModule = api.GetNewShaderModule(); + EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _)).WillOnce(Return(apiCsModule)); + + WGPUComputePipelineDescriptor descriptor{}; + descriptor.computeStage.module = csModule; + descriptor.computeStage.entryPoint = "main"; + + wgpuDeviceCreateReadyComputePipeline(device, &descriptor, + ToMockCreateReadyComputePipelineCallback, this); + + EXPECT_CALL(api, OnDeviceCreateReadyComputePipelineCallback(apiDevice, _, _, _)) + .WillOnce(InvokeWithoutArgs([&]() { + api.CallDeviceCreateReadyComputePipelineCallback( + apiDevice, WGPUCreateReadyPipelineStatus_Success, nullptr, ""); + })); + + FlushClient(); + + EXPECT_CALL(*mockCreateReadyComputePipelineCallback, + Call(WGPUCreateReadyPipelineStatus_Success, _, StrEq(""), this)) + .Times(1); + + FlushServer(); +} + +// Test when creating a compute pipeline with CreateReadyComputePipeline() results in an error. +TEST_F(WireCreateReadyPipelineTest, CreateReadyComputePipelineError) { + WGPUShaderModuleDescriptor csDescriptor{}; + WGPUShaderModule csModule = wgpuDeviceCreateShaderModule(device, &csDescriptor); + WGPUShaderModule apiCsModule = api.GetNewShaderModule(); + EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _)).WillOnce(Return(apiCsModule)); + + WGPUComputePipelineDescriptor descriptor{}; + descriptor.computeStage.module = csModule; + descriptor.computeStage.entryPoint = "main"; + + wgpuDeviceCreateReadyComputePipeline(device, &descriptor, + ToMockCreateReadyComputePipelineCallback, this); + + EXPECT_CALL(api, OnDeviceCreateReadyComputePipelineCallback(apiDevice, _, _, _)) + .WillOnce(InvokeWithoutArgs([&]() { + api.CallDeviceCreateReadyComputePipelineCallback( + apiDevice, WGPUCreateReadyPipelineStatus_Error, nullptr, "Some error message"); + })); + + FlushClient(); + + EXPECT_CALL(*mockCreateReadyComputePipelineCallback, + Call(WGPUCreateReadyPipelineStatus_Error, _, StrEq("Some error message"), this)) + .Times(1); + + FlushServer(); +}