Add the entry point of CreateReadyRenderPipeline

BUG=dawn:529
TEST=dawn_end2end_tests

Change-Id: I42ac0edc77e5b6119eb374da72698fca14596f7b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/30540
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index 5e5fa34..6a3bb9a 100644
--- a/dawn.json
+++ b/dawn.json
@@ -519,6 +519,15 @@
             {"value": 3, "name": "unknown"}
         ]
     },
+    "create ready render pipeline callback": {
+        "category": "callback",
+        "args": [
+            {"name": "status", "type": "create ready pipeline status"},
+            {"name": "pipeline", "type": "render pipeline"},
+            {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"},
+            {"name": "userdata", "type": "void", "annotation": "*"}
+        ]
+    },
     "cull mode": {
         "category": "enum",
         "values": [
@@ -594,6 +603,15 @@
                 ]
             },
             {
+                "name": "create ready render pipeline",
+                "returns": "void",
+                "args": [
+                    {"name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"},
+                    {"name": "callback", "type": "create ready render pipeline callback"},
+                    {"name": "userdata", "type": "void", "annotation": "*"}
+                ]
+            },
+            {
                 "name": "create render bundle encoder",
                 "returns": "render bundle encoder",
                 "args": [
diff --git a/dawn_wire.json b/dawn_wire.json
index 8870932..505af37 100644
--- a/dawn_wire.json
+++ b/dawn_wire.json
@@ -45,6 +45,12 @@
             { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"},
             { "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"}
         ],
+        "device create ready render pipeline": [
+            { "name": "device", "type": "device" },
+            { "name": "request serial", "type": "uint64_t" },
+            { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "render pipeline"},
+            { "name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"}
+        ],
         "device pop error scope": [
             { "name": "device", "type": "device" },
             { "name": "request serial", "type": "uint64_t" }
@@ -85,7 +91,12 @@
         "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"}
+            { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" }
+        ],
+        "device create ready render 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"},
@@ -121,6 +132,7 @@
             "BufferGetMappedRange",
             "DeviceCreateBuffer",
             "DeviceCreateReadyComputePipeline",
+            "DeviceCreateReadyRenderPipeline",
             "DevicePopErrorScope",
             "DeviceSetDeviceLostCallback",
             "DeviceSetUncapturedErrorCallback",
diff --git a/generator/templates/mock_webgpu.cpp b/generator/templates/mock_webgpu.cpp
index 77f73be..10d3716 100644
--- a/generator/templates/mock_webgpu.cpp
+++ b/generator/templates/mock_webgpu.cpp
@@ -112,6 +112,18 @@
     OnDeviceCreateReadyComputePipelineCallback(self, descriptor, callback, userdata);
 }
 
+void ProcTableAsClass::DeviceCreateReadyRenderPipeline(
+    WGPUDevice self,
+    WGPURenderPipelineDescriptor const * descriptor,
+    WGPUCreateReadyRenderPipelineCallback callback,
+    void* userdata) {
+    auto object = reinterpret_cast<ProcTableAsClass::Object*>(self);
+    object->createReadyRenderPipelineCallback = callback;
+    object->userdata = userdata;
+
+    OnDeviceCreateReadyRenderPipelineCallback(self, descriptor, callback, userdata);
+}
+
 void ProcTableAsClass::CallDeviceErrorCallback(WGPUDevice device,
                                                WGPUErrorType type,
                                                const char* message) {
@@ -143,6 +155,14 @@
     object->createReadyComputePipelineCallback(status, pipeline, message, object->userdata);
 }
 
+void ProcTableAsClass::CallDeviceCreateReadyRenderPipelineCallback(WGPUDevice device,
+                                                                   WGPUCreateReadyPipelineStatus status,
+                                                                   WGPURenderPipeline pipeline,
+                                                                   const char* message) {
+    auto object = reinterpret_cast<ProcTableAsClass::Object*>(device);
+    object->createReadyRenderPipelineCallback(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 813f4bd..e3dfdbb 100644
--- a/generator/templates/mock_webgpu.h
+++ b/generator/templates/mock_webgpu.h
@@ -56,6 +56,10 @@
                                               WGPUComputePipelineDescriptor const * descriptor,
                                               WGPUCreateReadyComputePipelineCallback callback,
                                               void* userdata);
+        void DeviceCreateReadyRenderPipeline(WGPUDevice self,
+                                             WGPURenderPipelineDescriptor const * descriptor,
+                                             WGPUCreateReadyRenderPipelineCallback callback,
+                                             void* userdata);
         void DeviceSetUncapturedErrorCallback(WGPUDevice self,
                                     WGPUErrorCallback callback,
                                     void* userdata);
@@ -80,6 +84,11 @@
             WGPUComputePipelineDescriptor const * descriptor,
             WGPUCreateReadyComputePipelineCallback callback,
             void* userdata) = 0;
+        virtual void OnDeviceCreateReadyRenderPipelineCallback(
+            WGPUDevice device,
+            WGPURenderPipelineDescriptor const * descriptor,
+            WGPUCreateReadyRenderPipelineCallback callback,
+            void* userdata) = 0;
         virtual void OnDeviceSetUncapturedErrorCallback(WGPUDevice device,
                                               WGPUErrorCallback callback,
                                               void* userdata) = 0;
@@ -102,6 +111,10 @@
                                                           WGPUCreateReadyPipelineStatus status,
                                                           WGPUComputePipeline pipeline,
                                                           const char* message);
