Validate the declaration of storage texture format in shader

This patch adds the validation on the storage texture format declared in
shaders when we create a rendering or compute pipeline with read-only or
write-only storage textures.

This patch also fixes a typo in the TextureValidationTest.

BUG=dawn:267
TEST=dawn_unittests

Change-Id: Id302b4b7803d7e03b57c61de1290cc71ba940e2c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16940
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index d653c1c..9f0cee4 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -751,6 +751,8 @@
     "src/utils/SystemUtils.h",
     "src/utils/TerribleCommandBuffer.cpp",
     "src/utils/TerribleCommandBuffer.h",
+    "src/utils/TextureFormatUtils.cpp",
+    "src/utils/TextureFormatUtils.h",
     "src/utils/Timer.h",
     "src/utils/WGPUHelpers.cpp",
     "src/utils/WGPUHelpers.h",
diff --git a/dawn.json b/dawn.json
index bec9c9e..aad0a35 100644
--- a/dawn.json
+++ b/dawn.json
@@ -94,7 +94,8 @@
             {"name": "has dynamic offset", "type": "bool", "default": "false"},
             {"name": "multisampled", "type": "bool", "default": "false"},
             {"name": "texture dimension", "type": "texture view dimension", "default": "undefined"},
-            {"name": "texture component type", "type": "texture component type", "default": "float"}
+            {"name": "texture component type", "type": "texture component type", "default": "float"},
+            {"name": "storage texture format", "type": "texture format", "default": "undefined"}
         ]
     },
     "bind group layout descriptor": {
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index 9039789..622ed02 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -58,7 +58,35 @@
         return {};
     }
 
