Capture: Support vertex attributes

Bug: 454365240
Change-Id: I6a6a69646bc6948c26cd1d98ec9e4c40edd06a3f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/268674
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Auto-Submit: Gregg Tavares <gman@chromium.org>
Commit-Queue: Gregg Tavares <gman@chromium.org>
diff --git a/src/dawn/native/PipelineLayout.cpp b/src/dawn/native/PipelineLayout.cpp
index a49479a..3c0e18e 100644
--- a/src/dawn/native/PipelineLayout.cpp
+++ b/src/dawn/native/PipelineLayout.cpp
@@ -613,13 +613,16 @@
 }
 
 bool PipelineLayoutBase::IsImplicit() const {
+    uint32_t numBindGroups = 0;
     for (BindGroupIndex groupIndex : GetBindGroupLayoutsMask()) {
         const BindGroupLayoutBase* bgl = GetFrontendBindGroupLayout(groupIndex);
         if (bgl->GetPipelineCompatibilityToken() != kExplicitPCT) {
             return true;
         }
+        ++numBindGroups;
     }
-    return false;
+    // A pipeline layout with no bindgroups can be considered implicit.
+    return numBindGroups == 0;
 }
 
 uint32_t PipelineLayoutBase::GetImmediateDataRangeByteSize() const {
diff --git a/src/dawn/native/webgpu/CommandBufferWGPU.cpp b/src/dawn/native/webgpu/CommandBufferWGPU.cpp
index 5a3c61d..24a404d 100644
--- a/src/dawn/native/webgpu/CommandBufferWGPU.cpp
+++ b/src/dawn/native/webgpu/CommandBufferWGPU.cpp
@@ -655,6 +655,7 @@
                 DAWN_SKIP_COMMAND(PushDebugGroup)
                 DAWN_SKIP_COMMAND(WriteTimestamp)
                 DAWN_SKIP_COMMAND(SetImmediateData)
+                DAWN_SKIP_COMMAND(SetVertexBuffer)
             default: {
                 DAWN_CHECK(false);
                 break;
@@ -761,6 +762,19 @@
                 }
                 break;
             }
