Support Storage Textures as Valid Binding Types

This patch adds the basic validation of read-only storage texture,
write-only storage texture and read-write storage texture as new
binding types with no bind group layout provided in the creation of
pipeline state objects.

- Read-only storage textures can be used in vertex, fragment and
  compute shaders.
- Write-only storage textures can only be used in compute shaders
  due to the limitation on Metal.
- Read-write storage textures are not allowed now and they are
  reserved to be supported as an extension in the future.

BUG=dawn:267
TEST=dawn_unittests

Change-Id: Iffc432f29a855b85d59451cb3c50269e03b84627
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16661
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index f09523e..6a4f381 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -171,6 +171,7 @@
     "src/dawn_native/BackendConnection.h",
     "src/dawn_native/BindGroup.cpp",
     "src/dawn_native/BindGroup.h",
+    "src/dawn_native/BindGroupAndStorageBarrierTracker.h",
     "src/dawn_native/BindGroupLayout.cpp",
     "src/dawn_native/BindGroupLayout.h",
     "src/dawn_native/BindGroupTracker.h",
@@ -863,6 +864,7 @@
     "src/tests/unittests/validation/RenderPipelineValidationTests.cpp",
     "src/tests/unittests/validation/SamplerValidationTests.cpp",
     "src/tests/unittests/validation/ShaderModuleValidationTests.cpp",
+    "src/tests/unittests/validation/StorageTextureValidationTests.cpp",
     "src/tests/unittests/validation/TextureValidationTests.cpp",
     "src/tests/unittests/validation/TextureViewValidationTests.cpp",
     "src/tests/unittests/validation/ToggleValidationTests.cpp",
diff --git a/dawn.json b/dawn.json
index 434ad14..bec9c9e 100644
--- a/dawn.json
+++ b/dawn.json
@@ -114,7 +114,9 @@
             {"value": 2, "name": "readonly storage buffer"},
             {"value": 3, "name": "sampler"},
             {"value": 4, "name": "sampled texture"},
