Capture: Render Pass with pipeline/bindGroup

This is the next step up from a pass with no pipeline

Bug: 451389801
Change-Id: I6a6a696417e832fb0a478bece207c55af3a340b8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/268314
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Gregg Tavares <gman@chromium.org>
diff --git a/src/dawn/native/webgpu/BindGroupWGPU.cpp b/src/dawn/native/webgpu/BindGroupWGPU.cpp
index a5ceaa0..f48d7e7 100644
--- a/src/dawn/native/webgpu/BindGroupWGPU.cpp
+++ b/src/dawn/native/webgpu/BindGroupWGPU.cpp
@@ -135,7 +135,7 @@
     {
         BindGroupLayoutInternalBase* layout = GetLayout();
         const auto& bindingMap = layout->GetBindingMap();
-        for (const auto& [bindingNumbers, apiBindingIndex] : bindingMap) {
+        for (const auto& [bindingNumber, apiBindingIndex] : bindingMap) {
             BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
             const auto& bindingInfo = layout->GetAPIBindingInfo(apiBindingIndex);
 
@@ -160,13 +160,14 @@
     for (const auto& [bindingNumber, apiBindingIndex] : bindingMap) {
         BindingIndex bindingIndex = layout->AsBindingIndex(apiBindingIndex);
         const auto& bindingInfo = layout->GetAPIBindingInfo(apiBindingIndex);
+        uint32_t binding = uint32_t(bindingNumber);
 
         MatchVariant(
             bindingInfo.bindingLayout,
             [&](const BufferBindingInfo& info) {
                 const auto& entry = GetBindingAsBufferBinding(bindingIndex);
                 entries.push_back(schema::BindGroupEntry{{
-                    .binding = uint32_t(bindingNumber),
+                    .binding = binding,
                     .bufferId = captureContext.GetId(entry.buffer),
                     .offset = entry.offset,
                     .size = entry.size,
@@ -174,6 +175,17 @@
                     .textureViewId = 0,
                 }});
             },
+            [&](const TextureBindingInfo& info) {
+                const auto& entry = GetBindingAsTextureView(bindingIndex);
+                entries.push_back(schema::BindGroupEntry{{
+                    .binding = binding,
+                    .bufferId = 0,
+                    .offset = 0,
+                    .size = 0,
+                    .samplerId = 0,
+                    .textureViewId = captureContext.GetId(entry),
+                }});
+            },
             [&](const auto& info) { DAWN_CHECK(false); });
     }
 
diff --git a/src/dawn/replay/Replay.cpp b/src/dawn/replay/Replay.cpp
index 41c52f1..7cd05b1 100644
--- a/src/dawn/replay/Replay.cpp
+++ b/src/dawn/replay/Replay.cpp
@@ -117,6 +117,40 @@
     };
 }
 
+wgpu::StencilFaceState ToWGPU(const schema::StencilFaceState& state) {
+    return wgpu::StencilFaceState{
+        .compare = state.compare,
+        .failOp = state.failOp,
+        .depthFailOp = state.depthFailOp,
+        .passOp = state.passOp,
+    };
+}
+
+wgpu::BlendComponent ToWGPU(const schema::BlendComponent& component) {
+    return wgpu::BlendComponent{
+        .operation = component.operation,
+        .srcFactor = component.srcFactor,
+        .dstFactor = component.dstFactor,
+    };
+}
+
+wgpu::BlendState ToWGPU(const schema::BlendState& state) {
+    return wgpu::BlendState{
+        .color = ToWGPU(state.color),
+        .alpha = ToWGPU(state.alpha),
+    };
+}
+
+bool IsBlendComponentEnabled(const wgpu::BlendComponent& component) {
+    return component.operation != wgpu::BlendOperation::Add ||
+           component.srcFactor != wgpu::BlendFactor::One ||
+           component.dstFactor != wgpu::BlendFactor::Zero;
+}
+
+bool IsBlendEnabled(const wgpu::BlendState& blend) {
+    return IsBlendComponentEnabled(blend.color) || IsBlendComponentEnabled(blend.alpha);
+}
+
 MaybeError ReadContentIntoBuffer(ReadHead& readHead,
                                  wgpu::Device device,
                                  wgpu::Buffer buffer,
@@ -230,6 +264,92 @@
     return {computePipeline};
 }
 
+template <typename F>
+ResultOrError<wgpu::RenderPipeline> CreateRenderPipeline(const Replay& replay,
+                                                         wgpu::Device device,
+                                                         ReadHead& readHead,
+                                                         const std::string& label,
+                                                         F func) {
+    schema::RenderPipeline pipeline;
+    DAWN_TRY(Deserialize(readHead, &pipeline));
+
+    std::vector<wgpu::ConstantEntry> vertexConstants = ToWGPU(pipeline.vertex.program.constants);
+    std::vector<wgpu::ConstantEntry> fragmentConstants =
+        ToWGPU(pipeline.fragment.program.constants);
+    std::vector<wgpu::ColorTargetState> colorTargets;
+    std::vector<wgpu::BlendState> blendStates(pipeline.fragment.targets.size());
+
+    wgpu::FragmentState* fragment = nullptr;
+    wgpu::FragmentState fragmentState;
+    if (pipeline.fragment.program.moduleId) {
+        fragment = &fragmentState;
+        fragmentState.module =
+            replay.GetObjectById<wgpu::ShaderModule>(pipeline.fragment.program.moduleId);
+        fragmentState.entryPoint = wgpu::StringView(pipeline.fragment.program.entryPoint);
+        fragmentState.constantCount = fragmentConstants.size();
+        fragmentState.constants = fragmentConstants.data();
+        for (const auto& target : pipeline.fragment.targets) {
+            wgpu::BlendState& blend = blendStates[colorTargets.size()];
+            blend = ToWGPU(target.blend);
+            colorTargets.push_back({
+                .format = target.format,
+                .blend = IsBlendEnabled(blend) ? &blend : nullptr,
+                .writeMask = target.writeMask,
+            });
+        }
+        fragmentState.targetCount = colorTargets.size();
+        fragmentState.targets = colorTargets.data();
+    }
+
+    wgpu::DepthStencilState* depthStencil = nullptr;
+    wgpu::DepthStencilState depthStencilState;
+    if (pipeline.depthStencil.format != wgpu::TextureFormat::Undefined) {
+        depthStencil = &depthStencilState;
+        depthStencilState.format = pipeline.depthStencil.format;
+        depthStencilState.depthWriteEnabled = pipeline.depthStencil.depthWriteEnabled;
+        depthStencilState.depthCompare = pipeline.depthStencil.depthCompare;
+        depthStencilState.stencilFront = ToWGPU(pipeline.depthStencil.stencilFront);
+        depthStencilState.stencilBack = ToWGPU(pipeline.depthStencil.stencilBack);
+        depthStencilState.stencilReadMask = pipeline.depthStencil.stencilReadMask;
+        depthStencilState.stencilWriteMask = pipeline.depthStencil.stencilWriteMask;
+        depthStencilState.depthBias = pipeline.depthStencil.depthBias;
+        depthStencilState.depthBiasSlopeScale = pipeline.depthStencil.depthBiasSlopeScale;
+        depthStencilState.depthBiasClamp = pipeline.depthStencil.depthBiasClamp;
+    }
+
+    wgpu::RenderPipelineDescriptor desc{
+        .label = wgpu::StringView(label),
+        .layout = replay.GetObjectById<wgpu::PipelineLayout>(pipeline.layoutId),
+        .vertex =
+            {
+                .module =
+                    replay.GetObjectById<wgpu::ShaderModule>(pipeline.vertex.program.moduleId),
+                .entryPoint = wgpu::StringView(pipeline.vertex.program.entryPoint),
+                .constantCount = vertexConstants.size(),
+                .constants = vertexConstants.data(),
+            },
+        .primitive =
+            {
+                .topology = pipeline.primitive.topology,
+                .stripIndexFormat = pipeline.primitive.stripIndexFormat,
+                .frontFace = pipeline.primitive.frontFace,
+                .cullMode = pipeline.primitive.cullMode,
+                .unclippedDepth = pipeline.primitive.unclippedDepth,
+            },
+        .depthStencil = depthStencil,
+        .multisample =
+            {
+                .count = pipeline.multisample.count,
+                .mask = pipeline.multisample.mask,
+                .alphaToCoverageEnabled = pipeline.multisample.alphaToCoverageEnabled,
+            },
+        .fragment = fragment,
+    };
+    wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&desc);
+    func(renderPipeline, pipeline.groupIndexIds);
+    return {renderPipeline};
+}
+
 ResultOrError<wgpu::ShaderModule> CreateShaderModule(wgpu::Device device,
                                                      ReadHead& readHead,
                                                      const std::string& label) {
@@ -554,6 +674,25 @@
             return {};
         }
 