+        void CallDeviceCreateReadyRenderPipelineCallback(WGPUDevice device,
+                                                         WGPUCreateReadyPipelineStatus status,
+                                                         WGPURenderPipeline 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);
@@ -111,6 +124,7 @@
             ProcTableAsClass* procs = nullptr;
             WGPUErrorCallback deviceErrorCallback = nullptr;
             WGPUCreateReadyComputePipelineCallback createReadyComputePipelineCallback = nullptr;
+            WGPUCreateReadyRenderPipelineCallback createReadyRenderPipelineCallback = nullptr;
             WGPUDeviceLostCallback deviceLostCallback = nullptr;
             WGPUBufferMapCallback mapAsyncCallback = nullptr;
             WGPUFenceOnCompletionCallback fenceOnCompletionCallback = nullptr;
@@ -150,6 +164,12 @@
                      WGPUCreateReadyComputePipelineCallback callback,
                      void* userdata),
                     (override));
+        MOCK_METHOD(void,
+                    OnDeviceCreateReadyRenderPipelineCallback,
+                    (WGPUDevice device, WGPURenderPipelineDescriptor const * descriptor,
+                     WGPUCreateReadyRenderPipelineCallback 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/CreateReadyPipelineTracker.cpp b/src/dawn_native/CreateReadyPipelineTracker.cpp
index 9d4ccc3..cfc19cc 100644
--- a/src/dawn_native/CreateReadyPipelineTracker.cpp
+++ b/src/dawn_native/CreateReadyPipelineTracker.cpp
@@ -18,39 +18,72 @@
 
 namespace dawn_native {
 
+    CreateReadyPipelineTaskBase::CreateReadyPipelineTaskBase(void* userdata) : mUserData(userdata) {
+    }
+
+    CreateReadyPipelineTaskBase::~CreateReadyPipelineTaskBase() {
+    }
+
     CreateReadyComputePipelineTask::CreateReadyComputePipelineTask(
         ComputePipelineBase* pipeline,
         WGPUCreateReadyComputePipelineCallback callback,
         void* userdata)
-        : mPipeline(pipeline), mCallback(callback), mUserData(userdata) {
-    }
-
-    CreateReadyComputePipelineTask::~CreateReadyComputePipelineTask() {
+        : CreateReadyPipelineTaskBase(userdata),
+          mPipeline(pipeline),
+          mCreateReadyComputePipelineCallback(callback) {
     }
 
     void CreateReadyComputePipelineTask::Finish() {
-        mCallback(WGPUCreateReadyPipelineStatus_Success,
-                  reinterpret_cast<WGPUComputePipeline>(mPipeline), "", mUserData);
+        ASSERT(mPipeline != nullptr);
+        ASSERT(mCreateReadyComputePipelineCallback != nullptr);
+
+        mCreateReadyComputePipelineCallback(WGPUCreateReadyPipelineStatus_Success,
+                                            reinterpret_cast<WGPUComputePipeline>(mPipeline), "",
+                                            mUserData);
+
+        // Set mCreateReadyComputePipelineCallback to nullptr in case it is called more than once.
+        mCreateReadyComputePipelineCallback = nullptr;
+    }
+
+    CreateReadyRenderPipelineTask::CreateReadyRenderPipelineTask(
+        RenderPipelineBase* pipeline,
+        WGPUCreateReadyRenderPipelineCallback callback,
+        void* userdata)
+        : CreateReadyPipelineTaskBase(userdata),
+          mPipeline(pipeline),
+          mCreateReadyRenderPipelineCallback(callback) {
+    }
+
+    void CreateReadyRenderPipelineTask::Finish() {
+        ASSERT(mPipeline != nullptr);
+        ASSERT(mCreateReadyRenderPipelineCallback != nullptr);
+
+        mCreateReadyRenderPipelineCallback(WGPUCreateReadyPipelineStatus_Success,
+                                           reinterpret_cast<WGPURenderPipeline>(mPipeline), "",
+                                           mUserData);
+
+        // Set mCreateReadyPipelineCallback to nullptr in case it is called more than once.
+        mCreateReadyRenderPipelineCallback = nullptr;
     }
 
     CreateReadyPipelineTracker::CreateReadyPipelineTracker(DeviceBase* device) : mDevice(device) {
     }
 
     CreateReadyPipelineTracker::~CreateReadyPipelineTracker() {
-        ASSERT(mCreateReadyComputePipelineTasksInFlight.Empty());
+        ASSERT(mCreateReadyPipelineTasksInFlight.Empty());
     }
 
-    void CreateReadyPipelineTracker::TrackTask(std::unique_ptr<CreateReadyComputePipelineTask> task,
+    void CreateReadyPipelineTracker::TrackTask(std::unique_ptr<CreateReadyPipelineTaskBase> task,
                                                ExecutionSerial serial) {
-        mCreateReadyComputePipelineTasksInFlight.Enqueue(std::move(task), serial);
+        mCreateReadyPipelineTasksInFlight.Enqueue(std::move(task), serial);
         mDevice->AddFutureSerial(serial);
     }
 
     void CreateReadyPipelineTracker::Tick(ExecutionSerial finishedSerial) {
-        for (auto& task : mCreateReadyComputePipelineTasksInFlight.IterateUpTo(finishedSerial)) {
+        for (auto& task : mCreateReadyPipelineTasksInFlight.IterateUpTo(finishedSerial)) {
             task->Finish();
         }
-        mCreateReadyComputePipelineTasksInFlight.ClearUpTo(finishedSerial);
+        mCreateReadyPipelineTasksInFlight.ClearUpTo(finishedSerial);
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/CreateReadyPipelineTracker.h b/src/dawn_native/CreateReadyPipelineTracker.h
index b85c8c2..0c6b1dc 100644
--- a/src/dawn_native/CreateReadyPipelineTracker.h
+++ b/src/dawn_native/CreateReadyPipelineTracker.h
@@ -25,19 +25,41 @@
 
     class ComputePipelineBase;
     class DeviceBase;
+    class PipelineBase;
+    class RenderPipelineBase;
 
-    struct CreateReadyComputePipelineTask {
+    struct CreateReadyPipelineTaskBase {
+        CreateReadyPipelineTaskBase(void* userData);
+        virtual ~CreateReadyPipelineTaskBase();
+
+        virtual void Finish() = 0;
+
+      protected:
+        void* mUserData;
+    };
+
+    struct CreateReadyComputePipelineTask final : public CreateReadyPipelineTaskBase {
         CreateReadyComputePipelineTask(ComputePipelineBase* pipeline,
                                        WGPUCreateReadyComputePipelineCallback callback,
                                        void* userdata);
-        ~CreateReadyComputePipelineTask();
 
-        void Finish();
+        void Finish() final;
 
       private:
         ComputePipelineBase* mPipeline;
-        WGPUCreateReadyComputePipelineCallback mCallback;
-        void* mUserData;
+        WGPUCreateReadyComputePipelineCallback mCreateReadyComputePipelineCallback;
+    };
+
+    struct CreateReadyRenderPipelineTask final : public CreateReadyPipelineTaskBase {
+        CreateReadyRenderPipelineTask(RenderPipelineBase* pipeline,
+                                      WGPUCreateReadyRenderPipelineCallback callback,
+                                      void* userdata);
+
+        void Finish() final;
+
+      private:
+        RenderPipelineBase* mPipeline;
+        WGPUCreateReadyRenderPipelineCallback mCreateReadyRenderPipelineCallback;
     };
 
     class CreateReadyPipelineTracker {
@@ -45,14 +67,13 @@
         CreateReadyPipelineTracker(DeviceBase* device);
         ~CreateReadyPipelineTracker();
 
-        void TrackTask(std::unique_ptr<CreateReadyComputePipelineTask> task,
-                       ExecutionSerial serial);
+        void TrackTask(std::unique_ptr<CreateReadyPipelineTaskBase> task, ExecutionSerial serial);
         void Tick(ExecutionSerial finishedSerial);
 
       private:
         DeviceBase* mDevice;
-        SerialQueue<ExecutionSerial, std::unique_ptr<CreateReadyComputePipelineTask>>
-            mCreateReadyComputePipelineTasksInFlight;
+        SerialQueue<ExecutionSerial, std::unique_ptr<CreateReadyPipelineTaskBase>>
+            mCreateReadyPipelineTasksInFlight;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 5ae6e46..2c5b3aa 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -674,6 +674,22 @@
 
         return result;
     }
+    void DeviceBase::CreateReadyRenderPipeline(const RenderPipelineDescriptor* descriptor,
+                                               WGPUCreateReadyRenderPipelineCallback callback,
+                                               void* userdata) {
+        RenderPipelineBase* result = nullptr;
+        MaybeError maybeError = CreateRenderPipelineInternal(&result, descriptor);
+        if (maybeError.IsError()) {
+            std::unique_ptr<ErrorData> error = maybeError.AcquireError();
+            callback(WGPUCreateReadyPipelineStatus_Error, nullptr, error->GetMessage().c_str(),
+                     userdata);
+            return;
+        }
+
+        std::unique_ptr<CreateReadyRenderPipelineTask> request =
+            std::make_unique<CreateReadyRenderPipelineTask>(result, callback, userdata);
+        mCreateReadyPipelineTracker->TrackTask(std::move(request), GetPendingCommandSerial());
+    }
     RenderBundleEncoder* DeviceBase::CreateRenderBundleEncoder(
         const RenderBundleEncoderDescriptor* descriptor) {
         RenderBundleEncoder* result = nullptr;
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index eab0763..494c431 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -149,6 +149,9 @@
         void CreateReadyComputePipeline(const ComputePipelineDescriptor* descriptor,
                                         WGPUCreateReadyComputePipelineCallback callback,
                                         void* userdata);
+        void CreateReadyRenderPipeline(const RenderPipelineDescriptor* descriptor,
+                                       WGPUCreateReadyRenderPipelineCallback callback,
+                                       void* userdata);
         RenderBundleEncoder* CreateRenderBundleEncoder(
             const RenderBundleEncoderDescriptor* descriptor);
         RenderPipelineBase* CreateRenderPipeline(const RenderPipelineDescriptor* descriptor);
diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp
index 338863e..cd9b5ab 100644
--- a/src/dawn_wire/client/ClientDoers.cpp
+++ b/src/dawn_wire/client/ClientDoers.cpp
@@ -85,8 +85,13 @@
     bool Client::DoDeviceCreateReadyComputePipelineCallback(uint64_t requestSerial,
                                                             WGPUCreateReadyPipelineStatus status,
                                                             const char* message) {
-        mDevice->OnCreateReadyComputePipelineCallback(requestSerial, status, message);
-        return true;
+        return mDevice->OnCreateReadyComputePipelineCallback(requestSerial, status, message);
+    }
+
+    bool Client::DoDeviceCreateReadyRenderPipelineCallback(uint64_t requestSerial,
+                                                           WGPUCreateReadyPipelineStatus status,
+                                                           const char* message) {
+        return mDevice->OnCreateReadyRenderPipelineCallback(requestSerial, status, message);
     }
 
 }}  // namespace dawn_wire::client
diff --git a/src/dawn_wire/client/Device.cpp b/src/dawn_wire/client/Device.cpp
index fd175fe..b49f8a4 100644
--- a/src/dawn_wire/client/Device.cpp
+++ b/src/dawn_wire/client/Device.cpp
@@ -43,10 +43,18 @@
             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);
+        auto createReadyPipelineRequests = std::move(mCreateReadyPipelineRequests);
+        for (const auto& it : createReadyPipelineRequests) {
+            if (it.second.createReadyComputePipelineCallback != nullptr) {
+                it.second.createReadyComputePipelineCallback(WGPUCreateReadyPipelineStatus_Unknown,
+                                                             nullptr, "Device destroyed",
+                                                             it.second.userdata);
+            } else {
+                ASSERT(it.second.createReadyRenderPipelineCallback != nullptr);
+                it.second.createReadyRenderPipelineCallback(WGPUCreateReadyPipelineStatus_Unknown,
+                                                            nullptr, "Device destroyed",
+                                                            it.second.userdata);
+            }
         }
 
         // Destroy the default queue
@@ -170,33 +178,32 @@
         cmd.device = ToAPI(this);
         cmd.descriptor = descriptor;
 
-        uint64_t serial = mCreateReadyComputePipelineRequestSerial++;
-        ASSERT(mCreateReadyComputePipelineRequests.find(serial) ==
-               mCreateReadyComputePipelineRequests.end());
+        uint64_t serial = mCreateReadyPipelineRequestSerial++;
+        ASSERT(mCreateReadyPipelineRequests.find(serial) == mCreateReadyPipelineRequests.end());
         cmd.requestSerial = serial;
 
         auto* allocation = GetClient()->ComputePipelineAllocator().New(this);
-        CreateReadyComputePipelineRequest request = {};
-        request.callback = callback;
+        CreateReadyPipelineRequest request = {};
+        request.createReadyComputePipelineCallback = 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);
+        mCreateReadyPipelineRequests[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()) {
+        const auto& requestIt = mCreateReadyPipelineRequests.find(requestSerial);
+        if (requestIt == mCreateReadyPipelineRequests.end()) {
             return false;
         }
 
-        CreateReadyComputePipelineRequest request = std::move(requestIt->second);
-        mCreateReadyComputePipelineRequests.erase(requestIt);
+        CreateReadyPipelineRequest request = std::move(requestIt->second);
+        mCreateReadyPipelineRequests.erase(requestIt);
 
         auto pipelineAllocation =
             GetClient()->ComputePipelineAllocator().GetObject(request.pipelineObjectID);
@@ -205,14 +212,64 @@
         // 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);
+            request.createReadyComputePipelineCallback(status, nullptr, message, request.userdata);
             return true;
         }
 
         WGPUComputePipeline pipeline = reinterpret_cast<WGPUComputePipeline>(pipelineAllocation);
-        request.callback(status, pipeline, message, request.userdata);
+        request.createReadyComputePipelineCallback(status, pipeline, message, request.userdata);
 
         return true;
     }
 
+    void Device::CreateReadyRenderPipeline(WGPURenderPipelineDescriptor const* descriptor,
+                                           WGPUCreateReadyRenderPipelineCallback callback,
+                                           void* userdata) {
+        DeviceCreateReadyRenderPipelineCmd cmd;
+        cmd.device = ToAPI(this);
+        cmd.descriptor = descriptor;
+
+        uint64_t serial = mCreateReadyPipelineRequestSerial++;
+        ASSERT(mCreateReadyPipelineRequests.find(serial) == mCreateReadyPipelineRequests.end());
+        cmd.requestSerial = serial;
+
+        auto* allocation = GetClient()->RenderPipelineAllocator().New(this);
+        CreateReadyPipelineRequest request = {};
+        request.createReadyRenderPipelineCallback = callback;
+        request.userdata = userdata;
+        request.pipelineObjectID = allocation->object->id;
+
+        cmd.pipelineObjectHandle = ObjectHandle(allocation->object->id, allocation->generation);
+        GetClient()->SerializeCommand(cmd);
+
+        mCreateReadyPipelineRequests[serial] = std::move(request);
+    }
+
+    bool Device::OnCreateReadyRenderPipelineCallback(uint64_t requestSerial,
+                                                     WGPUCreateReadyPipelineStatus status,
+                                                     const char* message) {
+        const auto& requestIt = mCreateReadyPipelineRequests.find(requestSerial);
+        if (requestIt == mCreateReadyPipelineRequests.end()) {
+            return false;
+        }
+
+        CreateReadyPipelineRequest request = std::move(requestIt->second);
+        mCreateReadyPipelineRequests.erase(requestIt);
+
+        auto pipelineAllocation =
+            GetClient()->RenderPipelineAllocator().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()->RenderPipelineAllocator().Free(pipelineAllocation);
+            request.createReadyRenderPipelineCallback(status, nullptr, message, request.userdata);
+            return true;
+        }
+
+        WGPURenderPipeline pipeline = reinterpret_cast<WGPURenderPipeline>(pipelineAllocation);
+        request.createReadyRenderPipelineCallback(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 087c72c..7d14c6f 100644
--- a/src/dawn_wire/client/Device.h
+++ b/src/dawn_wire/client/Device.h
@@ -43,6 +43,9 @@
         void CreateReadyComputePipeline(WGPUComputePipelineDescriptor const* descriptor,
                                         WGPUCreateReadyComputePipelineCallback callback,
                                         void* userdata);
+        void CreateReadyRenderPipeline(WGPURenderPipelineDescriptor const* descriptor,
+                                       WGPUCreateReadyRenderPipelineCallback callback,
+                                       void* userdata);
 
         void HandleError(WGPUErrorType errorType, const char* message);
         void HandleDeviceLost(const char* message);
@@ -52,6 +55,9 @@
         bool OnCreateReadyComputePipelineCallback(uint64_t requestSerial,
                                                   WGPUCreateReadyPipelineStatus status,
                                                   const char* message);
+        bool OnCreateReadyRenderPipelineCallback(uint64_t requestSerial,
+                                                 WGPUCreateReadyPipelineStatus status,
+                                                 const char* message);
 
         WGPUQueue GetDefaultQueue();
 
@@ -64,13 +70,14 @@
         uint64_t mErrorScopeRequestSerial = 0;
         uint64_t mErrorScopeStackSize = 0;
 
-        struct CreateReadyComputePipelineRequest {
-            WGPUCreateReadyComputePipelineCallback callback = nullptr;
+        struct CreateReadyPipelineRequest {
+            WGPUCreateReadyComputePipelineCallback createReadyComputePipelineCallback = nullptr;
+            WGPUCreateReadyRenderPipelineCallback createReadyRenderPipelineCallback = nullptr;
             void* userdata = nullptr;
             ObjectId pipelineObjectID;
         };
-        std::map<uint64_t, CreateReadyComputePipelineRequest> mCreateReadyComputePipelineRequests;
-        uint64_t mCreateReadyComputePipelineRequestSerial = 0;
+        std::map<uint64_t, CreateReadyPipelineRequest> mCreateReadyPipelineRequests;
+        uint64_t mCreateReadyPipelineRequestSerial = 0;
 
         Client* mClient = nullptr;
         WGPUErrorCallback mErrorCallback = nullptr;
diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h
index acf321a..4e725a8 100644
--- a/src/dawn_wire/server/Server.h
+++ b/src/dawn_wire/server/Server.h
@@ -99,6 +99,10 @@
                                                       WGPUComputePipeline pipeline,
                                                       const char* message,
                                                       void* userdata);
+        static void ForwardCreateReadyRenderPipeline(WGPUCreateReadyPipelineStatus status,
+                                                     WGPURenderPipeline pipeline,
+                                                     const char* message,
+                                                     void* userdata);
 
         // Error callbacks
         void OnUncapturedError(WGPUErrorType type, const char* message);
@@ -115,6 +119,10 @@
                                                   WGPUComputePipeline pipeline,
                                                   const char* message,
                                                   CreateReadyPipelineUserData* userdata);
+        void OnCreateReadyRenderPipelineCallback(WGPUCreateReadyPipelineStatus status,
+                                                 WGPURenderPipeline 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 9975562..b3accf8 100644
--- a/src/dawn_wire/server/ServerDevice.cpp
+++ b/src/dawn_wire/server/ServerDevice.cpp
@@ -36,6 +36,16 @@
             status, pipeline, message, createReadyPipelineUserData);
     }
 
+    void Server::ForwardCreateReadyRenderPipeline(WGPUCreateReadyPipelineStatus status,
+                                                  WGPURenderPipeline pipeline,
+                                                  const char* message,
+                                                  void* userdata) {
+        CreateReadyPipelineUserData* createReadyPipelineUserData =
+            static_cast<CreateReadyPipelineUserData*>(userdata);
+        createReadyPipelineUserData->server->OnCreateReadyRenderPipelineCallback(
+            status, pipeline, message, createReadyPipelineUserData);
+    }
+
     void Server::OnUncapturedError(WGPUErrorType type, const char* message) {
         ReturnDeviceUncapturedErrorCallbackCmd cmd;
         cmd.type = type;
@@ -105,6 +115,47 @@
         SerializeCommand(cmd);
     }
 
+    bool Server::DoDeviceCreateReadyRenderPipeline(WGPUDevice cDevice,
+                                                   uint64_t requestSerial,
+                                                   ObjectHandle pipelineObjectHandle,
+                                                   const WGPURenderPipelineDescriptor* descriptor) {
+        auto* resultData = RenderPipelineObjects().Allocate(pipelineObjectHandle.id);
+        if (resultData == nullptr) {
+            return false;
+        }
+
+        resultData->generation = pipelineObjectHandle.generation;
+
+        std::unique_ptr<CreateReadyPipelineUserData> userdata =
+            std::make_unique<CreateReadyPipelineUserData>();
+        userdata->server = this;
+        userdata->requestSerial = requestSerial;
+        userdata->pipelineObjectID = pipelineObjectHandle.id;
+
+        mProcs.deviceCreateReadyRenderPipeline(
+            cDevice, descriptor, ForwardCreateReadyRenderPipeline, userdata.release());
+        return true;
+    }
+
+    void Server::OnCreateReadyRenderPipelineCallback(WGPUCreateReadyPipelineStatus status,
+                                                     WGPURenderPipeline pipeline,
+                                                     const char* message,
+                                                     CreateReadyPipelineUserData* userdata) {
+        std::unique_ptr<CreateReadyPipelineUserData> data(userdata);
+        if (status != WGPUCreateReadyPipelineStatus_Success) {
+            RenderPipelineObjects().Free(data->pipelineObjectID);
+        } else {
+            RenderPipelineObjects().Get(data->pipelineObjectID)->handle = pipeline;
+        }
+
+        ReturnDeviceCreateReadyRenderPipelineCallbackCmd 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<ErrorScopeUserdata*>(userdata);
diff --git a/src/tests/end2end/CreateReadyPipelineTests.cpp b/src/tests/end2end/CreateReadyPipelineTests.cpp
index 89845fb..60c4b06 100644
--- a/src/tests/end2end/CreateReadyPipelineTests.cpp
+++ b/src/tests/end2end/CreateReadyPipelineTests.cpp
@@ -14,11 +14,13 @@
 
 #include "tests/DawnTest.h"
 
+#include "utils/ComboRenderPipelineDescriptor.h"
 #include "utils/WGPUHelpers.h"
 
 namespace {
     struct CreateReadyPipelineTask {
-        wgpu::ComputePipeline computePipeline;
+        wgpu::ComputePipeline computePipeline = nullptr;
+        wgpu::RenderPipeline renderPipeline = nullptr;
         bool isCompleted = false;
         std::string message;
     };
@@ -130,6 +132,133 @@
     ASSERT_EQ(nullptr, task.computePipeline.Get());
 }
 
+// Verify the basic use of CreateReadyRenderPipeline() works on all backends.
+TEST_P(CreateReadyPipelineTest, BasicUseOfCreateReadyRenderPipeline) {
+    constexpr wgpu::TextureFormat kOutputAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm;
+
+    const char* vertexShader = R"(
+        #version 450
+        void main() {
+            gl_Position = vec4(0.f, 0.f, 0.f, 1.f);
+            gl_PointSize = 1.0f;
+        })";
+    const char* fragmentShader = R"(
+        #version 450
+        layout(location = 0) out vec4 o_color;
+        void main() {
+            o_color = vec4(0.f, 1.f, 0.f, 1.f);
+        })";
+
+    utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vertexShader);
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fragmentShader);
+    renderPipelineDescriptor.vertexStage.module = vsModule;
+    renderPipelineDescriptor.cFragmentStage.module = fsModule;
+    renderPipelineDescriptor.cColorStates[0].format = kOutputAttachmentFormat;
+    renderPipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::PointList;
+
+    CreateReadyPipelineTask task;
+    device.CreateReadyRenderPipeline(
+        &renderPipelineDescriptor,
+        [](WGPUCreateReadyPipelineStatus status, WGPURenderPipeline returnPipeline,
+           const char* message, void* userdata) {
+            ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Success, status);
+
+            CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
+            task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline);
+            task->isCompleted = true;
+            task->message = message;
+        },
+        &task);
+
+    wgpu::TextureDescriptor textureDescriptor;
+    textureDescriptor.size = {1, 1, 1};
+    textureDescriptor.format = kOutputAttachmentFormat;
+    textureDescriptor.usage = wgpu::TextureUsage::OutputAttachment | 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);
+}
+
+// Verify CreateReadyRenderPipeline() works as expected when there is any error that happens during
+// the creation of the render pipeline. The SPEC requires that during the call of
+// CreateReadyRenderPipeline() any error won't be forwarded to the error scope / unhandled error
+// callback.
+TEST_P(CreateReadyPipelineTest, CreateRenderPipelineFailed) {
+    DAWN_SKIP_TEST_IF(IsDawnValidationSkipped());
+
+    constexpr wgpu::TextureFormat kOutputAttachmentFormat = wgpu::TextureFormat::Depth32Float;
+
+    const char* vertexShader = R"(
+        #version 450
+        void main() {
+            gl_Position = vec4(0.f, 0.f, 0.f, 1.f);
+            gl_PointSize = 1.0f;
+        })";
+    const char* fragmentShader = R"(
+        #version 450
+        layout(location = 0) out vec4 o_color;
+        void main() {
+            o_color = vec4(0.f, 1.f, 0.f, 1.f);
+        })";
+
+    utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
+    wgpu::ShaderModule vsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vertexShader);
+    wgpu::ShaderModule fsModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fragmentShader);
+    renderPipelineDescriptor.vertexStage.module = vsModule;
+    renderPipelineDescriptor.cFragmentStage.module = fsModule;
+    renderPipelineDescriptor.cColorStates[0].format = kOutputAttachmentFormat;
+    renderPipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::PointList;
+
+    CreateReadyPipelineTask task;
+    device.CreateReadyRenderPipeline(
+        &renderPipelineDescriptor,
+        [](WGPUCreateReadyPipelineStatus status, WGPURenderPipeline returnPipeline,
+           const char* message, void* userdata) {
+            ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Error, status);
+
+            CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
+            task->renderPipeline = wgpu::RenderPipeline::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(),
diff --git a/src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp b/src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp
index 6d699c0..98fb1f8 100644
--- a/src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp
+++ b/src/tests/unittests/wire/WireCreateReadyPipelineTests.cpp
@@ -39,6 +39,25 @@
         mockCreateReadyComputePipelineCallback->Call(status, pipeline, message, userdata);
     }
 