-            {"value": 5, "name": "storage texture"}
+            {"value": 5, "name": "storage texture"},
+            {"value": 6, "name": "readonly storage texture"},
+            {"value": 7, "name": "writeonly storage texture"}
         ]
     },
     "blend descriptor": {
diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp
index 9e85be1..fe8166f 100644
--- a/src/dawn_native/BindGroup.cpp
+++ b/src/dawn_native/BindGroup.cpp
@@ -159,6 +159,12 @@
                 case wgpu::BindingType::Sampler:
                     DAWN_TRY(ValidateSamplerBinding(device, binding));
                     break;
+                // TODO(jiawei.shao@intel.com): support creating bind group with read-only and
+                // write-only storage textures.
+                case wgpu::BindingType::ReadonlyStorageTexture:
+                case wgpu::BindingType::WriteonlyStorageTexture:
+                    return DAWN_VALIDATION_ERROR(
+                        "Readonly and writeonly storage textures are not supported.");
                 case wgpu::BindingType::StorageTexture:
                     UNREACHABLE();
                     break;
diff --git a/src/dawn_native/BindGroupAndStorageBarrierTracker.h b/src/dawn_native/BindGroupAndStorageBarrierTracker.h
index 0c016e1..06fc268 100644
--- a/src/dawn_native/BindGroupAndStorageBarrierTracker.h
+++ b/src/dawn_native/BindGroupAndStorageBarrierTracker.h
@@ -15,9 +15,8 @@
 #ifndef DAWNNATIVE_BINDGROUPANDSTORAGEBARRIERTRACKER_H_
 #define DAWNNATIVE_BINDGROUPANDSTORAGEBARRIERTRACKER_H_
 
-#include "dawn_native/BindGroupTracker.h"
-
 #include "dawn_native/BindGroup.h"
+#include "dawn_native/BindGroupTracker.h"
 
 namespace dawn_native {
 
@@ -62,6 +61,8 @@
                             break;
 
                         case wgpu::BindingType::StorageTexture:
+                        case wgpu::BindingType::ReadonlyStorageTexture:
+                        case wgpu::BindingType::WriteonlyStorageTexture:
                             // Not implemented.
 
                         default:
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index d16330a..c583137 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -14,22 +14,45 @@
 
 #include "dawn_native/BindGroupLayout.h"
 
+#include <functional>
+
 #include "common/BitSetIterator.h"
 #include "common/HashUtils.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/ValidationUtils_autogen.h"
 
-#include <functional>
-
 namespace dawn_native {
 
     MaybeError ValidateBindingTypeWithShaderStageVisibility(
         wgpu::BindingType bindingType,
         wgpu::ShaderStage shaderStageVisibility) {
-        if (bindingType == wgpu::BindingType::StorageBuffer &&
-            (shaderStageVisibility & wgpu::ShaderStage::Vertex) != 0) {
-            return DAWN_VALIDATION_ERROR(
-                "storage buffer binding is not supported in vertex shader");
+        // TODO(jiawei.shao@intel.com): support read-write storage textures.
+        switch (bindingType) {
+            case wgpu::BindingType::StorageBuffer: {
+                if ((shaderStageVisibility & wgpu::ShaderStage::Vertex) != 0) {
+                    return DAWN_VALIDATION_ERROR(
+                        "storage buffer binding is not supported in vertex shader");
+                }
+            } break;
+
+            case wgpu::BindingType::WriteonlyStorageTexture: {
+                if ((shaderStageVisibility &
+                     (wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment)) != 0) {
+                    return DAWN_VALIDATION_ERROR(
+                        "write-only storage texture binding is only supported in compute shader");
+                }
+            } break;
+
+            case wgpu::BindingType::StorageTexture: {
+                return DAWN_VALIDATION_ERROR("Read-write storage texture binding is not supported");
+            } break;
+
+            case wgpu::BindingType::UniformBuffer:
+            case wgpu::BindingType::ReadonlyStorageBuffer:
+            case wgpu::BindingType::Sampler:
+            case wgpu::BindingType::SampledTexture:
+            case wgpu::BindingType::ReadonlyStorageTexture:
+                break;
         }
 
         return {};
@@ -78,6 +101,8 @@
                     break;
                 case wgpu::BindingType::SampledTexture:
                 case wgpu::BindingType::Sampler:
+                case wgpu::BindingType::ReadonlyStorageTexture:
+                case wgpu::BindingType::WriteonlyStorageTexture:
                     if (binding.hasDynamicOffset) {
                         return DAWN_VALIDATION_ERROR("Samplers and textures cannot be dynamic");
                     }
@@ -105,7 +130,7 @@
         }
 
         return {};
-    }
+    }  // namespace dawn_native
 
     namespace {
         size_t HashBindingInfo(const BindGroupLayoutBase::LayoutBindingInfo& info) {
@@ -171,6 +196,8 @@
                     case wgpu::BindingType::SampledTexture:
                     case wgpu::BindingType::Sampler:
                     case wgpu::BindingType::StorageTexture:
+                    case wgpu::BindingType::ReadonlyStorageTexture:
+                    case wgpu::BindingType::WriteonlyStorageTexture:
                         UNREACHABLE();
                         break;
                 }
diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt
index e90f790..b3e4a89 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -31,6 +31,7 @@
     "BackendConnection.h"
     "BindGroup.cpp"
     "BindGroup.h"
+    "BindGroupAndStorageBarrierTracker.h"
     "BindGroupLayout.cpp"
     "BindGroupLayout.h"
     "BindGroupTracker.h"
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp
index 5cdf781..6f22685 100644
--- a/src/dawn_native/PipelineLayout.cpp
+++ b/src/dawn_native/PipelineLayout.cpp
@@ -33,6 +33,31 @@
                    lhs.textureComponentType == rhs.textureComponentType;
         }
 
+        wgpu::ShaderStage GetShaderStageVisibilityWithBindingType(wgpu::BindingType bindingType) {
+            // TODO(jiawei.shao@intel.com): support read-only and read-write storage textures.
+            switch (bindingType) {
+                case wgpu::BindingType::StorageBuffer:
+                    return wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute;
+
+                case wgpu::BindingType::WriteonlyStorageTexture:
+                    return wgpu::ShaderStage::Compute;
+
+                case wgpu::BindingType::StorageTexture:
+                    UNREACHABLE();
+                    return wgpu::ShaderStage::None;
+
+                case wgpu::BindingType::UniformBuffer:
+                case wgpu::BindingType::ReadonlyStorageBuffer:
+                case wgpu::BindingType::Sampler:
+                case wgpu::BindingType::SampledTexture:
+                case wgpu::BindingType::ReadonlyStorageTexture:
+                    return wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment |
+                           wgpu::ShaderStage::Compute;
+            }
+
+            return {};
+        }
+
     }  // anonymous namespace
 
     MaybeError ValidatePipelineLayoutDescriptor(DeviceBase* device,
@@ -135,14 +160,9 @@
 
                     DAWN_TRY(ValidateBindingTypeWithShaderStageVisibility(
                         bindingInfo.type, StageBit(module->GetExecutionModel())));
-                    if (bindingInfo.type == wgpu::BindingType::StorageBuffer) {
-                        bindingSlot.visibility =
-                            wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute;
-                    } else {
-                        bindingSlot.visibility = wgpu::ShaderStage::Vertex |
-                                                 wgpu::ShaderStage::Fragment |
-                                                 wgpu::ShaderStage::Compute;
-                    }
+                    bindingSlot.visibility =
+                        GetShaderStageVisibilityWithBindingType(bindingInfo.type);
+
                     bindingSlot.type = bindingInfo.type;
                     bindingSlot.hasDynamicOffset = false;
                     bindingSlot.multisampled = bindingInfo.multisampled;
diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp
index bedf0c4..a68c58f 100644
--- a/src/dawn_native/ProgrammablePassEncoder.cpp
+++ b/src/dawn_native/ProgrammablePassEncoder.cpp
@@ -59,6 +59,8 @@
                         break;
 
                     case wgpu::BindingType::StorageTexture:
+                    case wgpu::BindingType::ReadonlyStorageTexture:
+                    case wgpu::BindingType::WriteonlyStorageTexture:
                         UNREACHABLE();
                         break;
                 }
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index eca1733..a6a12c5 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -406,6 +406,16 @@
                             info->type = wgpu::BindingType::StorageBuffer;
                         }
                     } break;