-    MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase*,
+    MaybeError ValidateStorageTextureFormat(DeviceBase* device,
+                                            wgpu::BindingType bindingType,
+                                            wgpu::TextureFormat storageTextureFormat) {
+        switch (bindingType) {
+            case wgpu::BindingType::ReadonlyStorageTexture:
+            case wgpu::BindingType::WriteonlyStorageTexture: {
+                DAWN_TRY(ValidateTextureFormat(storageTextureFormat));
+
+                const Format& format = device->GetValidInternalFormat(storageTextureFormat);
+                if (!format.supportsStorageUsage) {
+                    return DAWN_VALIDATION_ERROR("The storage texture format is not supported");
+                }
+            } break;
+
+            case wgpu::BindingType::StorageBuffer:
+            case wgpu::BindingType::UniformBuffer:
+            case wgpu::BindingType::ReadonlyStorageBuffer:
+            case wgpu::BindingType::Sampler:
+            case wgpu::BindingType::SampledTexture:
+                break;
+            default:
+                UNREACHABLE();
+                break;
+        }
+
+        return {};
+    }
+
+    MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device,
                                                  const BindGroupLayoutDescriptor* descriptor) {
         if (descriptor->nextInChain != nullptr) {
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
@@ -87,6 +115,9 @@
             DAWN_TRY(
                 ValidateBindingTypeWithShaderStageVisibility(binding.type, binding.visibility));
 
+            DAWN_TRY(
+                ValidateStorageTextureFormat(device, binding.type, binding.storageTextureFormat));
+
             switch (binding.type) {
                 case wgpu::BindingType::UniformBuffer:
                     if (binding.hasDynamicOffset) {
diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h
index 24bae39..7455b58 100644
--- a/src/dawn_native/BindGroupLayout.h
+++ b/src/dawn_native/BindGroupLayout.h
@@ -36,6 +36,10 @@
         wgpu::BindingType bindingType,
         wgpu::ShaderStage shaderStageVisibility);
 
+    MaybeError ValidateStorageTextureFormat(DeviceBase* device,
+                                            wgpu::BindingType bindingType,
+                                            wgpu::TextureFormat storageTextureFormat);
+
     class BindGroupLayoutBase : public CachedObject {
       public:
         BindGroupLayoutBase(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor);
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp
index 6f22685..f72bbda 100644
--- a/src/dawn_native/PipelineLayout.cpp
+++ b/src/dawn_native/PipelineLayout.cpp
@@ -160,6 +160,9 @@
 
                     DAWN_TRY(ValidateBindingTypeWithShaderStageVisibility(
                         bindingInfo.type, StageBit(module->GetExecutionModel())));
+                    DAWN_TRY(ValidateStorageTextureFormat(device, bindingInfo.type,
+                                                          bindingInfo.storageTextureFormat));
+
                     bindingSlot.visibility =
                         GetShaderStageVisibilityWithBindingType(bindingInfo.type);
 
@@ -169,6 +172,7 @@
                     bindingSlot.textureDimension = bindingInfo.textureDimension;
                     bindingSlot.textureComponentType =
                         Format::FormatTypeToTextureComponentType(bindingInfo.textureComponentType);
+                    bindingSlot.storageTextureFormat = bindingInfo.storageTextureFormat;
 
                     if (usedBindings[group][binding]) {
                         if (bindingSlot == bindingData[group][usedBindingsMap[group][binding]]) {
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index a6a12c5..0a12a8f 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -113,6 +113,9 @@
                     return wgpu::BindingType::Sampler;
                 case shaderc_spvc_binding_type_sampled_texture:
                     return wgpu::BindingType::SampledTexture;
+
+                // TODO(jiawei.shao@intel.com): add convertion to read-only and write-only storage
+                // textures when they are supported as shaderc_spvc binding types.
                 case shaderc_spvc_binding_type_storage_texture:
                     return wgpu::BindingType::StorageTexture;
             }
@@ -134,6 +137,77 @@
                         "Attempted to convert invalid spvc execution model to SingleShaderStage");
             }
         }
+
+        wgpu::TextureFormat ToWGPUTextureFormat(spv::ImageFormat format) {
+            switch (format) {
+                case spv::ImageFormatR8:
+                    return wgpu::TextureFormat::R8Unorm;
+                case spv::ImageFormatR8Snorm:
+                    return wgpu::TextureFormat::R8Snorm;
+                case spv::ImageFormatR8ui:
+                    return wgpu::TextureFormat::R8Uint;
+                case spv::ImageFormatR8i:
+                    return wgpu::TextureFormat::R8Sint;
+                case spv::ImageFormatR16ui:
+                    return wgpu::TextureFormat::R16Uint;
+                case spv::ImageFormatR16i:
+                    return wgpu::TextureFormat::R16Sint;
+                case spv::ImageFormatR16f:
+                    return wgpu::TextureFormat::R16Float;
+                case spv::ImageFormatRg8:
+                    return wgpu::TextureFormat::RG8Unorm;
+                case spv::ImageFormatRg8Snorm:
+                    return wgpu::TextureFormat::RG8Snorm;
+                case spv::ImageFormatRg8ui:
+                    return wgpu::TextureFormat::RG8Uint;
+                case spv::ImageFormatRg8i:
+                    return wgpu::TextureFormat::RG8Sint;
+                case spv::ImageFormatR32f:
+                    return wgpu::TextureFormat::R32Float;
+                case spv::ImageFormatR32ui:
+                    return wgpu::TextureFormat::R32Uint;
+                case spv::ImageFormatR32i:
+                    return wgpu::TextureFormat::R32Sint;
+                case spv::ImageFormatRg16ui:
+                    return wgpu::TextureFormat::RG16Uint;
+                case spv::ImageFormatRg16i:
+                    return wgpu::TextureFormat::RG16Sint;
+                case spv::ImageFormatRg16f:
+                    return wgpu::TextureFormat::RG16Float;
+                case spv::ImageFormatRgba8:
+                    return wgpu::TextureFormat::RGBA8Unorm;
+                case spv::ImageFormatRgba8Snorm:
+                    return wgpu::TextureFormat::RGBA8Snorm;
+                case spv::ImageFormatRgba8ui:
+                    return wgpu::TextureFormat::RGBA8Uint;
+                case spv::ImageFormatRgba8i:
+                    return wgpu::TextureFormat::RGBA8Sint;
+                case spv::ImageFormatRgb10A2:
+                    return wgpu::TextureFormat::RGB10A2Unorm;
+                case spv::ImageFormatR11fG11fB10f:
+                    return wgpu::TextureFormat::RG11B10Float;
+                case spv::ImageFormatRg32f:
+                    return wgpu::TextureFormat::RG32Float;
+                case spv::ImageFormatRg32ui:
+                    return wgpu::TextureFormat::RG32Uint;
+                case spv::ImageFormatRg32i:
+                    return wgpu::TextureFormat::RG32Sint;
+                case spv::ImageFormatRgba16ui:
+                    return wgpu::TextureFormat::RGBA16Uint;
+                case spv::ImageFormatRgba16i:
+                    return wgpu::TextureFormat::RGBA16Sint;
+                case spv::ImageFormatRgba16f:
+                    return wgpu::TextureFormat::RGBA16Float;
+                case spv::ImageFormatRgba32f:
+                    return wgpu::TextureFormat::RGBA32Float;
+                case spv::ImageFormatRgba32ui:
+                    return wgpu::TextureFormat::RGBA32Uint;
+                case spv::ImageFormatRgba32i:
+                    return wgpu::TextureFormat::RGBA32Sint;
+                default:
+                    return wgpu::TextureFormat::Undefined;
+            }
+        }
     }  // anonymous namespace
 
     MaybeError ValidateShaderModuleDescriptor(DeviceBase*,
@@ -173,7 +247,7 @@
         }
 
         return {};
-    }
+    }  // namespace
 
     // ShaderModuleBase
 
