D3D12: Support creating render pipeline asynchronously

This patch implements the asynchronous path of CreateRenderPipelineAsync
on D3D12 backend.
1. Call the constructor of dawn_native::d3d12::RenderPipeline in main
   thread.
2. Execute dawn_native::RenderPipelineBase::Initialize() (a virtual function)
   asynchronously.
3. Ensure every operation in dawn_native::d3d12::RenderPipeline::Initialize()
   is thread-safe.
4. Save all the return values (pipeline object or error message, userdata, etc)
   in a CreateRenderPipelineAsyncWaitableCallbackTask object and insert this
   callback task into CallbackTaskManager.
5. In Callback.Finish():
- Insert the pipeline object into the pipeline cache if necessary
- Call WGPUCreateRenderPipelineAsyncCallback

This patch also removes FlatRenderPipelineDescriptor as it is not needed
right now.

BUG=dawn:529
TEST=dawn_end2end_tests

Change-Id: I7fd30339ab7bea599c483dea4bd1100359982409
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/64440
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/CreatePipelineAsyncTask.cpp b/src/dawn_native/CreatePipelineAsyncTask.cpp
index 6a35b94..fae8ad2 100644
--- a/src/dawn_native/CreatePipelineAsyncTask.cpp
+++ b/src/dawn_native/CreatePipelineAsyncTask.cpp
@@ -106,7 +106,7 @@
         size_t blueprintHash,
         WGPUCreateComputePipelineAsyncCallback callback,
         void* userdata)
-        : mComputePipeline(nonInitializedComputePipeline),
+        : mComputePipeline(std::move(nonInitializedComputePipeline)),
           mBlueprintHash(blueprintHash),
           mCallback(callback),
           mUserdata(userdata) {
@@ -139,4 +139,41 @@
         device->GetAsyncTaskManager()->PostTask(std::move(asyncTask));
     }
 
+    CreateRenderPipelineAsyncTask::CreateRenderPipelineAsyncTask(
+        Ref<RenderPipelineBase> 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<CreateRenderPipelineAsyncTask> 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<CreateRenderPipelineAsyncTask> 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 34456aa..33e3e95 100644
--- a/src/dawn_native/CreatePipelineAsyncTask.h
+++ b/src/dawn_native/CreatePipelineAsyncTask.h
@@ -52,17 +52,17 @@
         WGPUCreateComputePipelineAsyncCallback mCreateComputePipelineAsyncCallback;
     };
 
-    struct CreateRenderPipelineAsyncCallbackTask final : CreatePipelineAsyncCallbackTaskBase {
+    struct CreateRenderPipelineAsyncCallbackTask : CreatePipelineAsyncCallbackTaskBase {
         CreateRenderPipelineAsyncCallbackTask(Ref<RenderPipelineBase> pipeline,
                                               std::string errorMessage,
                                               WGPUCreateRenderPipelineAsyncCallback callback,
                                               void* userdata);
 
-        void Finish() final;
+        void Finish() override;
         void HandleShutDown() final;
         void HandleDeviceLoss() final;
 
-      private:
+      protected:
         Ref<RenderPipelineBase> mPipeline;
         WGPUCreateRenderPipelineAsyncCallback mCreateRenderPipelineAsyncCallback;
     };
@@ -76,18 +76,37 @@
                                        WGPUCreateComputePipelineAsyncCallback callback,
                                        void* userdata);
 
-        virtual ~CreateComputePipelineAsyncTask() = default;
         void Run();
 
         static void RunAsync(std::unique_ptr<CreateComputePipelineAsyncTask> task);
 
-      protected:
+      private:
         Ref<ComputePipelineBase> 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<RenderPipelineBase> nonInitializedRenderPipeline,
+                                      size_t blueprintHash,
+                                      WGPUCreateRenderPipelineAsyncCallback callback,
+                                      void* userdata);
+
+        void Run();
+
+        static void RunAsync(std::unique_ptr<CreateRenderPipelineAsyncTask> task);
+
+      private:
+        Ref<RenderPipelineBase> 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 3696adb..7ade6ab 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -1338,9 +1338,8 @@
             // 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 @@
             }
 
             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 @@
                 std::move(pipeline), errorMessage, callback, userdata, blueprintHash));
     }
 
+    void DeviceBase::AddRenderPipelineAsyncCallbackTask(
+        Ref<RenderPipelineBase> 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<RenderPipelineBase> 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<CreateRenderPipelineAsyncWaitableCallbackTask>(
+                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 dd59f4f..3688ee1 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -299,6 +299,11 @@
                                                  WGPUCreateComputePipelineAsyncCallback callback,
                                                  void* userdata,
                                                  size_t blueprintHash);
+        void AddRenderPipelineAsyncCallbackTask(Ref<RenderPipelineBase> 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 b417deb..7bdfd0a 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -364,72 +364,6 @@
         }
     }  // 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 @@
 
         return true;
     }