+                    case wgpu::BindingType::StorageTexture: {
+                        spirv_cross::Bitset flags = compiler.get_decoration_bitset(resource.id);
+                        if (flags.get(spv::DecorationNonReadable)) {
+                            info->type = wgpu::BindingType::WriteonlyStorageTexture;
+                        } else if (flags.get(spv::DecorationNonWritable)) {
+                            info->type = wgpu::BindingType::ReadonlyStorageTexture;
+                        } else {
+                            info->type = wgpu::BindingType::StorageTexture;
+                        }
+                    } break;
                     default:
                         info->type = bindingType;
                 }
@@ -421,6 +431,8 @@
                                          wgpu::BindingType::Sampler));
         DAWN_TRY(ExtractResourcesBinding(resources.storage_buffers, compiler,
                                          wgpu::BindingType::StorageBuffer));
+        DAWN_TRY(ExtractResourcesBinding(resources.storage_images, compiler,
+                                         wgpu::BindingType::StorageTexture));
 
         // Extract the vertex attributes
         if (mExecutionModel == SingleShaderStage::Vertex) {
diff --git a/src/dawn_native/d3d12/BindGroupD3D12.cpp b/src/dawn_native/d3d12/BindGroupD3D12.cpp
index c8138b2..0f9ea86 100644
--- a/src/dawn_native/d3d12/BindGroupD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupD3D12.cpp
@@ -13,15 +13,15 @@
 // limitations under the License.
 
 #include "dawn_native/d3d12/BindGroupD3D12.h"
+
 #include "common/BitSetIterator.h"
 #include "dawn_native/d3d12/BindGroupLayoutD3D12.h"
 #include "dawn_native/d3d12/BufferD3D12.h"
+#include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/SamplerD3D12.h"
 #include "dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h"
 #include "dawn_native/d3d12/TextureD3D12.h"
 
-#include "dawn_native/d3d12/DeviceD3D12.h"
-
 namespace dawn_native { namespace d3d12 {
 
     BindGroup::BindGroup(Device* device, const BindGroupDescriptor* descriptor)
@@ -161,6 +161,8 @@
                 } break;
 
                 case wgpu::BindingType::StorageTexture:
+                case wgpu::BindingType::ReadonlyStorageTexture:
+                case wgpu::BindingType::WriteonlyStorageTexture:
                     UNREACHABLE();
                     break;
 
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
index cce9196..80e9d8c 100644
--- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
@@ -47,6 +47,8 @@
                     break;
 
                 case wgpu::BindingType::StorageTexture:
+                case wgpu::BindingType::ReadonlyStorageTexture:
+                case wgpu::BindingType::WriteonlyStorageTexture:
                     UNREACHABLE();
                     break;
             }
@@ -107,6 +109,8 @@
                     case wgpu::BindingType::SampledTexture:
                     case wgpu::BindingType::Sampler:
                     case wgpu::BindingType::StorageTexture:
+                    case wgpu::BindingType::ReadonlyStorageTexture:
+                    case wgpu::BindingType::WriteonlyStorageTexture:
                         UNREACHABLE();
                         break;
                 }
