Capture: Capture Storage Texture

Bug: 451460562
Change-Id: I6a6a69646247fb1f6b41299b1280cb1545359fa3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/270174
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Gregg Tavares <gman@chromium.org>
diff --git a/src/dawn/native/webgpu/BindGroupLayoutWGPU.cpp b/src/dawn/native/webgpu/BindGroupLayoutWGPU.cpp
index 2d45dd0..6b60248 100644
--- a/src/dawn/native/webgpu/BindGroupLayoutWGPU.cpp
+++ b/src/dawn/native/webgpu/BindGroupLayoutWGPU.cpp
@@ -129,6 +129,18 @@
                 Serialize(captureContext, entry);
                 return {};
             },
+            [&](const StorageTextureBindingInfo& info) -> MaybeError {
+                schema::BindGroupLayoutEntryTypeStorageTextureBinding entry{{
+                    .binding = binding,
+                    .data{{
+                        .format = info.format,
+                        .viewDimension = info.viewDimension,
+                        .access = info.access,
+                    }},
+                }};
+                Serialize(captureContext, entry);
+                return {};
+            },
             [&](const auto& info) -> MaybeError {
                 return DAWN_INTERNAL_ERROR("Unsupported bind layout entry type");
             }));
diff --git a/src/dawn/native/webgpu/BindGroupWGPU.cpp b/src/dawn/native/webgpu/BindGroupWGPU.cpp
index 9586948..68c7af3 100644
--- a/src/dawn/native/webgpu/BindGroupWGPU.cpp
+++ b/src/dawn/native/webgpu/BindGroupWGPU.cpp
@@ -140,6 +140,9 @@
 
             DAWN_TRY(MatchVariant(
                 bindingInfo.bindingLayout,
+                [&](const StorageTextureBindingInfo& info) -> MaybeError {
+                    return captureContext.AddResource(GetBindingAsTextureView(bindingIndex));
+                },
                 [&](const TextureBindingInfo& info) -> MaybeError {
                     return captureContext.AddResource(GetBindingAsTextureView(bindingIndex));
                 },
@@ -184,6 +187,16 @@
                 }};
                 Serialize(captureContext, data);
             },
+            [&](const StorageTextureBindingInfo& info) {
+                const auto& entry = GetBindingAsTextureView(bindingIndex);
+                schema::BindGroupEntryTypeTextureBinding data{{
+                    .binding = binding,
+                    .data{{
+                        .textureViewId = captureContext.GetId(entry),
+                    }},
+                }};
+                Serialize(captureContext, data);
+            },
             [&](const TextureBindingInfo& info) {
                 const auto& entry = GetBindingAsTextureView(bindingIndex);
                 schema::BindGroupEntryTypeTextureBinding data{{
diff --git a/src/dawn/replay/Replay.cpp b/src/dawn/replay/Replay.cpp
index ee98bc3..eea4b7d 100644
--- a/src/dawn/replay/Replay.cpp
+++ b/src/dawn/replay/Replay.cpp
@@ -265,6 +265,23 @@
                 });
                 break;
             }
+            case schema::BindGroupLayoutEntryType::StorageTextureBinding: {
+                schema::BindGroupLayoutEntryTypeStorageTextureBindingData data;
+                DAWN_TRY(Deserialize(readHead, &data));
+
+                entries.push_back({
+                    .binding = binding.binding,
+                    .visibility = binding.visibility,
+                    .bindingArraySize = binding.bindingArraySize,
+                    .storageTexture =
+                        {
+                            .access = data.access,
+                            .format = data.format,
+                            .viewDimension = data.viewDimension,
+                        },
+                });
+                break;
+            }
             default:
                 return DAWN_INTERNAL_ERROR("unhandled bind group layout entry type");
         }
diff --git a/src/dawn/serialization/Schema.h b/src/dawn/serialization/Schema.h
index d0ccaab..df96c75 100644
--- a/src/dawn/serialization/Schema.h
+++ b/src/dawn/serialization/Schema.h
@@ -259,6 +259,14 @@
 
 DAWN_REPLAY_MAKE_BINDGROUP_LAYOUT_VARIANT(BufferBinding, BUFFER_BIND_GROUP_LAYOUT_MEMBER){};
 
+#define STORAGE_TEXTURE_BIND_GROUP_LAYOUT_MEMBER(X) \
+    X(wgpu::TextureFormat, format)                  \
+    X(wgpu::TextureViewDimension, viewDimension)    \
+    X(wgpu::StorageTextureAccess, access)
+
+DAWN_REPLAY_MAKE_BINDGROUP_LAYOUT_VARIANT(StorageTextureBinding,
+                                          STORAGE_TEXTURE_BIND_GROUP_LAYOUT_MEMBER){};
+
 #define BIND_GROUP_LAYOUT_MEMBER(X) X(uint32_t, numEntries)
 
 DAWN_REPLAY_SERIALIZABLE(struct, BindGroupLayout, BIND_GROUP_LAYOUT_MEMBER){};
diff --git a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
index 8d24aa3..0dec83f 100644
--- a/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
+++ b/src/dawn/tests/white_box/CaptureAndReplayTests.cpp
@@ -1457,6 +1457,80 @@
     }
 }
 
+// Capture and replay a pass that uses a storage texture
+TEST_P(CaptureAndReplayTests, CaptureStorageTextureUsageWithExplicitBindGroupLayout) {
+    wgpu::BindGroupLayoutEntry entries[1];
+    entries[0].binding = 0;
+    entries[0].visibility = wgpu::ShaderStage::Compute;
+    entries[0].storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
+    entries[0].storageTexture.format = wgpu::TextureFormat::RGBA8Uint;
+
+    wgpu::BindGroupLayoutDescriptor bglDesc;
+    bglDesc.entryCount = 1;
+    bglDesc.entries = entries;
+    wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&bglDesc);
+
+    wgpu::PipelineLayoutDescriptor plDesc;
+    plDesc.bindGroupLayoutCount = 1;
+    plDesc.bindGroupLayouts = &layout;
+    wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&plDesc);
+
+    wgpu::TextureDescriptor textureDesc;
+    textureDesc.label = "myTexture";
+    textureDesc.size = {1, 1, 1};
+    textureDesc.format = wgpu::TextureFormat::RGBA8Uint;
+    textureDesc.usage = wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc;
+    wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+    const char* shader = R"(
+        @group(0) @binding(0) var tex: texture_storage_2d<rgba8uint, write>;
+
+        @compute @workgroup_size(1) fn main() {
+            textureStore(tex, vec2u(0), vec4u(0x11, 0x22, 0x33, 0x44));
+        }
+    )";
+    auto module = utils::CreateShaderModule(device, shader);
+
+    wgpu::ComputePipelineDescriptor csDesc;
+    csDesc.layout = pipelineLayout;
+    csDesc.compute.module = module;
+    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
+
+    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+                                                     {
+                                                         {0, texture.CreateView()},
+                                                     });
+
+    wgpu::CommandBuffer commands;
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.SetBindGroup(0, bindGroup);
+        pass.SetPipeline(pipeline);
+        pass.DispatchWorkgroups(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>("myTexture");
+        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