+    class MockCreateReadyRenderPipelineCallback {
+      public:
+        MOCK_METHOD(void,
+                    Call,
+                    (WGPUCreateReadyPipelineStatus status,
+                     WGPURenderPipeline pipeline,
+                     const char* message,
+                     void* userdata));
+    };
+
+    std::unique_ptr<StrictMock<MockCreateReadyRenderPipelineCallback>>
+        mockCreateReadyRenderPipelineCallback;
+    void ToMockCreateReadyRenderPipelineCallback(WGPUCreateReadyPipelineStatus status,
+                                                 WGPURenderPipeline pipeline,
+                                                 const char* message,
+                                                 void* userdata) {
+        mockCreateReadyRenderPipelineCallback->Call(status, pipeline, message, userdata);
+    }
+
 }  // anonymous namespace
 
 class WireCreateReadyPipelineTest : public WireTest {
@@ -48,6 +67,8 @@
 
         mockCreateReadyComputePipelineCallback =
             std::make_unique<StrictMock<MockCreateReadyComputePipelineCallback>>();
+        mockCreateReadyRenderPipelineCallback =
+            std::make_unique<StrictMock<MockCreateReadyRenderPipelineCallback>>();
     }
 
     void TearDown() override {
@@ -55,6 +76,7 @@
 
         // Delete mock so that expectations are checked
         mockCreateReadyComputePipelineCallback = nullptr;
+        mockCreateReadyRenderPipelineCallback = nullptr;
     }
 
     void FlushClient() {
@@ -125,3 +147,69 @@
 
     FlushServer();
 }