@@ -251,6 +325,7 @@
             return {};
         };
 
+        // TODO(jiawei.shao@intel.com): extract binding information about storage textures.
         std::vector<shaderc_spvc_binding_info> resource_bindings;
         DAWN_TRY(CheckSpvcSuccess(mSpvcContext.GetBindingInfo(
                                       shaderc_spvc_shader_resource_uniform_buffers,
@@ -415,6 +490,22 @@
                         } else {
                             info->type = wgpu::BindingType::StorageTexture;
                         }
+
+                        spirv_cross::SPIRType::ImageType imageType =
+                            compiler.get_type(info->base_type_id).image;
+                        wgpu::TextureFormat storageTextureFormat =
+                            ToWGPUTextureFormat(imageType.format);
+                        if (storageTextureFormat == wgpu::TextureFormat::Undefined) {
+                            return DAWN_VALIDATION_ERROR(
+                                "Invalid image format declaration on storage image");
+                        }
+                        const Format& format =
+                            GetDevice()->GetValidInternalFormat(storageTextureFormat);
+                        if (!format.supportsStorageUsage) {
+                            return DAWN_VALIDATION_ERROR(
+                                "The storage texture format is not supported");
+                        }
+                        info->storageTextureFormat = storageTextureFormat;
                     } break;
                     default:
                         info->type = bindingType;
diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h
index 096d528..20f67a1 100644
--- a/src/dawn_native/ShaderModule.h
+++ b/src/dawn_native/ShaderModule.h
@@ -58,6 +58,7 @@
             Format::Type textureComponentType = Format::Type::Float;
             bool multisampled = false;
             bool used = false;
+            wgpu::TextureFormat storageTextureFormat = wgpu::TextureFormat::Undefined;
         };
         using ModuleBindingInfo =
             std::array<std::array<BindingInfo, kMaxBindingsPerGroup>, kMaxBindGroups>;
diff --git a/src/tests/end2end/StorageTextureTests.cpp b/src/tests/end2end/StorageTextureTests.cpp
index 3886098..b351db7 100644
--- a/src/tests/end2end/StorageTextureTests.cpp
+++ b/src/tests/end2end/StorageTextureTests.cpp
@@ -25,6 +25,7 @@
     {
         wgpu::BindGroupLayoutBinding binding = {0, wgpu::ShaderStage::Compute,
                                                 wgpu::BindingType::ReadonlyStorageTexture};
+        binding.storageTextureFormat = wgpu::TextureFormat::R32Float;
         wgpu::BindGroupLayoutDescriptor descriptor;
         descriptor.bindingCount = 1;
         descriptor.bindings = &binding;
@@ -36,6 +37,7 @@
     {
         wgpu::BindGroupLayoutBinding binding = {0, wgpu::ShaderStage::Compute,
                                                 wgpu::BindingType::WriteonlyStorageTexture};
+        binding.storageTextureFormat = wgpu::TextureFormat::R32Float;
         wgpu::BindGroupLayoutDescriptor descriptor;
         descriptor.bindingCount = 1;
         descriptor.bindings = &binding;
diff --git a/src/tests/end2end/TextureFormatTests.cpp b/src/tests/end2end/TextureFormatTests.cpp
index a31d38f..61f80e3 100644
--- a/src/tests/end2end/TextureFormatTests.cpp
+++ b/src/tests/end2end/TextureFormatTests.cpp
@@ -17,6 +17,7 @@
 #include "common/Assert.h"
 #include "common/Math.h"
 #include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/TextureFormatUtils.h"
 #include "utils/WGPUHelpers.h"
 
 #include <type_traits>
@@ -185,21 +186,7 @@
             })");
 
         // Compute the prefix needed for GLSL types that handle our texture's data.
