Allow one texture as write-only storage and sampled in one compute pass

This patch enables one texture to be used as both write-only storage
texture and sampled texture in one compute pass.

Note that while we don't need to check the usage scope of a texture in
the whole compute pass scope, we still need to verify one texture cannot
be bound to multiple bindings that are used in one dispatch at the same
time. This check will be added in the following patches.

This patch also adds tests to ensure a texture can be used as the
following binding types in one compute pass:
- read-only storage + sampled
- write-only storage + sampled
- read-only storage + write-only storage

BUG=dawn:267
TEST=dawn_unittests

Change-Id: Ibff2b005a5269a0bfa254e0417de4920758add39
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/20120
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 23710cd..bb6109f 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -526,7 +526,7 @@
     RenderPassEncoder* CommandEncoder::BeginRenderPass(const RenderPassDescriptor* descriptor) {
         DeviceBase* device = GetDevice();
 
-        PassResourceUsageTracker usageTracker;
+        PassResourceUsageTracker usageTracker(PassType::Render);
         bool success =
             mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
                 uint32_t width = 0;
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index a5e765c..7fc8cae 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -317,9 +317,9 @@
 
             bool readOnly = (usage & kReadOnlyTextureUsages) == usage;
             bool singleUse = wgpu::HasZeroOrOneBits(usage);
-            if (!readOnly && !singleUse) {
+            if (pass.passType == PassType::Render && !readOnly && !singleUse) {
                 return DAWN_VALIDATION_ERROR(
-                    "Texture used as writable usage and another usage in pass");
+                    "Texture used as writable usage and another usage in render pass");
             }
         }
 
diff --git a/src/dawn_native/ComputePassEncoder.cpp b/src/dawn_native/ComputePassEncoder.cpp
index cd88c87..5e36601 100644
--- a/src/dawn_native/ComputePassEncoder.cpp
+++ b/src/dawn_native/ComputePassEncoder.cpp
@@ -25,14 +25,15 @@
     ComputePassEncoder::ComputePassEncoder(DeviceBase* device,
                                            CommandEncoder* commandEncoder,
                                            EncodingContext* encodingContext)