+            case Command::SetVertexBuffer: {
+                const auto& cmd = *commands.NextCommand<SetVertexBufferCmd>();
+                schema::RenderPassCommandSetVertexBufferCmd data{{
+                    .data = {{
+                        .slot = uint32_t(cmd.slot),
+                        .bufferId = captureContext.GetId(cmd.buffer),
+                        .offset = cmd.offset,
+                        .size = cmd.size,
+                    }},
+                }};
+                Serialize(captureContext, data);
+                break;
+            }
             case Command::Draw: {
                 const auto& cmd = *commands.NextCommand<DrawCmd>();
                 schema::RenderPassCommandDrawCmd data{{
diff --git a/src/dawn/native/webgpu/RenderPipelineWGPU.cpp b/src/dawn/native/webgpu/RenderPipelineWGPU.cpp
index 1307fea..2b31bd7 100644
--- a/src/dawn/native/webgpu/RenderPipelineWGPU.cpp
+++ b/src/dawn/native/webgpu/RenderPipelineWGPU.cpp
@@ -246,7 +246,19 @@
         const auto& info = GetVertexBuffer(slot);
 
         std::vector<schema::VertexAttribute> attributes;
-        // TODO(454365240): handle attributes
+
+        for (VertexAttributeLocation loc : GetAttributeLocationsUsed()) {
+            const VertexAttributeInfo& attrib = GetAttribute(loc);
+            // Only use the attributes that use the current input
+            if (attrib.vertexBufferSlot != slot) {
+                continue;
+            }
+            attributes.push_back({{
+                .format = attrib.format,
+                .offset = attrib.offset,
+                .shaderLocation = uint32_t(attrib.shaderLocation),
+            }});
+        }
 
         buffers.push_back({{
             .arrayStride = info.arrayStride,
diff --git a/src/dawn/replay/Replay.cpp b/src/dawn/replay/Replay.cpp
index 7cd05b1..ac155b9 100644
--- a/src/dawn/replay/Replay.cpp
+++ b/src/dawn/replay/Replay.cpp
@@ -29,6 +29,7 @@
 
 #include <algorithm>
 
+#include "dawn/common/Constants.h"
 #include "dawn/replay/Deserialization.h"
 
 namespace dawn::replay {
@@ -278,6 +279,26 @@
         ToWGPU(pipeline.fragment.program.constants);
     std::vector<wgpu::ColorTargetState> colorTargets;
     std::vector<wgpu::BlendState> blendStates(pipeline.fragment.targets.size());
+    std::vector<wgpu::VertexBufferLayout> buffers;
+
+    std::vector<wgpu::VertexAttribute> attributes(kMaxVertexAttributes);
+    uint32_t attributeCount = 0;
+
+    for (const auto& buffer : pipeline.vertex.buffers) {
+        const auto attributesForBuffer = &attributes[attributeCount];
+        for (const auto& attrib : buffer.attributes) {
+            auto& attr = attributes[attributeCount++];
+            attr.format = attrib.format;
+            attr.offset = attrib.offset;
+            attr.shaderLocation = attrib.shaderLocation;
+        }
+        buffers.push_back({
+            .stepMode = buffer.stepMode,
+            .arrayStride = buffer.arrayStride,
+            .attributeCount = buffer.attributes.size(),
+            .attributes = attributesForBuffer,
+        });
+    }
 
     wgpu::FragmentState* fragment = nullptr;
     wgpu::FragmentState fragmentState;
@@ -327,6 +348,8 @@
                 .entryPoint = wgpu::StringView(pipeline.vertex.program.entryPoint),
                 .constantCount = vertexConstants.size(),
                 .constants = vertexConstants.data(),
+                .bufferCount = buffers.size(),
+                .buffers = buffers.data(),
             },
         .primitive =
             {
@@ -480,6 +503,13 @@
                                   data.dynamicOffsets.size(), data.dynamicOffsets.data());
                 break;
             }
+            case schema::RenderPassCommand::SetVertexBuffer: {
+                schema::RenderPassCommandSetVertexBufferCmdData data;
+                DAWN_TRY(Deserialize(readHead, &data));
+                pass.SetVertexBuffer(data.slot, replay.GetObjectById<wgpu::Buffer>(data.bufferId),
+                                     data.offset, data.size);
+                break;
+            }
             case schema::RenderPassCommand::Draw: {
                 schema::RenderPassCommandDrawCmdData data;
                 DAWN_TRY(Deserialize(readHead, &data));
diff --git a/src/dawn/serialization/Schema.h b/src/dawn/serialization/Schema.h
index 80fbfef..9f999a9 100644
--- a/src/dawn/serialization/Schema.h
+++ b/src/dawn/serialization/Schema.h
@@ -480,6 +480,14 @@
 
 DAWN_REPLAY_MAKE_RENDER_PASS_CMD_AND_CMD_DATA(SetBindGroup, SET_BIND_GROUP_CMD_DATA_MEMBER){};
 
+#define SET_VERTEX_BUFFER_CMD_DATA_MEMBER(X) \
+    X(uint32_t, slot)                        \
+    X(ObjectId, bufferId)                    \
+    X(uint64_t, offset)                      \
+    X(uint64_t, size)
+
+DAWN_REPLAY_MAKE_RENDER_PASS_CMD_AND_CMD_DATA(SetVertexBuffer, SET_VERTEX_BUFFER_CMD_DATA_MEMBER){};
+
 #define DRAW_CMD_DATA_MEMBER(X) \
     X(uint32_t, vertexCount)    \
     X(uint32_t, instanceCount)  \
diff --git a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
index 18dd68f..5d92ec6 100644
--- a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
+++ b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
@@ -1306,6 +1306,83 @@
     }
 }
 
+TEST_P(CaptureAndReplayTests, CaptureRenderPassBasicWithAttributes) {
+    const float myVertices[] = {
+        -1, -1, 3, -1, -1, 3,
+    };
+
+    wgpu::BufferDescriptor descriptor;
+    descriptor.label = "vertexBuffer";
+    descriptor.size = sizeof(myVertices);
+    descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex;
+    wgpu::Buffer vertexBuffer = device.CreateBuffer(&descriptor);
+
+    queue.WriteBuffer(vertexBuffer, 0, &myVertices, sizeof(myVertices));
+
+    wgpu::TextureDescriptor textureDesc;
+    textureDesc.label = "dstTexture";
+    textureDesc.size = {1, 1, 1};
+    textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
+    textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
+    wgpu::Texture dstTexture = device.CreateTexture(&textureDesc);
+
+    const char* shader = R"(
+        @vertex fn vs(@location(0) pos: vec4f) -> @builtin(position) vec4f {
+            return pos;
+        }
+
+        @fragment fn fs() -> @location(0) vec4u {
+            return vec4u(0x11, 0x22, 0x33, 0x44);
+        }
+    )";
+    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::TriangleList;
+    desc.cBuffers[0].arrayStride = 2 * sizeof(float);
+    desc.cBuffers[0].attributeCount = 1;
+    desc.cBuffers[0].attributes = &desc.cAttributes[0];
+    desc.cAttributes[0].shaderLocation = 0;
+    desc.cAttributes[0].format = wgpu::VertexFormat::Float32x2;
+    desc.vertex.bufferCount = 1;
+    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
+
+    wgpu::CommandBuffer commands;
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+
+        utils::ComboRenderPassDescriptor passDescriptor({dstTexture.CreateView()});
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
+        pass.SetPipeline(pipeline);
+        pass.SetVertexBuffer(0, vertexBuffer);
+        pass.Draw(3);
+        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