+        case schema::ObjectType::RenderPipeline: {
+            wgpu::RenderPipeline renderPipeline;
+            DAWN_TRY_ASSIGN(
+                renderPipeline,
+                CreateRenderPipeline(
+                    *this, device, readHead, resource.label,
+                    [this](wgpu::RenderPipeline& renderPipeline,
+                           const std::vector<schema::BindGroupLayoutIndexIdPair>& groupIndexIds) {
+                        // Register any implicit bindgroups.
+                        for (const auto& groupIndexId : groupIndexIds) {
+                            wgpu::BindGroupLayout bgl =
+                                renderPipeline.GetBindGroupLayout(groupIndexId.groupIndex);
+                            mResources.insert({groupIndexId.bindGroupLayoutId, {"", bgl}});
+                        }
+                    }));
+            mResources.insert({resource.id, {resource.label, renderPipeline}});
+            return {};
+        }
+
         case schema::ObjectType::ShaderModule: {
             wgpu::ShaderModule shaderModule;
             DAWN_TRY_ASSIGN(shaderModule, CreateShaderModule(device, readHead, resource.label));
@@ -629,8 +768,7 @@
                 break;
             }
             default: {
-                // UNIMPLEMENTED();
-                break;
+                return DAWN_INTERNAL_ERROR("unimplemented root command");
             }
         }
     }