-        const char* prefix = nullptr;
-        switch (sampleFormatInfo.type) {
-            case wgpu::TextureComponentType::Float:
-                prefix = "";
-                break;
-            case wgpu::TextureComponentType::Sint:
-                prefix = "i";
-                break;
-            case wgpu::TextureComponentType::Uint:
-                prefix = "u";
-                break;
-            default:
-                UNREACHABLE();
-                break;
-        }
+        const char* prefix = utils::GetColorTextureComponentTypePrefix(sampleFormatInfo.format);
 
         std::ostringstream fsSource;
         fsSource << "#version 450\n";
diff --git a/src/tests/unittests/validation/StorageTextureValidationTests.cpp b/src/tests/unittests/validation/StorageTextureValidationTests.cpp
index 0bcfddb..30e9e65 100644
--- a/src/tests/unittests/validation/StorageTextureValidationTests.cpp
+++ b/src/tests/unittests/validation/StorageTextureValidationTests.cpp
@@ -12,19 +12,132 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "common/Assert.h"
 #include "tests/unittests/validation/ValidationTest.h"
 #include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/TextureFormatUtils.h"
 #include "utils/WGPUHelpers.h"
 
 class StorageTextureValidationTests : public ValidationTest {
   protected:
-    wgpu::ShaderModule mDefaultVSModule =
+    static const char* GetGLSLImageFormatQualifier(wgpu::TextureFormat textureFormat) {
+        switch (textureFormat) {
+            case wgpu::TextureFormat::R8Unorm:
+                return "r8";
+            case wgpu::TextureFormat::R8Snorm:
+                return "r8_snorm";
+            case wgpu::TextureFormat::R8Uint:
+                return "r8ui";
+            case wgpu::TextureFormat::R8Sint:
+                return "r8i";
+            case wgpu::TextureFormat::R16Uint:
+                return "r16ui";
+            case wgpu::TextureFormat::R16Sint:
+                return "r16i";
+            case wgpu::TextureFormat::R16Float:
+                return "r16f";
+            case wgpu::TextureFormat::RG8Unorm:
+                return "rg8";
+            case wgpu::TextureFormat::RG8Snorm:
+                return "rg8_snorm";
+            case wgpu::TextureFormat::RG8Uint:
+                return "rg8ui";
+            case wgpu::TextureFormat::RG8Sint:
+                return "rg8i";
+            case wgpu::TextureFormat::R32Float:
+                return "r32f";
+            case wgpu::TextureFormat::R32Uint:
+                return "r32ui";
+            case wgpu::TextureFormat::R32Sint:
+                return "r32i";
+            case wgpu::TextureFormat::RG16Uint:
+                return "rg16ui";
+            case wgpu::TextureFormat::RG16Sint:
+                return "rg16i";
+            case wgpu::TextureFormat::RG16Float:
+                return "rg16f";
+            case wgpu::TextureFormat::RGBA8Unorm:
+                return "rgba8";
+            case wgpu::TextureFormat::RGBA8Snorm:
+                return "rgba8_snorm";
+            case wgpu::TextureFormat::RGBA8Uint:
+                return "rgba8ui";
+            case wgpu::TextureFormat::RGBA8Sint:
+                return "rgba8i";
+            case wgpu::TextureFormat::RGB10A2Unorm:
+                return "rgb10_a2";
+            case wgpu::TextureFormat::RG11B10Float:
+                return "r11f_g11f_b10f";
+            case wgpu::TextureFormat::RG32Float:
+                return "rg32f";
+            case wgpu::TextureFormat::RG32Uint:
+                return "rg32ui";
+            case wgpu::TextureFormat::RG32Sint:
+                return "rg32i";
+            case wgpu::TextureFormat::RGBA16Uint:
+                return "rgba16ui";
+            case wgpu::TextureFormat::RGBA16Sint:
+                return "rgba16i";
+            case wgpu::TextureFormat::RGBA16Float:
+                return "rgba16f";
+            case wgpu::TextureFormat::RGBA32Float:
+                return "rgba32f";
+            case wgpu::TextureFormat::RGBA32Uint:
+                return "rgba32ui";
+            case wgpu::TextureFormat::RGBA32Sint:
+                return "rgba32i";
+            default:
+                UNREACHABLE();
+                return "";
+        }
+    }
+
+    static std::string CreateComputeShaderWithStorageTexture(
+        wgpu::BindingType storageTextureBindingType,
+        wgpu::TextureFormat textureFormat) {
+        const char* glslImageFormatQualifier = GetGLSLImageFormatQualifier(textureFormat);
+        const char* textureComponentTypePrefix =
+            utils::GetColorTextureComponentTypePrefix(textureFormat);
+        return CreateComputeShaderWithStorageTexture(
+            storageTextureBindingType, glslImageFormatQualifier, textureComponentTypePrefix);
+    }
+
+    static std::string CreateComputeShaderWithStorageTexture(
+        wgpu::BindingType storageTextureBindingType,
+        const char* glslImageFormatQualifier,
+        const char* textureComponentTypePrefix) {
+        const char* memoryQualifier = "";
+        switch (storageTextureBindingType) {
+            case wgpu::BindingType::ReadonlyStorageTexture:
+                memoryQualifier = "readonly";
+                break;
+            case wgpu::BindingType::WriteonlyStorageTexture:
+                memoryQualifier = "writeonly";
+                break;
+            default:
+                UNREACHABLE();
+                break;
+        }
+
+        std::ostringstream ostream;
+        ostream << "#version 450\n"
+                   "layout (set = 0, binding = 0, "
+                << glslImageFormatQualifier << ") uniform " << memoryQualifier << " "
+                << textureComponentTypePrefix
+                << "image2D image0;\n"
+                   "void main() {\n"
+                   "}\n";
+
+        return ostream.str();
+    }
+
+    const 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 =
+    const wgpu::ShaderModule mDefaultFSModule =
         utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
         #version 450
         layout(location = 0) out vec4 fragColor;
@@ -227,6 +340,7 @@
 
     for (const auto& testSpec : kTestSpecs) {
         wgpu::BindGroupLayoutBinding binding = {0, testSpec.stage, testSpec.type};
+        binding.storageTextureFormat = wgpu::TextureFormat::R32Uint;
         wgpu::BindGroupLayoutDescriptor descriptor;
         descriptor.bindingCount = 1;
         descriptor.bindings = &binding;
@@ -238,3 +352,73 @@
         }
     }
 }