@@ -129,6 +133,8 @@
                     break;
 
                 case wgpu::BindingType::StorageTexture:
+                case wgpu::BindingType::ReadonlyStorageTexture:
+                case wgpu::BindingType::WriteonlyStorageTexture:
                     UNREACHABLE();
                     break;
 
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index c7c42fe..7f7bc6a 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -136,6 +136,8 @@
                                 break;
 
                             case wgpu::BindingType::StorageTexture:
+                            case wgpu::BindingType::ReadonlyStorageTexture:
+                            case wgpu::BindingType::WriteonlyStorageTexture:
                                 // Not implemented.
 
                             case wgpu::BindingType::UniformBuffer:
@@ -224,6 +226,8 @@
                         case wgpu::BindingType::SampledTexture:
                         case wgpu::BindingType::Sampler:
                         case wgpu::BindingType::StorageTexture:
+                        case wgpu::BindingType::ReadonlyStorageTexture:
+                        case wgpu::BindingType::WriteonlyStorageTexture:
                             UNREACHABLE();
                             break;
                     }
diff --git a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
index 8d3dd17..7afadc7 100644
--- a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
+++ b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
@@ -51,6 +51,8 @@
                 case wgpu::BindingType::SampledTexture:
                 case wgpu::BindingType::Sampler:
                 case wgpu::BindingType::StorageTexture:
+                case wgpu::BindingType::ReadonlyStorageTexture:
+                case wgpu::BindingType::WriteonlyStorageTexture:
                     UNREACHABLE();
             }
         }
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 628f77b..7958217 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -599,6 +599,8 @@
                         } break;
 
                         case wgpu::BindingType::StorageTexture:
+                        case wgpu::BindingType::ReadonlyStorageTexture:
+                        case wgpu::BindingType::WriteonlyStorageTexture:
                             UNREACHABLE();
                             break;
                     }
diff --git a/src/dawn_native/metal/PipelineLayoutMTL.mm b/src/dawn_native/metal/PipelineLayoutMTL.mm
index 1d3e220..3a09238 100644
--- a/src/dawn_native/metal/PipelineLayoutMTL.mm
+++ b/src/dawn_native/metal/PipelineLayoutMTL.mm
@@ -54,6 +54,8 @@
                             textureIndex++;
                             break;
                         case wgpu::BindingType::StorageTexture:
+                        case wgpu::BindingType::ReadonlyStorageTexture:
+                        case wgpu::BindingType::WriteonlyStorageTexture:
                             UNREACHABLE();
                             break;
                     }
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 2194016..25d3c81 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -305,6 +305,8 @@
                         } break;
 
                         case wgpu::BindingType::StorageTexture:
+                        case wgpu::BindingType::ReadonlyStorageTexture:
+                        case wgpu::BindingType::WriteonlyStorageTexture:
                             UNREACHABLE();
                             break;
 
diff --git a/src/dawn_native/opengl/PipelineGL.cpp b/src/dawn_native/opengl/PipelineGL.cpp
index e280875..71e2b83 100644
--- a/src/dawn_native/opengl/PipelineGL.cpp
+++ b/src/dawn_native/opengl/PipelineGL.cpp
@@ -140,6 +140,8 @@
                         break;
 
                     case wgpu::BindingType::StorageTexture:
