Allow readwrite storage texture in pipelinelayout matches writeonly in shader

This patch enables using read-write storage texture access in pipeline layout
and declaring the correspondent storage texture as write-only in shader.

Note that currently we still disallow read-write access in pipeline layout be
compatible with read-only in shader as on D3D12 read-write access in pipeline
layout will be implemented with an UAV, while read-only access in shader will
consume an SRV slot.

Bug: dawn:1972
Test: dawn_unittests, dawn_end2end_tests
Change-Id: I26387eb4c57cd850bc7672db9a5a3b68a4f7f8ef
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/150560
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 41b66a8..56d8a2c 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -371,6 +371,14 @@
     return requiredBufferSizes;
 }
 
+bool IsShaderCompatibleWithPipelineLayoutOnStorageTextureAccess(
+    const BindingInfo& bindingInfo,
+    const ShaderBindingInfo& shaderBindingInfo) {
+    return bindingInfo.storageTexture.access == shaderBindingInfo.storageTexture.access ||
+           (bindingInfo.storageTexture.access == wgpu::StorageTextureAccess::ReadWrite &&
+            shaderBindingInfo.storageTexture.access == wgpu::StorageTextureAccess::WriteOnly);
+}
+
 MaybeError ValidateCompatibilityOfSingleBindingWithLayout(const DeviceBase* device,
                                                           const BindGroupLayoutInternalBase* layout,
                                                           SingleShaderStage entryPointStage,
@@ -451,10 +459,11 @@
             ASSERT(layoutInfo.storageTexture.format != wgpu::TextureFormat::Undefined);
             ASSERT(shaderInfo.storageTexture.format != wgpu::TextureFormat::Undefined);
 
-            DAWN_INVALID_IF(layoutInfo.storageTexture.access != shaderInfo.storageTexture.access,
-                            "The layout's binding access (%s) isn't compatible with the shader's "
-                            "binding access (%s).",
-                            layoutInfo.storageTexture.access, shaderInfo.storageTexture.access);
+            DAWN_INVALID_IF(
+                !IsShaderCompatibleWithPipelineLayoutOnStorageTextureAccess(layoutInfo, shaderInfo),
+                "The layout's binding access (%s) isn't compatible with the shader's "
+                "binding access (%s).",
+                layoutInfo.storageTexture.access, shaderInfo.storageTexture.access);
 
             DAWN_INVALID_IF(layoutInfo.storageTexture.format != shaderInfo.storageTexture.format,
                             "The layout's binding format (%s) doesn't match the shader's binding "
diff --git a/src/dawn/tests/end2end/StorageTextureTests.cpp b/src/dawn/tests/end2end/StorageTextureTests.cpp
index 54098c9..d57d921 100644
--- a/src/dawn/tests/end2end/StorageTextureTests.cpp
+++ b/src/dawn/tests/end2end/StorageTextureTests.cpp
@@ -1291,6 +1291,61 @@
     CheckDrawsGreen(kSimpleVertexShader, fsstream.str().c_str(), readonlyStorageTexture);
 }
 
+// Verify using read-write storage texture access in pipeline layout is compatible with write-only
+// storage texture access in shader.
+TEST_P(ReadWriteStorageTextureTests, ReadWriteInPipelineLayoutAndWriteOnlyInShader) {
+    DAWN_TEST_UNSUPPORTED_IF(!IsReadWriteStorageTextureSupported());
+
+    constexpr wgpu::TextureFormat kStorageTextureFormat = wgpu::TextureFormat::R32Uint;
+    std::array<uint32_t, kWidth * kHeight> expectedData;
+    for (size_t i = 0; i < expectedData.size(); ++i) {
+        expectedData[i] = i + 1;
+    }
+
+    wgpu::Texture storageTexture = CreateTexture(
+        wgpu::TextureFormat::R32Uint,
+        wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, {kWidth, kHeight, 1});
+
+    std::ostringstream sstream;
+    sstream << R"(
+enable chromium_experimental_read_write_storage_texture;
+@group(0) @binding(0) var rwImage : texture_storage_2d<r32uint, write>;
+
+@compute @workgroup_size()"
+            << kWidth << ", " << kHeight << R"()
+fn main(
+  @builtin(local_invocation_id) local_id: vec3u,
+  @builtin(local_invocation_index) local_index : u32) {
+  let data1 = vec4u(local_index + 1u, 0, 0, 1);
+  textureStore(rwImage, vec2i(local_id.xy), data1);
+})";
+
+    wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout(
+        device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadWrite,
+                  kStorageTextureFormat, wgpu::TextureViewDimension::e2D}});
+    wgpu::ComputePipelineDescriptor computeDescriptor;
+    computeDescriptor.layout = utils::MakePipelineLayout(device, {bindGroupLayout});
+    computeDescriptor.compute.module = utils::CreateShaderModule(device, sstream.str().c_str());
+    computeDescriptor.compute.entryPoint = "main";
+    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&computeDescriptor);
+
+    wgpu::BindGroup bindGroup =
+        utils::MakeBindGroup(device, bindGroupLayout, {{0, storageTexture.CreateView()}});
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::ComputePassEncoder computePassEncoder = encoder.BeginComputePass();
+    computePassEncoder.SetBindGroup(0, bindGroup);
+    computePassEncoder.SetPipeline(pipeline);
+    computePassEncoder.DispatchWorkgroups(1);
+    computePassEncoder.End();
+    wgpu::CommandBuffer commandBuffer = encoder.Finish();
+    queue.Submit(1, &commandBuffer);
+
+    CheckOutputStorageTexture(storageTexture, wgpu::TextureFormat::R32Uint, {kWidth, kHeight},
+                              reinterpret_cast<const uint8_t*>(expectedData.data()),
+                              expectedData.size() * sizeof(uint32_t));
+}
+
 DAWN_INSTANTIATE_TEST(ReadWriteStorageTextureTests,
                       D3D11Backend(),
                       D3D12Backend(),
diff --git a/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp b/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp
index f7f1f3b..2dee034 100644
--- a/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/StorageTextureValidationTests.cpp
@@ -989,8 +989,9 @@
     }
 }
 
-// Test that storage texture access in shader must match the one in pipeline layout when we create
-// a pipeline with storage texture.
+// Test that storage texture access in shader must be compatible with the one in pipeline layout
+// when we create a pipeline with storage texture. Note that read-write storage texture access in
+// pipeline layout is compatible with write-only storage texture access in shader.
 TEST_F(ReadWriteStorageTextureValidationTests, StorageTextureAccessInPipeline) {
     constexpr std::array<wgpu::StorageTextureAccess, 3> kStorageTextureAccesses = {
         {wgpu::StorageTextureAccess::ReadOnly, wgpu::StorageTextureAccess::WriteOnly,
@@ -1010,7 +1011,9 @@
             computePipelineDescriptor.compute.entryPoint = "main";
             computePipelineDescriptor.layout =
                 utils::MakePipelineLayout(device, {{bindGroupLayout}});
-            if (accessInShader == accessInBindGroupLayout) {
+            if (accessInShader == accessInBindGroupLayout ||
+                (accessInShader == wgpu::StorageTextureAccess::WriteOnly &&
+                 accessInBindGroupLayout == wgpu::StorageTextureAccess::ReadWrite)) {
                 device.CreateComputePipeline(&computePipelineDescriptor);
             } else {
                 ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&computePipelineDescriptor));