diff --git a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
index b13007c..18dd68f 100644
--- a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
+++ b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
@@ -35,6 +35,7 @@
 #include "dawn/replay/Replay.h"
 #include "dawn/tests/DawnTest.h"
 #include "dawn/tests/MockCallback.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
 #include "dawn/utils/TestUtils.h"
 #include "dawn/utils/WGPUHelpers.h"
 
@@ -1223,6 +1224,88 @@
     }
 }
 
+// Capture and replay the a render pass where a texture is rendered into another.
+TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithBindGroup) {
+    const uint8_t myData[] = {0x11, 0x22, 0x33, 0x44};
+
+    wgpu::TextureDescriptor textureDesc;
+    textureDesc.label = "srcTexture";
+    textureDesc.size = {1, 1, 1};
+    textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
+    textureDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst;
+    wgpu::Texture srcTexture = device.CreateTexture(&textureDesc);
+
+    {
+        wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
+            utils::CreateTexelCopyBufferLayout(0, 4);
+        wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
+            utils::CreateTexelCopyTextureInfo(srcTexture, 0, {0, 0, 0}, wgpu::TextureAspect::All);
+        wgpu::Extent3D extent = {1, 1, 1};
+        queue.WriteTexture(&texelCopyTextureInfo, myData, sizeof(myData), &texelCopyBufferLayout,
+                           &extent);
+    }
+
+    textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
+    textureDesc.label = "dstTexture";
+    wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
+
+    const char* shader = R"(
+        @group(0) @binding(0) var tex: texture_2d<u32>;
+
+        @vertex fn vs() -> @builtin(position) vec4f {
+            return vec4f(0.0, 0.0, 0.0, 1.0);
+        }
+
+        @fragment fn fs() -> @location(0) vec4u {
+            return textureLoad(tex, vec2u(0), 0);
+        }
+    )";
+    auto module = utils::CreateShaderModule(device, shader);
+
+    utils::ComboRenderPipelineDescriptor desc;
+    desc.vertex.module = module;
+    desc.cFragment.module = module;
+    desc.cFragment.targetCount = 1;
+    desc.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
+    desc.primitive.topology = wgpu::PrimitiveTopology::PointList;
+    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
+
+    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+                                                     {
+                                                         {0, srcTexture.CreateView()},
+                                                     });
+    wgpu::CommandBuffer commands;
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+
+        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
+        pass.SetPipeline(pipeline);
+        pass.SetBindGroup(0, bindGroup);
+        pass.Draw(1);
+        pass.End();
+
+        commands = encoder.Finish();
+    }
+
+    // --- capture ---
+    auto recorder = Recorder::CreateAndStart(device);
+
+    queue.Submit(1, &commands);
+
+    // --- replay ---
+    auto capture = recorder.Finish();
+    auto replay = capture.Replay(device);
+
+    {
+        wgpu::Texture texture = replay->GetObjectByLabel<wgpu::Texture>("dstTexture");
+        ASSERT_NE(texture, nullptr);
+
+        uint8_t expected[] = {0x11, 0x22, 0x33, 0x44};
+        EXPECT_TEXTURE_EQ(&expected[0], texture, {0, 0}, {1, 1}, 0, wgpu::TextureAspect::All);
+    }
+}
+
 DAWN_INSTANTIATE_TEST(CaptureAndReplayTests, WebGPUBackend());
 
 }  // anonymous namespace