-        : ProgrammablePassEncoder(device, encodingContext), mCommandEncoder(commandEncoder) {
+        : ProgrammablePassEncoder(device, encodingContext, PassType::Compute),
+          mCommandEncoder(commandEncoder) {
     }
 
     ComputePassEncoder::ComputePassEncoder(DeviceBase* device,
                                            CommandEncoder* commandEncoder,
                                            EncodingContext* encodingContext,
                                            ErrorTag errorTag)
-        : ProgrammablePassEncoder(device, encodingContext, errorTag),
+        : ProgrammablePassEncoder(device, encodingContext, errorTag, PassType::Compute),
           mCommandEncoder(commandEncoder) {
     }
 
diff --git a/src/dawn_native/PassResourceUsage.h b/src/dawn_native/PassResourceUsage.h
index c2a2071..c9ff10f 100644
--- a/src/dawn_native/PassResourceUsage.h
+++ b/src/dawn_native/PassResourceUsage.h
@@ -25,10 +25,13 @@
     class BufferBase;
     class TextureBase;
 
+    enum class PassType { Render, Compute };
+
     // Which resources are used by pass and how they are used. The command buffer validation
     // pre-computes this information so that backends with explicit barriers don't have to
     // re-compute it.
     struct PassResourceUsage {
+        PassType passType;
         std::vector<BufferBase*> buffers;
         std::vector<wgpu::BufferUsage> bufferUsages;
 
diff --git a/src/dawn_native/PassResourceUsageTracker.cpp b/src/dawn_native/PassResourceUsageTracker.cpp
index 4831b36..0773150 100644
--- a/src/dawn_native/PassResourceUsageTracker.cpp
+++ b/src/dawn_native/PassResourceUsageTracker.cpp
@@ -18,6 +18,8 @@
 #include "dawn_native/Texture.h"
 
 namespace dawn_native {
+    PassResourceUsageTracker::PassResourceUsageTracker(PassType passType) : mPassType(passType) {
+    }
 
     void PassResourceUsageTracker::BufferUsedAs(BufferBase* buffer, wgpu::BufferUsage usage) {
         // std::map's operator[] will create the key and return 0 if the key didn't exist
@@ -34,6 +36,7 @@
     // Returns the per-pass usage for use by backends for APIs with explicit barriers.
     PassResourceUsage PassResourceUsageTracker::AcquireResourceUsage() {
         PassResourceUsage result;
+        result.passType = mPassType;
         result.buffers.reserve(mBufferUsages.size());
         result.bufferUsages.reserve(mBufferUsages.size());
         result.textures.reserve(mTextureUsages.size());
diff --git a/src/dawn_native/PassResourceUsageTracker.h b/src/dawn_native/PassResourceUsageTracker.h
index 458b175..eccb3eb 100644
--- a/src/dawn_native/PassResourceUsageTracker.h
+++ b/src/dawn_native/PassResourceUsageTracker.h
@@ -32,6 +32,7 @@
     // information.
     class PassResourceUsageTracker {
       public:
+        PassResourceUsageTracker(PassType passType);
         void BufferUsedAs(BufferBase* buffer, wgpu::BufferUsage usage);
         void TextureUsedAs(TextureBase* texture, wgpu::TextureUsage usage);
 
@@ -39,6 +40,7 @@
         PassResourceUsage AcquireResourceUsage();
 
       private:
+        PassType mPassType;
         std::map<BufferBase*, wgpu::BufferUsage> mBufferUsages;
         std::map<TextureBase*, wgpu::TextureUsage> mTextureUsages;
     };
diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp
index 6e29b43..93a9692 100644
--- a/src/dawn_native/ProgrammablePassEncoder.cpp
+++ b/src/dawn_native/ProgrammablePassEncoder.cpp
@@ -86,14 +86,16 @@
     }  // namespace
 
     ProgrammablePassEncoder::ProgrammablePassEncoder(DeviceBase* device,
-                                                     EncodingContext* encodingContext)
-        : ObjectBase(device), mEncodingContext(encodingContext) {
+                                                     EncodingContext* encodingContext,
+                                                     PassType passType)
+        : ObjectBase(device), mEncodingContext(encodingContext), mUsageTracker(passType) {
     }
 
     ProgrammablePassEncoder::ProgrammablePassEncoder(DeviceBase* device,
                                                      EncodingContext* encodingContext,
-                                                     ErrorTag errorTag)
-        : ObjectBase(device, errorTag), mEncodingContext(encodingContext) {
+                                                     ErrorTag errorTag,
+                                                     PassType passType)
+        : ObjectBase(device, errorTag), mEncodingContext(encodingContext), mUsageTracker(passType) {
     }
 
     void ProgrammablePassEncoder::InsertDebugMarker(const char* groupLabel) {
diff --git a/src/dawn_native/ProgrammablePassEncoder.h b/src/dawn_native/ProgrammablePassEncoder.h
index 17bfeb4..b7bd452 100644
--- a/src/dawn_native/ProgrammablePassEncoder.h
+++ b/src/dawn_native/ProgrammablePassEncoder.h
@@ -30,7 +30,9 @@
     // Base class for shared functionality between ComputePassEncoder and RenderPassEncoder.
     class ProgrammablePassEncoder : public ObjectBase {
       public:
-        ProgrammablePassEncoder(DeviceBase* device, EncodingContext* encodingContext);
+        ProgrammablePassEncoder(DeviceBase* device,
+                                EncodingContext* encodingContext,
+                                PassType passType);
 
         void InsertDebugMarker(const char* groupLabel);
         void PopDebugGroup();
@@ -45,7 +47,8 @@
         // Construct an "error" programmable pass encoder.
         ProgrammablePassEncoder(DeviceBase* device,
                                 EncodingContext* encodingContext,
-                                ErrorTag errorTag);
+                                ErrorTag errorTag,
+                                PassType passType);
 
         EncodingContext* mEncodingContext = nullptr;
         PassResourceUsageTracker mUsageTracker;
diff --git a/src/dawn_native/RenderEncoderBase.cpp b/src/dawn_native/RenderEncoderBase.cpp
index 532c448..aea8efe 100644
--- a/src/dawn_native/RenderEncoderBase.cpp
+++ b/src/dawn_native/RenderEncoderBase.cpp
@@ -27,7 +27,7 @@
 namespace dawn_native {
 
     RenderEncoderBase::RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext)
-        : ProgrammablePassEncoder(device, encodingContext),
+        : ProgrammablePassEncoder(device, encodingContext, PassType::Render),
           mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)),
           mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) {
     }
@@ -35,7 +35,7 @@
     RenderEncoderBase::RenderEncoderBase(DeviceBase* device,
                                          EncodingContext* encodingContext,
                                          ErrorTag errorTag)
-        : ProgrammablePassEncoder(device, encodingContext, errorTag),
+        : ProgrammablePassEncoder(device, encodingContext, errorTag, PassType::Render),
           mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)),
           mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) {
     }