+
+// Test when creating a render pipeline with CreateReadyRenderPipeline() successfully.
+TEST_F(WireCreateReadyPipelineTest, CreateReadyRenderPipelineSuccess) {
+    WGPUShaderModuleDescriptor vertexDescriptor = {};
+    WGPUShaderModule vsModule = wgpuDeviceCreateShaderModule(device, &vertexDescriptor);
+    WGPUShaderModule apiVsModule = api.GetNewShaderModule();
+    EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _)).WillOnce(Return(apiVsModule));
+
+    WGPURenderPipelineDescriptor pipelineDescriptor{};
+    pipelineDescriptor.vertexStage.module = vsModule;
+    pipelineDescriptor.vertexStage.entryPoint = "main";
+
+    WGPUProgrammableStageDescriptor fragmentStage = {};
+    fragmentStage.module = vsModule;
+    fragmentStage.entryPoint = "main";
+    pipelineDescriptor.fragmentStage = &fragmentStage;
+
+    wgpuDeviceCreateReadyRenderPipeline(device, &pipelineDescriptor,
+                                        ToMockCreateReadyRenderPipelineCallback, this);
+    EXPECT_CALL(api, OnDeviceCreateReadyRenderPipelineCallback(apiDevice, _, _, _))
+        .WillOnce(InvokeWithoutArgs([&]() {
+            api.CallDeviceCreateReadyRenderPipelineCallback(
+                apiDevice, WGPUCreateReadyPipelineStatus_Success, nullptr, "");
+        }));
+
+    FlushClient();
+
+    EXPECT_CALL(*mockCreateReadyRenderPipelineCallback,
+                Call(WGPUCreateReadyPipelineStatus_Success, _, StrEq(""), this))
+        .Times(1);
+
+    FlushServer();
+}
+
+// Test when creating a render pipeline with CreateReadyRenderPipeline() results in an error.
+TEST_F(WireCreateReadyPipelineTest, CreateReadyRenderPipelineError) {
+    WGPUShaderModuleDescriptor vertexDescriptor = {};
+    WGPUShaderModule vsModule = wgpuDeviceCreateShaderModule(device, &vertexDescriptor);
+    WGPUShaderModule apiVsModule = api.GetNewShaderModule();
+    EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _)).WillOnce(Return(apiVsModule));
+
+    WGPURenderPipelineDescriptor pipelineDescriptor{};
+    pipelineDescriptor.vertexStage.module = vsModule;
+    pipelineDescriptor.vertexStage.entryPoint = "main";
+
+    WGPUProgrammableStageDescriptor fragmentStage = {};
+    fragmentStage.module = vsModule;
+    fragmentStage.entryPoint = "main";
+    pipelineDescriptor.fragmentStage = &fragmentStage;
+
+    wgpuDeviceCreateReadyRenderPipeline(device, &pipelineDescriptor,
+                                        ToMockCreateReadyRenderPipelineCallback, this);
+    EXPECT_CALL(api, OnDeviceCreateReadyRenderPipelineCallback(apiDevice, _, _, _))
+        .WillOnce(InvokeWithoutArgs([&]() {
+            api.CallDeviceCreateReadyRenderPipelineCallback(
+                apiDevice, WGPUCreateReadyPipelineStatus_Error, nullptr, "Some error message");
+        }));
+
+    FlushClient();
+
+    EXPECT_CALL(*mockCreateReadyRenderPipelineCallback,
+                Call(WGPUCreateReadyPipelineStatus_Error, _, StrEq("Some error message"), this))
+        .Times(1);
+
+    FlushServer();
+}