+
+// Validate it is an error to declare a read-only or write-only storage texture in shaders with any
+// format that doesn't support TextureUsage::Storage texture usages.
+TEST_F(StorageTextureValidationTests, StorageTextureFormatInShaders) {
+    // Not include RGBA8UnormSrgb, BGRA8Unorm, BGRA8UnormSrgb because they are not related to any
+    // SPIR-V Image Formats.
+    constexpr std::array<wgpu::TextureFormat, 32> kWGPUTextureFormatSupportedAsSPIRVImageFormats = {
+        wgpu::TextureFormat::R32Uint,      wgpu::TextureFormat::R32Sint,
+        wgpu::TextureFormat::R32Float,     wgpu::TextureFormat::RGBA8Unorm,
+        wgpu::TextureFormat::RGBA8Snorm,   wgpu::TextureFormat::RGBA8Uint,
+        wgpu::TextureFormat::RGBA8Sint,    wgpu::TextureFormat::RG32Uint,
+        wgpu::TextureFormat::RG32Sint,     wgpu::TextureFormat::RG32Float,
+        wgpu::TextureFormat::RGBA16Uint,   wgpu::TextureFormat::RGBA16Sint,
+        wgpu::TextureFormat::RGBA16Float,  wgpu::TextureFormat::RGBA32Uint,
+        wgpu::TextureFormat::RGBA32Sint,   wgpu::TextureFormat::RGBA32Float,
+        wgpu::TextureFormat::R8Unorm,      wgpu::TextureFormat::R8Snorm,
+        wgpu::TextureFormat::R8Uint,       wgpu::TextureFormat::R8Sint,
+        wgpu::TextureFormat::R16Uint,      wgpu::TextureFormat::R16Sint,
+        wgpu::TextureFormat::R16Float,     wgpu::TextureFormat::RG8Unorm,
+        wgpu::TextureFormat::RG8Snorm,     wgpu::TextureFormat::RG8Uint,
+        wgpu::TextureFormat::RG8Sint,      wgpu::TextureFormat::RG16Uint,
+        wgpu::TextureFormat::RG16Sint,     wgpu::TextureFormat::RG16Float,
+        wgpu::TextureFormat::RGB10A2Unorm, wgpu::TextureFormat::RG11B10Float};
+
+    constexpr std::array<wgpu::BindingType, 2> kStorageTextureBindingTypes = {
+        wgpu::BindingType::ReadonlyStorageTexture, wgpu::BindingType::WriteonlyStorageTexture};
+
+    for (wgpu::BindingType storageTextureBindingType : kStorageTextureBindingTypes) {
+        for (wgpu::TextureFormat format : kWGPUTextureFormatSupportedAsSPIRVImageFormats) {
+            std::string computeShader =
+                CreateComputeShaderWithStorageTexture(storageTextureBindingType, format);
+            if (utils::TextureFormatSupportsStorageTexture(format)) {
+                utils::CreateShaderModule(device, utils::SingleShaderStage::Compute,
+                                          computeShader.c_str());
+            } else {
+                ASSERT_DEVICE_ERROR(utils::CreateShaderModule(
+                    device, utils::SingleShaderStage::Compute, computeShader.c_str()));
+            }
+        }
+    }
+}
+
+// Verify that declaring a storage texture format that is not supported in WebGPU causes validation
+// error.
+TEST_F(StorageTextureValidationTests, UnsupportedSPIRVStorageTextureFormat) {
+    struct TextureFormatInfo {
+        const char* name;
+        const char* componentTypePrefix;
+    };
+
+    constexpr std::array<TextureFormatInfo, 7> kUnsupportedTextureFormats = {{{"rgba16", ""},
+                                                                              {"rg16", ""},
+                                                                              {"r16", ""},
+                                                                              {"rgba16_snorm", ""},
+                                                                              {"rg16_snorm", ""},
+                                                                              {"r16_snorm", ""},
+                                                                              {"rgb10_a2ui", "u"}}};
+
+    constexpr std::array<wgpu::BindingType, 2> kStorageTextureBindingTypes = {
+        wgpu::BindingType::ReadonlyStorageTexture, wgpu::BindingType::WriteonlyStorageTexture};
+
+    for (wgpu::BindingType bindingType : kStorageTextureBindingTypes) {
+        for (const TextureFormatInfo& formatInfo : kUnsupportedTextureFormats) {
+            std::string computeShader = CreateComputeShaderWithStorageTexture(
+                bindingType, formatInfo.name, formatInfo.componentTypePrefix);
+            ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, utils::SingleShaderStage::Compute,
+                                                          computeShader.c_str()));
+        }
+    }
+}
diff --git a/src/tests/unittests/validation/TextureValidationTests.cpp b/src/tests/unittests/validation/TextureValidationTests.cpp
index 4a996fc..a75c5cf 100644
--- a/src/tests/unittests/validation/TextureValidationTests.cpp
+++ b/src/tests/unittests/validation/TextureValidationTests.cpp
@@ -16,6 +16,7 @@
 
 #include "common/Constants.h"
 #include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/TextureFormatUtils.h"
 #include "utils/WGPUHelpers.h"
 
 namespace {
@@ -345,37 +346,13 @@
     descriptor.size = {1, 1, 1};
     descriptor.usage = wgpu::TextureUsage::Storage;
 
-    wgpu::TextureFormat kSupportedFormatsWithStorageUsage[] = {
-        wgpu::TextureFormat::R32Uint,     wgpu::TextureFormat::R32Sint,
-        wgpu::TextureFormat::R32Uint,     wgpu::TextureFormat::RGBA8Unorm,
-        wgpu::TextureFormat::RGBA8Snorm,  wgpu::TextureFormat::RGBA8Uint,
-        wgpu::TextureFormat::RGBA8Sint,   wgpu::TextureFormat::RG32Uint,
-        wgpu::TextureFormat::RG32Sint,    wgpu::TextureFormat::RG32Float,
-        wgpu::TextureFormat::RGBA16Uint,  wgpu::TextureFormat::RGBA16Sint,
-        wgpu::TextureFormat::RGBA16Float, wgpu::TextureFormat::RGBA32Uint,
-        wgpu::TextureFormat::RGBA32Sint,  wgpu::TextureFormat::RGBA32Float};
-    for (wgpu::TextureFormat format : kSupportedFormatsWithStorageUsage) {
+    for (wgpu::TextureFormat format : utils::kAllTextureFormats) {
         descriptor.format = format;
-        device.CreateTexture(&descriptor);
-    }
-
-    wgpu::TextureFormat kUnsupportedFormatsWithStorageUsage[] = {
-        wgpu::TextureFormat::R8Unorm,        wgpu::TextureFormat::R8Snorm,
-        wgpu::TextureFormat::R8Uint,         wgpu::TextureFormat::R8Sint,
-        wgpu::TextureFormat::R16Uint,        wgpu::TextureFormat::R16Sint,
-        wgpu::TextureFormat::R16Float,       wgpu::TextureFormat::RG8Unorm,
-        wgpu::TextureFormat::RG8Snorm,       wgpu::TextureFormat::RG8Uint,
-        wgpu::TextureFormat::RG8Sint,        wgpu::TextureFormat::RG16Uint,
-        wgpu::TextureFormat::RG16Sint,       wgpu::TextureFormat::RG16Float,
-        wgpu::TextureFormat::RGBA8UnormSrgb, wgpu::TextureFormat::BGRA8Unorm,
-        wgpu::TextureFormat::BGRA8UnormSrgb, wgpu::TextureFormat::RGB10A2Unorm,
-        wgpu::TextureFormat::RG11B10Float,
-
-        wgpu::TextureFormat::Depth24Plus,    wgpu::TextureFormat::Depth24PlusStencil8,
-        wgpu::TextureFormat::Depth32Float};
-    for (wgpu::TextureFormat format : kUnsupportedFormatsWithStorageUsage) {
-        descriptor.format = format;
-        ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+        if (utils::TextureFormatSupportsStorageTexture(format)) {
+            device.CreateTexture(&descriptor);
+        } else {
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+        }
     }
 }
 
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 06ec98c..3a95940 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -26,6 +26,8 @@
     "SystemUtils.h"
     "TerribleCommandBuffer.cpp"
     "TerribleCommandBuffer.h"
+    "TextureFormatUtils.cpp"
+    "TextureFormatUtils.h"
     "Timer.h"
     "WGPUHelpers.cpp"
     "WGPUHelpers.h"
diff --git a/src/utils/TextureFormatUtils.cpp b/src/utils/TextureFormatUtils.cpp
new file mode 100644
index 0000000..634417c
--- /dev/null
+++ b/src/utils/TextureFormatUtils.cpp
@@ -0,0 +1,89 @@
+// 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 "TextureFormatUtils.h"
+
+namespace utils {
+    const char* GetColorTextureComponentTypePrefix(wgpu::TextureFormat textureFormat) {
+        switch (textureFormat) {
+            case wgpu::TextureFormat::R8Unorm:
+            case wgpu::TextureFormat::R8Snorm:
+            case wgpu::TextureFormat::R16Float:
+            case wgpu::TextureFormat::RG8Unorm:
+            case wgpu::TextureFormat::RG8Snorm:
+            case wgpu::TextureFormat::R32Float:
+            case wgpu::TextureFormat::RG16Float:
+            case wgpu::TextureFormat::RGBA8Unorm:
+            case wgpu::TextureFormat::RGBA8Snorm:
+            case wgpu::TextureFormat::RGB10A2Unorm:
+            case wgpu::TextureFormat::RG11B10Float:
+            case wgpu::TextureFormat::RG32Float:
+            case wgpu::TextureFormat::RGBA16Float:
+            case wgpu::TextureFormat::RGBA32Float:
+            case wgpu::TextureFormat::BGRA8Unorm:
+            case wgpu::TextureFormat::BGRA8UnormSrgb:
+            case wgpu::TextureFormat::RGBA8UnormSrgb:
+                return "";
+
+            case wgpu::TextureFormat::R8Uint:
+            case wgpu::TextureFormat::R16Uint:
+            case wgpu::TextureFormat::RG8Uint:
+            case wgpu::TextureFormat::R32Uint:
+            case wgpu::TextureFormat::RG16Uint:
+            case wgpu::TextureFormat::RGBA8Uint:
+            case wgpu::TextureFormat::RG32Uint:
+            case wgpu::TextureFormat::RGBA16Uint:
+            case wgpu::TextureFormat::RGBA32Uint:
+                return "u";
+
+            case wgpu::TextureFormat::R8Sint:
+            case wgpu::TextureFormat::R16Sint:
+            case wgpu::TextureFormat::RG8Sint:
+            case wgpu::TextureFormat::R32Sint:
+            case wgpu::TextureFormat::RG16Sint:
+            case wgpu::TextureFormat::RGBA8Sint:
+            case wgpu::TextureFormat::RG32Sint:
+            case wgpu::TextureFormat::RGBA16Sint:
+            case wgpu::TextureFormat::RGBA32Sint:
+                return "i";
+            default:
+                UNREACHABLE();
+                return "";
+        }
+    }
+
+    bool TextureFormatSupportsStorageTexture(wgpu::TextureFormat format) {
+        switch (format) {
+            case wgpu::TextureFormat::R32Uint:
+            case wgpu::TextureFormat::R32Sint:
+            case wgpu::TextureFormat::R32Float:
+            case wgpu::TextureFormat::RGBA8Unorm:
+            case wgpu::TextureFormat::RGBA8Snorm:
+            case wgpu::TextureFormat::RGBA8Uint:
+            case wgpu::TextureFormat::RGBA8Sint:
+            case wgpu::TextureFormat::RG32Uint:
+            case wgpu::TextureFormat::RG32Sint:
+            case wgpu::TextureFormat::RG32Float:
+            case wgpu::TextureFormat::RGBA16Uint:
+            case wgpu::TextureFormat::RGBA16Sint:
+            case wgpu::TextureFormat::RGBA16Float:
+            case wgpu::TextureFormat::RGBA32Uint:
+            case wgpu::TextureFormat::RGBA32Sint:
+            case wgpu::TextureFormat::RGBA32Float:
+                return true;
+            default:
+                return false;
+        }
+    }
+}  // namespace utils
diff --git a/src/utils/TextureFormatUtils.h b/src/utils/TextureFormatUtils.h
new file mode 100644
index 0000000..bbd5f0c
--- /dev/null
+++ b/src/utils/TextureFormatUtils.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef UTILS_TEXTURE_FORMAT_UTILS_H_
+#define UTILS_TEXTURE_FORMAT_UTILS_H_
+
+#include <array>
+
+#include <dawn/webgpu_cpp.h>
+
+#include "common/Assert.h"
+
+namespace utils {
+    static constexpr std::array<wgpu::TextureFormat, 52> kAllTextureFormats = {
+        wgpu::TextureFormat::R8Unorm,        wgpu::TextureFormat::R8Snorm,
+        wgpu::TextureFormat::R8Uint,         wgpu::TextureFormat::R8Sint,
+        wgpu::TextureFormat::R16Uint,        wgpu::TextureFormat::R16Sint,
+        wgpu::TextureFormat::R16Float,       wgpu::TextureFormat::RG8Unorm,
+        wgpu::TextureFormat::RG8Snorm,       wgpu::TextureFormat::RG8Uint,
+        wgpu::TextureFormat::RG8Sint,        wgpu::TextureFormat::R32Float,
+        wgpu::TextureFormat::R32Uint,        wgpu::TextureFormat::R32Sint,
+        wgpu::TextureFormat::RG16Uint,       wgpu::TextureFormat::RG16Sint,
+        wgpu::TextureFormat::RG16Float,      wgpu::TextureFormat::RGBA8Unorm,
+        wgpu::TextureFormat::RGBA8UnormSrgb, wgpu::TextureFormat::RGBA8Snorm,
+        wgpu::TextureFormat::RGBA8Uint,      wgpu::TextureFormat::RGBA8Sint,
+        wgpu::TextureFormat::BGRA8Unorm,     wgpu::TextureFormat::BGRA8UnormSrgb,
+        wgpu::TextureFormat::RGB10A2Unorm,   wgpu::TextureFormat::RG11B10Float,
+        wgpu::TextureFormat::RG32Float,      wgpu::TextureFormat::RG32Uint,
+        wgpu::TextureFormat::RG32Sint,       wgpu::TextureFormat::RGBA16Uint,
+        wgpu::TextureFormat::RGBA16Sint,     wgpu::TextureFormat::RGBA16Float,
+        wgpu::TextureFormat::RGBA32Float,    wgpu::TextureFormat::RGBA32Uint,
+        wgpu::TextureFormat::RGBA32Sint,     wgpu::TextureFormat::Depth32Float,
+        wgpu::TextureFormat::Depth24Plus,    wgpu::TextureFormat::Depth24PlusStencil8,
+        wgpu::TextureFormat::BC1RGBAUnorm,   wgpu::TextureFormat::BC1RGBAUnormSrgb,
+        wgpu::TextureFormat::BC2RGBAUnorm,   wgpu::TextureFormat::BC2RGBAUnormSrgb,
+        wgpu::TextureFormat::BC3RGBAUnorm,   wgpu::TextureFormat::BC3RGBAUnormSrgb,
+        wgpu::TextureFormat::BC4RUnorm,      wgpu::TextureFormat::BC4RSnorm,
+        wgpu::TextureFormat::BC5RGUnorm,     wgpu::TextureFormat::BC5RGSnorm,
+        wgpu::TextureFormat::BC6HRGBUfloat,  wgpu::TextureFormat::BC6HRGBSfloat,
+        wgpu::TextureFormat::BC7RGBAUnorm,   wgpu::TextureFormat::BC7RGBAUnormSrgb,
+    };
+
+    const char* GetColorTextureComponentTypePrefix(wgpu::TextureFormat textureFormat);
+    bool TextureFormatSupportsStorageTexture(wgpu::TextureFormat format);
+}  // namespace utils
+
+#endif