diff --git a/src/dawn_native/CreatePipelineAsyncTask.cpp b/src/dawn_native/CreatePipelineAsyncTask.cpp index 6a35b94023..fae8ad2510 100644 --- a/src/dawn_native/CreatePipelineAsyncTask.cpp +++ b/src/dawn_native/CreatePipelineAsyncTask.cpp @@ -106,7 +106,7 @@ namespace dawn_native { size_t blueprintHash, WGPUCreateComputePipelineAsyncCallback callback, void* userdata) - : mComputePipeline(nonInitializedComputePipeline), + : mComputePipeline(std::move(nonInitializedComputePipeline)), mBlueprintHash(blueprintHash), mCallback(callback), mUserdata(userdata) { @@ -139,4 +139,41 @@ namespace dawn_native { device->GetAsyncTaskManager()->PostTask(std::move(asyncTask)); } + CreateRenderPipelineAsyncTask::CreateRenderPipelineAsyncTask( + Ref nonInitializedRenderPipeline, + size_t blueprintHash, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) + : mRenderPipeline(std::move(nonInitializedRenderPipeline)), + mBlueprintHash(blueprintHash), + mCallback(callback), + mUserdata(userdata) { + ASSERT(mRenderPipeline != nullptr); + } + + void CreateRenderPipelineAsyncTask::Run() { + MaybeError maybeError = mRenderPipeline->Initialize(); + std::string errorMessage; + if (maybeError.IsError()) { + mRenderPipeline = nullptr; + errorMessage = maybeError.AcquireError()->GetMessage(); + } + + mRenderPipeline->GetDevice()->AddRenderPipelineAsyncCallbackTask( + mRenderPipeline, errorMessage, mCallback, mUserdata, mBlueprintHash); + } + + void CreateRenderPipelineAsyncTask::RunAsync( + std::unique_ptr task) { + DeviceBase* device = task->mRenderPipeline->GetDevice(); + + // Using "taskPtr = std::move(task)" causes compilation error while it should be supported + // since C++14: + // https://docs.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-160 + auto asyncTask = [taskPtr = task.release()] { + std::unique_ptr innerTaskPtr(taskPtr); + innerTaskPtr->Run(); + }; + device->GetAsyncTaskManager()->PostTask(std::move(asyncTask)); + } } // namespace dawn_native diff --git a/src/dawn_native/CreatePipelineAsyncTask.h b/src/dawn_native/CreatePipelineAsyncTask.h index 34456aa2a4..33e3e95322 100644 --- a/src/dawn_native/CreatePipelineAsyncTask.h +++ b/src/dawn_native/CreatePipelineAsyncTask.h @@ -52,17 +52,17 @@ namespace dawn_native { WGPUCreateComputePipelineAsyncCallback mCreateComputePipelineAsyncCallback; }; - struct CreateRenderPipelineAsyncCallbackTask final : CreatePipelineAsyncCallbackTaskBase { + struct CreateRenderPipelineAsyncCallbackTask : CreatePipelineAsyncCallbackTaskBase { CreateRenderPipelineAsyncCallbackTask(Ref pipeline, std::string errorMessage, WGPUCreateRenderPipelineAsyncCallback callback, void* userdata); - void Finish() final; + void Finish() override; void HandleShutDown() final; void HandleDeviceLoss() final; - private: + protected: Ref mPipeline; WGPUCreateRenderPipelineAsyncCallback mCreateRenderPipelineAsyncCallback; }; @@ -76,18 +76,37 @@ namespace dawn_native { WGPUCreateComputePipelineAsyncCallback callback, void* userdata); - virtual ~CreateComputePipelineAsyncTask() = default; void Run(); static void RunAsync(std::unique_ptr task); - protected: + private: Ref mComputePipeline; size_t mBlueprintHash; WGPUCreateComputePipelineAsyncCallback mCallback; void* mUserdata; }; + // CreateRenderPipelineAsyncTask defines all the inputs and outputs of + // CreateRenderPipelineAsync() tasks, which are the same among all the backends. + class CreateRenderPipelineAsyncTask { + public: + CreateRenderPipelineAsyncTask(Ref nonInitializedRenderPipeline, + size_t blueprintHash, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); + + void Run(); + + static void RunAsync(std::unique_ptr task); + + private: + Ref mRenderPipeline; + size_t mBlueprintHash; + WGPUCreateRenderPipelineAsyncCallback mCallback; + void* mUserdata; + }; + } // namespace dawn_native #endif // DAWNNATIVE_CREATEPIPELINEASYNCTASK_H_ diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index 3696adb73b..7ade6ab93c 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -1338,9 +1338,8 @@ namespace dawn_native { // Otherwise we will create the pipeline object in CreateRenderPipelineAsyncImpl(), // where the pipeline object may be created asynchronously and the result will be saved // to mCreatePipelineAsyncTracker. - FlatRenderPipelineDescriptor appliedFlatDescriptor(&descriptorWithPipelineLayout); const size_t blueprintHash = pipelineAndBlueprintFromCache.second; - CreateRenderPipelineAsyncImpl(&appliedFlatDescriptor, blueprintHash, callback, + CreateRenderPipelineAsyncImpl(&descriptorWithPipelineLayout, blueprintHash, callback, userdata); } @@ -1530,8 +1529,8 @@ namespace dawn_native { } void Finish() final { - // TODO(jiawei.shao@intel.com): call AddOrGetCachedComputePipeline() asynchronously - // in CreateComputePipelineAsyncTaskImpl::Run() when the front-end pipeline cache is + // TODO(dawn:529): call AddOrGetCachedComputePipeline() asynchronously in + // CreateComputePipelineAsyncTaskImpl::Run() when the front-end pipeline cache is // thread-safe. if (mPipeline.Get() != nullptr) { mPipeline = mPipeline->GetDevice()->AddOrGetCachedComputePipeline( @@ -1550,6 +1549,50 @@ namespace dawn_native { std::move(pipeline), errorMessage, callback, userdata, blueprintHash)); } + void DeviceBase::AddRenderPipelineAsyncCallbackTask( + Ref pipeline, + std::string errorMessage, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata, + size_t blueprintHash) { + // CreateRenderPipelineAsyncWaitableCallbackTask is declared as an internal class as it + // needs to call the private member function DeviceBase::AddOrGetCachedRenderPipeline(). + struct CreateRenderPipelineAsyncWaitableCallbackTask final + : CreateRenderPipelineAsyncCallbackTask { + CreateRenderPipelineAsyncWaitableCallbackTask( + Ref pipeline, + std::string errorMessage, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata, + size_t blueprintHash) + : CreateRenderPipelineAsyncCallbackTask(std::move(pipeline), + errorMessage, + callback, + userdata), + mBlueprintHash(blueprintHash) { + } + + void Finish() final { + // TODO(dawn:529): call AddOrGetCachedRenderPipeline() asynchronously in + // CreateRenderPipelineAsyncTaskImpl::Run() when the front-end pipeline cache is + // thread-safe. + if (mPipeline.Get() != nullptr) { + mPipeline = mPipeline->GetDevice()->AddOrGetCachedRenderPipeline( + mPipeline, mBlueprintHash); + } + + CreateRenderPipelineAsyncCallbackTask::Finish(); + } + + private: + size_t mBlueprintHash; + }; + + mCallbackTaskManager->AddCallbackTask( + std::make_unique( + std::move(pipeline), errorMessage, callback, userdata, blueprintHash)); + } + PipelineCompatibilityToken DeviceBase::GetNextPipelineCompatibilityToken() { return PipelineCompatibilityToken(mNextPipelineCompatibilityToken++); } diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index dd59f4fd8f..3688ee1905 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -299,6 +299,11 @@ namespace dawn_native { WGPUCreateComputePipelineAsyncCallback callback, void* userdata, size_t blueprintHash); + void AddRenderPipelineAsyncCallbackTask(Ref pipeline, + std::string errorMessage, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata, + size_t blueprintHash); PipelineCompatibilityToken GetNextPipelineCompatibilityToken(); diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp index b417deb349..7bdfd0a893 100644 --- a/src/dawn_native/RenderPipeline.cpp +++ b/src/dawn_native/RenderPipeline.cpp @@ -364,72 +364,6 @@ namespace dawn_native { } } // anonymous namespace - // FlatRenderPipelineDescriptor - FlatRenderPipelineDescriptor::FlatRenderPipelineDescriptor( - const RenderPipelineDescriptor* descriptor) - : mLabel(descriptor->label != nullptr ? descriptor->label : ""), - mLayout(descriptor->layout), - mVertexModule(descriptor->vertex.module), - mVertexEntryPoint(descriptor->vertex.entryPoint) { - label = mLabel.c_str(); - - ASSERT(descriptor->layout != nullptr); - layout = mLayout.Get(); - - vertex.module = mVertexModule.Get(); - vertex.entryPoint = mVertexEntryPoint.c_str(); - vertex.bufferCount = descriptor->vertex.bufferCount; - vertex.buffers = mVertexBuffers.data(); - uint32_t vertexAttributeCount = 0; - for (uint32_t vertexBufferIndex = 0; vertexBufferIndex < vertex.bufferCount; - ++vertexBufferIndex) { - const VertexBufferLayout& vertexLayout = descriptor->vertex.buffers[vertexBufferIndex]; - mVertexBuffers[vertexBufferIndex] = vertexLayout; - mVertexBuffers[vertexBufferIndex].attributes = &mVertexAttributes[vertexAttributeCount]; - for (uint32_t attributeIndex = 0; attributeIndex < vertexLayout.attributeCount; - ++attributeIndex) { - mVertexAttributes[vertexAttributeCount + attributeIndex] = - vertexLayout.attributes[attributeIndex]; - } - - vertexAttributeCount += vertexLayout.attributeCount; - } - - primitive = descriptor->primitive; - - if (descriptor->depthStencil != nullptr) { - mDepthStencilState = *(descriptor->depthStencil); - depthStencil = &mDepthStencilState; - } - - multisample = descriptor->multisample; - - if (descriptor->fragment != nullptr) { - mFragmentModule = descriptor->fragment->module; - mFragmentState.module = mFragmentModule.Get(); - - mFragmentEntryPoint = descriptor->fragment->entryPoint; - mFragmentState.entryPoint = mFragmentEntryPoint.c_str(); - - mFragmentState.targetCount = descriptor->fragment->targetCount; - - mFragmentState.targets = mColorTargetStates.data(); - for (uint32_t colorTargetIndex = 0; colorTargetIndex < mFragmentState.targetCount; - ++colorTargetIndex) { - mColorTargetStates[colorTargetIndex] = - descriptor->fragment->targets[colorTargetIndex]; - - if (descriptor->fragment->targets[colorTargetIndex].blend != nullptr) { - mColorTargetStates[colorTargetIndex].blend = &mBlendStates[colorTargetIndex]; - mBlendStates[colorTargetIndex] = - *(descriptor->fragment->targets[colorTargetIndex].blend); - } - } - - fragment = &mFragmentState; - } - } - // Helper functions size_t IndexFormatSize(wgpu::IndexFormat format) { switch (format) { @@ -954,4 +888,8 @@ namespace dawn_native { return true; } + + MaybeError RenderPipelineBase::Initialize() { + return {}; + } } // namespace dawn_native diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h index 35840b335f..ea618dac33 100644 --- a/src/dawn_native/RenderPipeline.h +++ b/src/dawn_native/RenderPipeline.h @@ -29,31 +29,6 @@ namespace dawn_native { class DeviceBase; - // TODO(dawn:529): Use FlatRenderPipelineDescriptor to keep all the members of - // RenderPipelineDescriptor (especially the members in pointers) valid when the creation of the - // render pipeline is executed asynchronously. - struct FlatRenderPipelineDescriptor : public RenderPipelineDescriptor, public NonMovable { - public: - explicit FlatRenderPipelineDescriptor(const RenderPipelineDescriptor* descriptor); - - private: - std::string mLabel; - Ref mLayout; - - Ref mVertexModule; - std::string mVertexEntryPoint; - std::array mVertexBuffers; - std::array mVertexAttributes; - - FragmentState mFragmentState; - Ref mFragmentModule; - std::string mFragmentEntryPoint; - std::array mColorTargetStates; - std::array mBlendStates; - - DepthStencilState mDepthStencilState; - }; - MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device, const RenderPipelineDescriptor* descriptor); @@ -130,6 +105,11 @@ namespace dawn_native { private: RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); + // CreateRenderPipelineAsyncTask is declared as a friend of RenderPipelineBase as it + // needs to call the private member function RenderPipelineBase::Initialize(). + friend class CreateRenderPipelineAsyncTask; + virtual MaybeError Initialize(); + // TODO(dawn:529): store all the following members in a FlatRenderPipelineDescriptor object // Vertex state uint32_t mVertexBufferCount; diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index c8c9762c61..97d5f10a31 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -382,6 +382,12 @@ namespace dawn_native { namespace d3d12 { void* userdata) { ComputePipeline::CreateAsync(this, descriptor, blueprintHash, callback, userdata); } + void Device::CreateRenderPipelineAsyncImpl(const RenderPipelineDescriptor* descriptor, + size_t blueprintHash, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) { + RenderPipeline::CreateAsync(this, descriptor, blueprintHash, callback, userdata); + } ResultOrError> Device::CreateStagingBuffer(size_t size) { std::unique_ptr stagingBuffer = diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h index 84309699f1..cefa780faf 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.h +++ b/src/dawn_native/d3d12/DeviceD3D12.h @@ -177,6 +177,10 @@ namespace dawn_native { namespace d3d12 { size_t blueprintHash, WGPUCreateComputePipelineAsyncCallback callback, void* userdata) override; + void CreateRenderPipelineAsyncImpl(const RenderPipelineDescriptor* descriptor, + size_t blueprintHash, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) override; void ShutDownImpl() override; MaybeError WaitForIdleForDestruction() override; diff --git a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp index e30fdb03dc..75de2b36e3 100644 --- a/src/dawn_native/d3d12/RenderPipelineD3D12.cpp +++ b/src/dawn_native/d3d12/RenderPipelineD3D12.cpp @@ -16,6 +16,8 @@ #include "common/Assert.h" #include "common/Log.h" +#include "dawn_native/AsyncTask.h" +#include "dawn_native/CreatePipelineAsyncTask.h" #include "dawn_native/d3d12/D3D12Error.h" #include "dawn_native/d3d12/DeviceD3D12.h" #include "dawn_native/d3d12/PipelineLayoutD3D12.h" @@ -471,4 +473,16 @@ namespace dawn_native { namespace d3d12 { return inputLayoutDescriptor; } + void RenderPipeline::CreateAsync(Device* device, + const RenderPipelineDescriptor* descriptor, + size_t blueprintHash, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) { + Ref pipeline = AcquireRef(new RenderPipeline(device, descriptor)); + std::unique_ptr asyncTask = + std::make_unique(pipeline, blueprintHash, callback, + userdata); + CreateRenderPipelineAsyncTask::RunAsync(std::move(asyncTask)); + } + }} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/RenderPipelineD3D12.h b/src/dawn_native/d3d12/RenderPipelineD3D12.h index ed71da92a9..199107f792 100644 --- a/src/dawn_native/d3d12/RenderPipelineD3D12.h +++ b/src/dawn_native/d3d12/RenderPipelineD3D12.h @@ -29,6 +29,11 @@ namespace dawn_native { namespace d3d12 { static ResultOrError> Create( Device* device, const RenderPipelineDescriptor* descriptor); + static void CreateAsync(Device* device, + const RenderPipelineDescriptor* descriptor, + size_t blueprintHash, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); RenderPipeline() = delete; D3D12_PRIMITIVE_TOPOLOGY GetD3D12PrimitiveTopology() const; @@ -42,7 +47,7 @@ namespace dawn_native { namespace d3d12 { private: ~RenderPipeline() override; using RenderPipelineBase::RenderPipelineBase; - MaybeError Initialize(); + MaybeError Initialize() override; D3D12_INPUT_LAYOUT_DESC ComputeInputLayout( std::array* inputElementDescriptors); diff --git a/src/dawn_native/metal/RenderPipelineMTL.h b/src/dawn_native/metal/RenderPipelineMTL.h index 07a2d77292..1d81072cc8 100644 --- a/src/dawn_native/metal/RenderPipelineMTL.h +++ b/src/dawn_native/metal/RenderPipelineMTL.h @@ -47,7 +47,7 @@ namespace dawn_native { namespace metal { private: using RenderPipelineBase::RenderPipelineBase; - MaybeError Initialize(); + MaybeError Initialize() override; MTLVertexDescriptor* MakeVertexDesc(); diff --git a/src/dawn_native/opengl/RenderPipelineGL.h b/src/dawn_native/opengl/RenderPipelineGL.h index 3c7a4d321a..1881fcd6b7 100644 --- a/src/dawn_native/opengl/RenderPipelineGL.h +++ b/src/dawn_native/opengl/RenderPipelineGL.h @@ -42,7 +42,7 @@ namespace dawn_native { namespace opengl { private: RenderPipeline(Device* device, const RenderPipelineDescriptor* descriptor); ~RenderPipeline() override; - MaybeError Initialize(); + MaybeError Initialize() override; void CreateVAOForVertexState(); diff --git a/src/dawn_native/vulkan/RenderPipelineVk.h b/src/dawn_native/vulkan/RenderPipelineVk.h index fe653c80d4..6a9207f8bb 100644 --- a/src/dawn_native/vulkan/RenderPipelineVk.h +++ b/src/dawn_native/vulkan/RenderPipelineVk.h @@ -38,7 +38,7 @@ namespace dawn_native { namespace vulkan { private: ~RenderPipeline() override; using RenderPipelineBase::RenderPipelineBase; - MaybeError Initialize(); + MaybeError Initialize() override; struct PipelineVertexInputStateCreateInfoTemporaryAllocations { std::array bindings; diff --git a/src/tests/end2end/CreatePipelineAsyncTests.cpp b/src/tests/end2end/CreatePipelineAsyncTests.cpp index eab8bc287f..d8b8af9a5b 100644 --- a/src/tests/end2end/CreatePipelineAsyncTests.cpp +++ b/src/tests/end2end/CreatePipelineAsyncTests.cpp @@ -68,6 +68,47 @@ class CreatePipelineAsyncTest : public DawnTest { ValidateCreateComputePipelineAsync(&task); } + void ValidateCreateRenderPipelineAsync(CreatePipelineAsyncTask* currentTask) { + constexpr wgpu::TextureFormat kRenderAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm; + + wgpu::TextureDescriptor textureDescriptor; + textureDescriptor.size = {1, 1, 1}; + textureDescriptor.format = kRenderAttachmentFormat; + textureDescriptor.usage = + wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; + wgpu::Texture outputTexture = device.CreateTexture(&textureDescriptor); + + utils::ComboRenderPassDescriptor renderPassDescriptor({outputTexture.CreateView()}); + renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; + renderPassDescriptor.cColorAttachments[0].clearColor = {1.f, 0.f, 0.f, 1.f}; + + wgpu::CommandBuffer commands; + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder renderPassEncoder = + encoder.BeginRenderPass(&renderPassDescriptor); + + while (!currentTask->isCompleted) { + WaitABit(); + } + ASSERT_TRUE(currentTask->message.empty()); + ASSERT_NE(nullptr, currentTask->renderPipeline.Get()); + + renderPassEncoder.SetPipeline(currentTask->renderPipeline); + renderPassEncoder.Draw(1); + renderPassEncoder.EndPass(); + commands = encoder.Finish(); + } + + queue.Submit(1, &commands); + + EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), outputTexture, 0, 0); + } + + void ValidateCreateRenderPipelineAsync() { + ValidateCreateRenderPipelineAsync(&task); + } + void DoCreateRenderPipelineAsync( const utils::ComboRenderPipelineDescriptor& renderPipelineDescriptor) { device.CreateRenderPipelineAsync( @@ -212,36 +253,7 @@ TEST_P(CreatePipelineAsyncTest, BasicUseOfCreateRenderPipelineAsync) { DoCreateRenderPipelineAsync(renderPipelineDescriptor); - wgpu::TextureDescriptor textureDescriptor; - textureDescriptor.size = {1, 1, 1}; - textureDescriptor.format = kRenderAttachmentFormat; - textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; - wgpu::Texture outputTexture = device.CreateTexture(&textureDescriptor); - - utils::ComboRenderPassDescriptor renderPassDescriptor({outputTexture.CreateView()}); - renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; - renderPassDescriptor.cColorAttachments[0].clearColor = {1.f, 0.f, 0.f, 1.f}; - - wgpu::CommandBuffer commands; - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor); - - while (!task.isCompleted) { - WaitABit(); - } - ASSERT_TRUE(task.message.empty()); - ASSERT_NE(nullptr, task.renderPipeline.Get()); - - renderPassEncoder.SetPipeline(task.renderPipeline); - renderPassEncoder.Draw(1); - renderPassEncoder.EndPass(); - commands = encoder.Finish(); - } - - queue.Submit(1, &commands); - - EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), outputTexture, 0, 0); + ValidateCreateRenderPipelineAsync(); } // Verify the render pipeline created with CreateRenderPipelineAsync() still works when the entry @@ -448,7 +460,7 @@ TEST_P(CreatePipelineAsyncTest, CreateSameComputePipelineTwice) { // Verify creating compute pipeline with same descriptor and CreateComputePipelineAsync() at the // same time works correctly. -TEST_P(CreatePipelineAsyncTest, CreateSamePipelineTwiceAtSameTime) { +TEST_P(CreatePipelineAsyncTest, CreateSameComputePipelineTwiceAtSameTime) { wgpu::BindGroupLayoutEntry binding = {}; binding.binding = 0; binding.buffer.type = wgpu::BufferBindingType::Storage; @@ -505,6 +517,50 @@ TEST_P(CreatePipelineAsyncTest, CreateSamePipelineTwiceAtSameTime) { } } +// Verify the basic use of CreateRenderPipelineAsync() works on all backends. +TEST_P(CreatePipelineAsyncTest, CreateSameRenderPipelineTwiceAtSameTime) { + constexpr wgpu::TextureFormat kRenderAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm; + + utils::ComboRenderPipelineDescriptor renderPipelineDescriptor; + wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( + [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { + return vec4(0.0, 0.0, 0.0, 1.0); + })"); + wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( + [[stage(fragment)]] fn main() -> [[location(0)]] vec4 { + return vec4(0.0, 1.0, 0.0, 1.0); + })"); + renderPipelineDescriptor.vertex.module = vsModule; + renderPipelineDescriptor.cFragment.module = fsModule; + renderPipelineDescriptor.cTargets[0].format = kRenderAttachmentFormat; + renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList; + + auto callback = [](WGPUCreatePipelineAsyncStatus status, WGPURenderPipeline returnPipeline, + const char* message, void* userdata) { + EXPECT_EQ(WGPUCreatePipelineAsyncStatus::WGPUCreatePipelineAsyncStatus_Success, status); + + CreatePipelineAsyncTask* task = static_cast(userdata); + task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline); + task->isCompleted = true; + task->message = message; + }; + + // Create two render pipelines with same descriptor. + CreatePipelineAsyncTask anotherTask; + device.CreateRenderPipelineAsync(&renderPipelineDescriptor, callback, &task); + device.CreateRenderPipelineAsync(&renderPipelineDescriptor, callback, &anotherTask); + + // Verify task.renderPipeline and anotherTask.renderPipeline are both created correctly. + ValidateCreateRenderPipelineAsync(&task); + ValidateCreateRenderPipelineAsync(&anotherTask); + + // Verify task.renderPipeline and anotherTask.renderPipeline are pointing to the same Dawn + // object. + if (!UsesWire()) { + EXPECT_EQ(task.renderPipeline.Get(), anotherTask.renderPipeline.Get()); + } +} + // Verify calling CreateRenderPipelineAsync() with valid VertexBufferLayouts works on all backends. TEST_P(CreatePipelineAsyncTest, CreateRenderPipelineAsyncWithVertexBufferLayouts) { wgpu::TextureDescriptor textureDescriptor;