+
+    MaybeError RenderPipelineBase::Initialize() {
+        return {};
+    }
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h
index 35840b3..ea618da 100644
--- a/src/dawn_native/RenderPipeline.h
+++ b/src/dawn_native/RenderPipeline.h
@@ -29,31 +29,6 @@
 
     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<PipelineLayoutBase> mLayout;
-
-        Ref<ShaderModuleBase> mVertexModule;
-        std::string mVertexEntryPoint;
-        std::array<VertexBufferLayout, kMaxVertexBuffers> mVertexBuffers;
-        std::array<VertexAttribute, kMaxVertexAttributes> mVertexAttributes;
-
-        FragmentState mFragmentState;
-        Ref<ShaderModuleBase> mFragmentModule;
-        std::string mFragmentEntryPoint;
-        std::array<ColorTargetState, kMaxColorAttachments> mColorTargetStates;
-        std::array<BlendState, kMaxColorAttachments> mBlendStates;
-
-        DepthStencilState mDepthStencilState;
-    };
-
     MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device,
                                                 const RenderPipelineDescriptor* descriptor);
 
@@ -130,6 +105,11 @@
       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 c8c9762..97d5f10 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -382,6 +382,12 @@
                                                 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<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
         std::unique_ptr<StagingBufferBase> stagingBuffer =
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 8430969..cefa780 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -177,6 +177,10 @@
                                             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 e30fdb0..75de2b3 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 @@
         return inputLayoutDescriptor;
     }
 
+    void RenderPipeline::CreateAsync(Device* device,
+                                     const RenderPipelineDescriptor* descriptor,
+                                     size_t blueprintHash,
+                                     WGPUCreateRenderPipelineAsyncCallback callback,
+                                     void* userdata) {
+        Ref<RenderPipeline> pipeline = AcquireRef(new RenderPipeline(device, descriptor));
+        std::unique_ptr<CreateRenderPipelineAsyncTask> asyncTask =
+            std::make_unique<CreateRenderPipelineAsyncTask>(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 ed71da9..199107f 100644
--- a/src/dawn_native/d3d12/RenderPipelineD3D12.h
+++ b/src/dawn_native/d3d12/RenderPipelineD3D12.h
@@ -29,6 +29,11 @@
         static ResultOrError<Ref<RenderPipeline>> 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 @@
       private:
         ~RenderPipeline() override;
         using RenderPipelineBase::RenderPipelineBase;
-        MaybeError Initialize();
+        MaybeError Initialize() override;
         D3D12_INPUT_LAYOUT_DESC ComputeInputLayout(
             std::array<D3D12_INPUT_ELEMENT_DESC, kMaxVertexAttributes>* inputElementDescriptors);
 
diff --git a/src/dawn_native/metal/RenderPipelineMTL.h b/src/dawn_native/metal/RenderPipelineMTL.h
index 07a2d77..1d81072 100644
--- a/src/dawn_native/metal/RenderPipelineMTL.h
+++ b/src/dawn_native/metal/RenderPipelineMTL.h
@@ -47,7 +47,7 @@
 
       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 3c7a4d3..1881fcd 100644
--- a/src/dawn_native/opengl/RenderPipelineGL.h
+++ b/src/dawn_native/opengl/RenderPipelineGL.h
@@ -42,7 +42,7 @@
       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 fe653c8..6a9207f 100644
--- a/src/dawn_native/vulkan/RenderPipelineVk.h
+++ b/src/dawn_native/vulkan/RenderPipelineVk.h
@@ -38,7 +38,7 @@
       private:
         ~RenderPipeline() override;
         using RenderPipelineBase::RenderPipelineBase;
-        MaybeError Initialize();
+        MaybeError Initialize() override;
 
         struct PipelineVertexInputStateCreateInfoTemporaryAllocations {
             std::array<VkVertexInputBindingDescription, kMaxVertexBuffers> bindings;
diff --git a/src/tests/end2end/CreatePipelineAsyncTests.cpp b/src/tests/end2end/CreatePipelineAsyncTests.cpp
index eab8bc2..d8b8af9 100644
--- a/src/tests/end2end/CreatePipelineAsyncTests.cpp
+++ b/src/tests/end2end/CreatePipelineAsyncTests.cpp
@@ -68,6 +68,47 @@
         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 @@
 
     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 @@
 
 // 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 @@
     }
 }
 
+// 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<f32> {
+            return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+        })");
+    wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
+        [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
+            return vec4<f32>(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<CreatePipelineAsyncTask*>(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;