diff --git a/src/tests/unittests/validation/StorageTextureValidationTests.cpp b/src/tests/unittests/validation/StorageTextureValidationTests.cpp
index 82d514c..4699d22 100644
--- a/src/tests/unittests/validation/StorageTextureValidationTests.cpp
+++ b/src/tests/unittests/validation/StorageTextureValidationTests.cpp
@@ -974,7 +974,7 @@
 
 // Verify it is invalid to use a a texture as both read-only storage texture and write-only storage
 // texture in one render pass.
-TEST_F(StorageTextureValidationTests, ReadOnlyStorageTextureAndWriteOnlyStorageTexture) {
+TEST_F(StorageTextureValidationTests, ReadOnlyAndWriteOnlyStorageTextureInOneRenderPass) {
     constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
     wgpu::Texture storageTexture = CreateTexture(wgpu::TextureUsage::Storage, kFormat);
 
@@ -993,7 +993,7 @@
         utils::MakeBindGroup(device, bindGroupLayout,
                              {{0, storageTexture.CreateView()}, {1, storageTexture.CreateView()}});
 
-    // It is invalid to use a a texture as both read-only storage texture and write-only storage
+    // It is invalid to use a texture as both read-only storage texture and write-only storage
     // texture in one render pass.
     wgpu::Texture outputAttachment = CreateTexture(wgpu::TextureUsage::OutputAttachment, kFormat);
     utils::ComboRenderPassDescriptor renderPassDescriptor({outputAttachment.CreateView()});
@@ -1003,3 +1003,66 @@
     renderPassEncoder.EndPass();
     ASSERT_DEVICE_ERROR(encoder.Finish());
 }
+
+// Verify it is valid to use a texture as both storage texture (read-only or write-only) and
+// sampled texture in one compute pass.
+TEST_F(StorageTextureValidationTests, StorageTextureAndSampledTextureInOneComputePass) {
+    constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
+    wgpu::Texture storageTexture =
+        CreateTexture(wgpu::TextureUsage::Storage | wgpu::TextureUsage::Sampled, kFormat);
+
+    for (wgpu::BindingType storageTextureType : kSupportedStorageTextureBindingTypes) {
+        // Create a bind group that binds the same texture as both storage texture and sampled
+        // texture.
+        wgpu::BindGroupLayout bindGroupLayout =
+            utils::MakeBindGroupLayout(device, {{.binding = 0,
+                                                 .visibility = wgpu::ShaderStage::Compute,
+                                                 .type = storageTextureType,
+                                                 .storageTextureFormat = kFormat},
+                                                {.binding = 1,
+                                                 .visibility = wgpu::ShaderStage::Compute,
+                                                 .type = wgpu::BindingType::SampledTexture,
+                                                 .storageTextureFormat = kFormat}});
+        wgpu::BindGroup bindGroup = utils::MakeBindGroup(
+            device, bindGroupLayout,
+            {{0, storageTexture.CreateView()}, {1, storageTexture.CreateView()}});
+
+        // It is valid to use a a texture as both storage texture (read-only or write-only) and
+        // sampled texture in one compute pass.
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder computePassEncoder = encoder.BeginComputePass();
+        computePassEncoder.SetBindGroup(0, bindGroup);
+        computePassEncoder.EndPass();
+        encoder.Finish();
+    }
+}
+
+// Verify it is valid to use a texture as both read-only storage texture and write-only storage
+// texture in one compute pass.
+TEST_F(StorageTextureValidationTests, ReadOnlyAndWriteOnlyStorageTextureInOneComputePass) {
+    constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
+    wgpu::Texture storageTexture = CreateTexture(wgpu::TextureUsage::Storage, kFormat);
+
+    // Create a bind group that uses the same texture as both read-only and write-only storage
+    // texture.
+    wgpu::BindGroupLayout bindGroupLayout =
+        utils::MakeBindGroupLayout(device, {{.binding = 0,
+                                             .visibility = wgpu::ShaderStage::Compute,
+                                             .type = wgpu::BindingType::ReadonlyStorageTexture,
+                                             .storageTextureFormat = kFormat},
+                                            {.binding = 1,
+                                             .visibility = wgpu::ShaderStage::Compute,
+                                             .type = wgpu::BindingType::WriteonlyStorageTexture,
+                                             .storageTextureFormat = kFormat}});
+    wgpu::BindGroup bindGroup =
+        utils::MakeBindGroup(device, bindGroupLayout,
+                             {{0, storageTexture.CreateView()}, {1, storageTexture.CreateView()}});
+
+    // It is valid to use a texture as both read-only storage texture and write-only storage
+    // texture in one compute pass.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::ComputePassEncoder computePassEncoder = encoder.BeginComputePass();
+    computePassEncoder.SetBindGroup(0, bindGroup);
+    computePassEncoder.EndPass();
+    encoder.Finish();
+}