+                    case wgpu::BindingType::ReadonlyStorageTexture:
+                    case wgpu::BindingType::WriteonlyStorageTexture:
                         UNREACHABLE();
                         break;
 
diff --git a/src/dawn_native/opengl/PipelineLayoutGL.cpp b/src/dawn_native/opengl/PipelineLayoutGL.cpp
index 530e7d0..18fd4f2 100644
--- a/src/dawn_native/opengl/PipelineLayoutGL.cpp
+++ b/src/dawn_native/opengl/PipelineLayoutGL.cpp
@@ -56,6 +56,8 @@
                         break;
 
                     case wgpu::BindingType::StorageTexture:
+                    case wgpu::BindingType::ReadonlyStorageTexture:
+                    case wgpu::BindingType::WriteonlyStorageTexture:
                         UNREACHABLE();
                         break;
 
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 7e12806..ab70419 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -149,6 +149,8 @@
                                 break;
 
                             case wgpu::BindingType::StorageTexture:
+                            case wgpu::BindingType::ReadonlyStorageTexture:
+                            case wgpu::BindingType::WriteonlyStorageTexture:
                                 // Not implemented.
 
                             case wgpu::BindingType::UniformBuffer:
diff --git a/src/tests/unittests/validation/StorageTextureValidationTests.cpp b/src/tests/unittests/validation/StorageTextureValidationTests.cpp
new file mode 100644
index 0000000..56cf999
--- /dev/null
+++ b/src/tests/unittests/validation/StorageTextureValidationTests.cpp
@@ -0,0 +1,207 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "tests/unittests/validation/ValidationTest.h"
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/WGPUHelpers.h"
+
+class StorageTextureValidationTests : public ValidationTest {
+  protected:
+    wgpu::ShaderModule mDefaultVSModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+        #version 450
+        void main() {
+            gl_Position = vec4(0.f, 0.f, 0.f, 1.f);
+        })");
+    wgpu::ShaderModule mDefaultFSModule =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+        #version 450
+        layout(location = 0) out vec4 fragColor;
+        void main() {
+            fragColor = vec4(1.f, 0.f, 0.f, 1.f);
+        })");
+};
+
+// Validate read-only storage textures can be declared in vertex and fragment
+// shaders, while writeonly storage textures can't.
+TEST_F(StorageTextureValidationTests, RenderPipeline) {
+    // Readonly storage texture can be declared in a vertex shader.
+    {
+        wgpu::ShaderModule vsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform readonly image2D image0;
+            void main() {
+                gl_Position = imageLoad(image0, ivec2(gl_VertexIndex, 0));
+            })");
+
+        utils::ComboRenderPipelineDescriptor descriptor(device);
+        descriptor.layout = nullptr;
+        descriptor.vertexStage.module = vsModule;
+        descriptor.cFragmentStage.module = mDefaultFSModule;
+        device.CreateRenderPipeline(&descriptor);
+    }
+
+    // Read-only storage textures can be declared in a fragment shader.
+    {
+        wgpu::ShaderModule fsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform readonly image2D image0;
+            layout(location = 0) out vec4 fragColor;
+            void main() {
+                fragColor = imageLoad(image0, ivec2(gl_FragCoord.xy));
+            })");
+
+        utils::ComboRenderPipelineDescriptor descriptor(device);
+        descriptor.layout = nullptr;
+        descriptor.vertexStage.module = mDefaultVSModule;
+        descriptor.cFragmentStage.module = fsModule;
+        device.CreateRenderPipeline(&descriptor);
+    }
+
+    // Write-only storage textures cannot be declared in a vertex shader.
+    {
+        wgpu::ShaderModule vsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform writeonly image2D image0;
+            void main() {
+                imageStore(image0, ivec2(gl_VertexIndex, 0), vec4(1.f, 0.f, 0.f, 1.f));
+            })");
+
+        utils::ComboRenderPipelineDescriptor descriptor(device);
+        descriptor.layout = nullptr;
+        descriptor.vertexStage.module = vsModule;
+        descriptor.cFragmentStage.module = mDefaultFSModule;
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+    }
+
+    // Write-only storage textures cannot be declared in a fragment shader.
+    {
+        wgpu::ShaderModule fsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform writeonly image2D image0;
+            void main() {
+                imageStore(image0, ivec2(gl_FragCoord.xy), vec4(1.f, 0.f, 0.f, 1.f));
+            })");
+
+        utils::ComboRenderPipelineDescriptor descriptor(device);
+        descriptor.layout = nullptr;
+        descriptor.vertexStage.module = mDefaultVSModule;
+        descriptor.cFragmentStage.module = fsModule;
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+    }
+}
+
+// Validate both read-only and write-only storage textures can be declared in
+// compute shaders.
+TEST_F(StorageTextureValidationTests, ComputePipeline) {
+    // Read-only storage textures can be declared in a compute shader.
+    {
+        wgpu::ShaderModule csModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform readonly image2D image0;
+            layout(std430, set = 0, binding = 0) buffer Buf { uint buf; };
+            void main() {
+                vec4 pixel = imageLoad(image0, ivec2(gl_LocalInvocationID.xy));
+                buf = uint(pixel.x);
+            })");
+
+        wgpu::ComputePipelineDescriptor descriptor;
+        descriptor.layout = nullptr;
+        descriptor.computeStage.module = csModule;
+        descriptor.computeStage.entryPoint = "main";
+
+        device.CreateComputePipeline(&descriptor);
+    }
+
+    // Write-only storage textures can be declared in a compute shader.
+    {
+        wgpu::ShaderModule csModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform writeonly image2D image0;
+            void main() {
+                imageStore(image0, ivec2(gl_LocalInvocationID.xy), vec4(0.f, 0.f, 0.f, 0.f));
+            })");
+
+        wgpu::ComputePipelineDescriptor descriptor;
+        descriptor.layout = nullptr;
+        descriptor.computeStage.module = csModule;
+        descriptor.computeStage.entryPoint = "main";
+
+        device.CreateComputePipeline(&descriptor);
+    }
+}
+
+// Validate read-write storage textures have not been supported yet.
+TEST_F(StorageTextureValidationTests, ReadWriteStorageTexture) {
+    // Read-write storage textures cannot be declared in a vertex shader by default.
+    {
+        wgpu::ShaderModule vsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform image2D image0;
+            void main() {
+                vec4 pixel = imageLoad(image0, ivec2(gl_VertexIndex, 0));
+                imageStore(image0, ivec2(gl_VertexIndex, 0), pixel * 2);
+            })");
+
+        utils::ComboRenderPipelineDescriptor descriptor(device);
+        descriptor.layout = nullptr;
+        descriptor.vertexStage.module = vsModule;
+        descriptor.cFragmentStage.module = mDefaultFSModule;
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+    }
+
+    // Read-write storage textures cannot be declared in a fragment shader by default.
+    {
+        wgpu::ShaderModule fsModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform image2D image0;
+            void main() {
+                vec4 pixel = imageLoad(image0, ivec2(gl_FragCoord.xy));
+                imageStore(image0, ivec2(gl_FragCoord.xy), pixel * 2);
+            })");
+
+        utils::ComboRenderPipelineDescriptor descriptor(device);
+        descriptor.layout = nullptr;
+        descriptor.vertexStage.module = mDefaultVSModule;
+        descriptor.cFragmentStage.module = fsModule;
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+    }
+
+    // Read-write storage textures cannot be declared in a compute shader by default.
+    {
+        wgpu::ShaderModule csModule =
+            utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
+            #version 450
+            layout(set = 0, binding = 0, rgba8) uniform image2D image0;
+            void main() {
+                vec4 pixel = imageLoad(image0, ivec2(gl_LocalInvocationID.xy));
+                imageStore(image0, ivec2(gl_LocalInvocationID.xy), pixel * 2);
+            })");
+
+        wgpu::ComputePipelineDescriptor descriptor;
+        descriptor.layout = nullptr;
+        descriptor.computeStage.module = csModule;
+        descriptor.computeStage.entryPoint = "main";
+
+        ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&descriptor));
+    }
+}