[ir][spirv-writer] Emit texture and sampler types

Bug: tint:1906
Change-Id: I938fe30f24b78069c52da396cc096beeef8cac65
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/139660
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Auto-Submit: James Price <jrprice@google.com>
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.cc b/src/tint/writer/spirv/ir/generator_impl_ir.cc
index 7b96272..782725e 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.cc
@@ -53,12 +53,19 @@
 #include "src/tint/transform/manager.h"
 #include "src/tint/type/array.h"
 #include "src/tint/type/bool.h"
+#include "src/tint/type/depth_multisampled_texture.h"
+#include "src/tint/type/depth_texture.h"
 #include "src/tint/type/f16.h"
 #include "src/tint/type/f32.h"
 #include "src/tint/type/i32.h"
 #include "src/tint/type/matrix.h"
+#include "src/tint/type/multisampled_texture.h"
 #include "src/tint/type/pointer.h"
+#include "src/tint/type/sampled_texture.h"
+#include "src/tint/type/sampler.h"
+#include "src/tint/type/storage_texture.h"
 #include "src/tint/type/struct.h"
+#include "src/tint/type/texture.h"
 #include "src/tint/type/type.h"
 #include "src/tint/type/u32.h"
 #include "src/tint/type/vector.h"
@@ -331,6 +338,18 @@
                                   Type(ptr->StoreType(), ptr->AddressSpace())});
             },
             [&](const type::Struct* str) { EmitStructType(id, str, addrspace); },
+            [&](const type::Texture* tex) { EmitTextureType(id, tex); },
+            [&](const type::Sampler* s) {
+                module_.PushType(spv::Op::OpTypeSampler, {id});
+
+                // Register both of the sampler types, as they're the same in SPIR-V.
+                if (s->kind() == type::SamplerKind::kSampler) {
+                    types_.Add(
+                        ir_->Types().Get<type::Sampler>(type::SamplerKind::kComparisonSampler), id);
+                } else {
+                    types_.Add(ir_->Types().Get<type::Sampler>(type::SamplerKind::kSampler), id);
+                }
+            },
             [&](Default) {
                 TINT_ICE(Writer, diagnostics_) << "unhandled type: " << ty->FriendlyName();
             });
@@ -451,6 +470,82 @@
     }
 }
 
+void GeneratorImplIr::EmitTextureType(uint32_t id, const type::Texture* texture) {
+    uint32_t sampled_type = Switch(
+        texture,  //
+        [&](const type::DepthTexture*) { return Type(ir_->Types().f32()); },
+        [&](const type::DepthMultisampledTexture*) { return Type(ir_->Types().f32()); },
+        [&](const type::SampledTexture* t) { return Type(t->type()); },
+        [&](const type::MultisampledTexture* t) { return Type(t->type()); },
+        [&](const type::StorageTexture* t) { return Type(t->type()); });
+
+    uint32_t dim = SpvDimMax;
+    uint32_t array = 0u;
+    switch (texture->dim()) {
+        case type::TextureDimension::kNone: {
+            break;
+        }
+        case type::TextureDimension::k1d: {
+            dim = SpvDim1D;
+            if (texture->Is<type::SampledTexture>()) {
+                module_.PushCapability(SpvCapabilitySampled1D);
+            } else if (texture->Is<type::StorageTexture>()) {
+                module_.PushCapability(SpvCapabilityImage1D);
+            }
+            break;
+        }
+        case type::TextureDimension::k2d: {
+            dim = SpvDim2D;
+            break;
+        }
+        case type::TextureDimension::k2dArray: {
+            dim = SpvDim2D;
+            array = 1u;
+            break;
+        }
+        case type::TextureDimension::k3d: {
+            dim = SpvDim3D;
+            break;
+        }
+        case type::TextureDimension::kCube: {
+            dim = SpvDimCube;
+            break;
+        }
+        case type::TextureDimension::kCubeArray: {
+            dim = SpvDimCube;
+            array = 1u;
+            if (texture->IsAnyOf<type::SampledTexture, type::DepthTexture>()) {
+                module_.PushCapability(SpvCapabilitySampledCubeArray);
+            }
+            break;
+        }
+    }
+
+    // The Vulkan spec says: The "Depth" operand of OpTypeImage is ignored.
+    // In SPIRV, 0 means not depth, 1 means depth, and 2 means unknown.
+    // Using anything other than 0 is problematic on various Vulkan drivers.
+    uint32_t depth = 0u;
+
+    uint32_t ms = 0u;
+    if (texture->IsAnyOf<type::MultisampledTexture, type::DepthMultisampledTexture>()) {
+        ms = 1u;
+    }
+
+    uint32_t sampled = 2u;
+    if (texture->IsAnyOf<type::MultisampledTexture, type::SampledTexture, type::DepthTexture,
+                         type::DepthMultisampledTexture>()) {
+        sampled = 1u;
+    }
+
+    uint32_t format = SpvImageFormat_::SpvImageFormatUnknown;
+    if (auto* st = texture->As<type::StorageTexture>()) {
+        format = TexelFormat(st->texel_format());
+    }
+
+    module_.PushType(spv::Op::OpTypeImage,
+                     {id, sampled_type, dim, depth, array, ms, sampled, format});
+}
+
 void GeneratorImplIr::EmitFunction(ir::Function* func) {
     auto id = Value(func);
 
@@ -1187,4 +1282,51 @@
     }
 }
 
+uint32_t GeneratorImplIr::TexelFormat(const builtin::TexelFormat format) {
+    switch (format) {
+        case builtin::TexelFormat::kBgra8Unorm:
+            TINT_ICE(Writer, diagnostics_)
+                << "bgra8unorm should have been polyfilled to rgba8unorm";
+            return SpvImageFormatUnknown;
+        case builtin::TexelFormat::kR32Uint:
+            return SpvImageFormatR32ui;
+        case builtin::TexelFormat::kR32Sint:
+            return SpvImageFormatR32i;
+        case builtin::TexelFormat::kR32Float:
+            return SpvImageFormatR32f;
+        case builtin::TexelFormat::kRgba8Unorm:
+            return SpvImageFormatRgba8;
+        case builtin::TexelFormat::kRgba8Snorm:
+            return SpvImageFormatRgba8Snorm;
+        case builtin::TexelFormat::kRgba8Uint:
+            return SpvImageFormatRgba8ui;
+        case builtin::TexelFormat::kRgba8Sint:
+            return SpvImageFormatRgba8i;
+        case builtin::TexelFormat::kRg32Uint:
+            module_.PushCapability(SpvCapabilityStorageImageExtendedFormats);
+            return SpvImageFormatRg32ui;
+        case builtin::TexelFormat::kRg32Sint:
+            module_.PushCapability(SpvCapabilityStorageImageExtendedFormats);
+            return SpvImageFormatRg32i;
+        case builtin::TexelFormat::kRg32Float:
+            module_.PushCapability(SpvCapabilityStorageImageExtendedFormats);
+            return SpvImageFormatRg32f;
+        case builtin::TexelFormat::kRgba16Uint:
+            return SpvImageFormatRgba16ui;
+        case builtin::TexelFormat::kRgba16Sint:
+            return SpvImageFormatRgba16i;
+        case builtin::TexelFormat::kRgba16Float:
+            return SpvImageFormatRgba16f;
+        case builtin::TexelFormat::kRgba32Uint:
+            return SpvImageFormatRgba32ui;
+        case builtin::TexelFormat::kRgba32Sint:
+            return SpvImageFormatRgba32i;
+        case builtin::TexelFormat::kRgba32Float:
+            return SpvImageFormatRgba32f;
+        case builtin::TexelFormat::kUndefined:
+            return SpvImageFormatUnknown;
+    }
+    return SpvImageFormatUnknown;
+}
+
 }  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.h b/src/tint/writer/spirv/ir/generator_impl_ir.h
index e2a78bc..57a7932 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.h
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.h
@@ -54,6 +54,7 @@
 }  // namespace tint::ir
 namespace tint::type {
 class Struct;
+class Texture;
 class Type;
 }  // namespace tint::type
 
@@ -100,6 +101,11 @@
     /// @returns the enum value of the corresponding SPIR-V builtin
     uint32_t Builtin(builtin::BuiltinValue builtin, builtin::AddressSpace addrspace);
 
+    /// Convert a texel format to the corresponding SPIR-V enum value, adding required capabilities.
+    /// @param format the format to convert
+    /// @returns the enum value of the corresponding SPIR-V texel format
+    uint32_t TexelFormat(const builtin::TexelFormat format);
+
     /// Get the result ID of the constant `constant`, emitting its instruction if necessary.
     /// @param constant the constant to get the ID for
     /// @returns the result ID of the constant
@@ -138,6 +144,11 @@
                         const type::Struct* str,
                         builtin::AddressSpace addrspace = builtin::AddressSpace::kUndefined);
 
+    /// Emit a texture type.
+    /// @param id the result ID to use
+    /// @param texture the texture type to emit
+    void EmitTextureType(uint32_t id, const type::Texture* texture);
+
     /// Emit a function.
     /// @param func the function to emit
     void EmitFunction(ir::Function* func);
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
index 9caa027..4284884 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
@@ -13,9 +13,14 @@
 // limitations under the License.
 
 #include "src/tint/type/bool.h"
+#include "src/tint/type/depth_multisampled_texture.h"
+#include "src/tint/type/depth_texture.h"
 #include "src/tint/type/f16.h"
 #include "src/tint/type/f32.h"
 #include "src/tint/type/i32.h"
+#include "src/tint/type/multisampled_texture.h"
+#include "src/tint/type/sampled_texture.h"
+#include "src/tint/type/storage_texture.h"
 #include "src/tint/type/type.h"
 #include "src/tint/type/u32.h"
 #include "src/tint/type/void.h"
@@ -196,6 +201,172 @@
     EXPECT_INST("%MyStruct = OpTypeStruct %mat3v3float %_arr__arr_mat2v4half_uint_4_uint_4");
 }
 
+TEST_F(SpvGeneratorImplTest, Type_Sampler) {
+    generator_.Type(ty.sampler());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpTypeSampler");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_SamplerComparison) {
+    generator_.Type(ty.comparison_sampler());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpTypeSampler");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_Samplers_Dedup) {
+    auto id = generator_.Type(ty.sampler());
+    EXPECT_EQ(generator_.Type(ty.comparison_sampler()), id);
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+}
+
+using Dim = type::TextureDimension;
+struct TextureCase {
+    std::string result;
+    Dim dim;
+    TestElementType format = kF32;
+};
+
+using Type_SampledTexture = SpvGeneratorImplTestWithParam<TextureCase>;
+TEST_P(Type_SampledTexture, Emit) {
+    auto params = GetParam();
+    generator_.Type(ty.Get<type::SampledTexture>(params.dim, MakeScalarType(params.format)));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    Type_SampledTexture,
+    testing::Values(
+        TextureCase{"%1 = OpTypeImage %float 1D 0 0 0 1 Unknown", Dim::k1d, kF32},
+        TextureCase{"%1 = OpTypeImage %float 2D 0 0 0 1 Unknown", Dim::k2d, kF32},
+        TextureCase{"%1 = OpTypeImage %float 2D 0 1 0 1 Unknown", Dim::k2dArray, kF32},
+        TextureCase{"%1 = OpTypeImage %float 3D 0 0 0 1 Unknown", Dim::k3d, kF32},
+        TextureCase{"%1 = OpTypeImage %float Cube 0 0 0 1 Unknown", Dim::kCube, kF32},
+        TextureCase{"%1 = OpTypeImage %float Cube 0 1 0 1 Unknown", Dim::kCubeArray, kF32},
+        TextureCase{"%1 = OpTypeImage %int 1D 0 0 0 1 Unknown", Dim::k1d, kI32},
+        TextureCase{"%1 = OpTypeImage %int 2D 0 0 0 1 Unknown", Dim::k2d, kI32},
+        TextureCase{"%1 = OpTypeImage %int 2D 0 1 0 1 Unknown", Dim::k2dArray, kI32},
+        TextureCase{"%1 = OpTypeImage %int 3D 0 0 0 1 Unknown", Dim::k3d, kI32},
+        TextureCase{"%1 = OpTypeImage %int Cube 0 0 0 1 Unknown", Dim::kCube, kI32},
+        TextureCase{"%1 = OpTypeImage %int Cube 0 1 0 1 Unknown", Dim::kCubeArray, kI32},
+        TextureCase{"%1 = OpTypeImage %uint 1D 0 0 0 1 Unknown", Dim::k1d, kU32},
+        TextureCase{"%1 = OpTypeImage %uint 2D 0 0 0 1 Unknown", Dim::k2d, kU32},
+        TextureCase{"%1 = OpTypeImage %uint 2D 0 1 0 1 Unknown", Dim::k2dArray, kU32},
+        TextureCase{"%1 = OpTypeImage %uint 3D 0 0 0 1 Unknown", Dim::k3d, kU32},
+        TextureCase{"%1 = OpTypeImage %uint Cube 0 0 0 1 Unknown", Dim::kCube, kU32},
+        TextureCase{"%1 = OpTypeImage %uint Cube 0 1 0 1 Unknown", Dim::kCubeArray, kU32}));
+
+using Type_MultisampledTexture = SpvGeneratorImplTestWithParam<TextureCase>;
+TEST_P(Type_MultisampledTexture, Emit) {
+    auto params = GetParam();
+    generator_.Type(ty.Get<type::MultisampledTexture>(params.dim, MakeScalarType(params.format)));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    Type_MultisampledTexture,
+    testing::Values(TextureCase{"%1 = OpTypeImage %float 2D 0 0 1 1 Unknown", Dim::k2d, kF32},
+                    TextureCase{"%1 = OpTypeImage %int 2D 0 0 1 1 Unknown", Dim::k2d, kI32},
+                    TextureCase{"%1 = OpTypeImage %uint 2D 0 0 1 1 Unknown", Dim::k2d, kU32}));
+
+using Type_DepthTexture = SpvGeneratorImplTestWithParam<TextureCase>;
+TEST_P(Type_DepthTexture, Emit) {
+    auto params = GetParam();
+    generator_.Type(ty.Get<type::DepthTexture>(params.dim));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.result);
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    Type_DepthTexture,
+    testing::Values(TextureCase{"%1 = OpTypeImage %float 2D 0 0 0 1 Unknown", Dim::k2d},
+                    TextureCase{"%1 = OpTypeImage %float 2D 0 1 0 1 Unknown", Dim::k2dArray},
+                    TextureCase{"%1 = OpTypeImage %float Cube 0 0 0 1 Unknown", Dim::kCube},
+                    TextureCase{"%1 = OpTypeImage %float Cube 0 1 0 1 Unknown", Dim::kCubeArray}));
+
+TEST_F(SpvGeneratorImplTest, Type_DepthMultiSampledTexture) {
+    generator_.Type(ty.Get<type::DepthMultisampledTexture>(Dim::k2d));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpTypeImage %float 2D 0 0 1 1 Unknown");
+}
+
+using Format = builtin::TexelFormat;
+struct StorageTextureCase {
+    std::string result;
+    Dim dim;
+    Format format;
+};
+using Type_StorageTexture = SpvGeneratorImplTestWithParam<StorageTextureCase>;
+TEST_P(Type_StorageTexture, Emit) {
+    auto params = GetParam();
+    generator_.Type(
+        ty.Get<type::StorageTexture>(params.dim, params.format, builtin::Access::kWrite,
+                                     type::StorageTexture::SubtypeFor(params.format, mod.Types())));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.result);
+    if (params.format == builtin::TexelFormat::kRg32Uint ||
+        params.format == builtin::TexelFormat::kRg32Sint ||
+        params.format == builtin::TexelFormat::kRg32Float) {
+        EXPECT_INST("OpCapability StorageImageExtendedFormats");
+    }
+}
+INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
+                         Type_StorageTexture,
+                         testing::Values(
+                             // Test all the dimensions with a single format.
+                             StorageTextureCase{"%1 = OpTypeImage %float 1D 0 0 0 2 R32f",  //
+                                                Dim::k1d, Format::kR32Float},
+                             StorageTextureCase{"%1 = OpTypeImage %float 2D 0 0 0 2 R32f",  //
+                                                Dim::k2d, Format::kR32Float},
+                             StorageTextureCase{"%1 = OpTypeImage %float 2D 0 1 0 2 R32f",  //
+                                                Dim::k2dArray, Format::kR32Float},
+                             StorageTextureCase{"%1 = OpTypeImage %float 3D 0 0 0 2 R32f",  //
+                                                Dim::k3d, Format::kR32Float},
+
+                             // Test all the formats with 2D.
+                             // TODO(jrprice): Enable this format when we polyfill it.
+                             // StorageTextureCase{"%1 = OpTypeImage %float 2D 0 0 0 2 Bgra8Unorm",
+                             //                    Dim::k2d, Format::kBgra8Unorm},
+                             StorageTextureCase{"%1 = OpTypeImage %int 2D 0 0 0 2 R32i",  //
+                                                Dim::k2d, Format::kR32Sint},
+                             StorageTextureCase{"%1 = OpTypeImage %uint 2D 0 0 0 2 R32u",  //
+                                                Dim::k2d, Format::kR32Uint},
+                             StorageTextureCase{"%1 = OpTypeImage %float 2D 0 0 0 2 Rg32f",  //
+                                                Dim::k2d, Format::kRg32Float},
+                             StorageTextureCase{"%1 = OpTypeImage %int 2D 0 0 0 2 Rg32i",  //
+                                                Dim::k2d, Format::kRg32Sint},
+                             StorageTextureCase{"%1 = OpTypeImage %uint 2D 0 0 0 2 Rg32ui",  //
+                                                Dim::k2d, Format::kRg32Uint},
+                             StorageTextureCase{"%1 = OpTypeImage %float 2D 0 0 0 2 Rgba16f",  //
+                                                Dim::k2d, Format::kRgba16Float},
+                             StorageTextureCase{"%1 = OpTypeImage %int 2D 0 0 0 2 Rgba16i",  //
+                                                Dim::k2d, Format::kRgba16Sint},
+                             StorageTextureCase{"%1 = OpTypeImage %uint 2D 0 0 0 2 Rgba16ui",  //
+                                                Dim::k2d, Format::kRgba16Uint},
+                             StorageTextureCase{"%1 = OpTypeImage %float 2D 0 0 0 2 Rgba32f",  //
+                                                Dim::k2d, Format::kRgba32Float},
+                             StorageTextureCase{"%1 = OpTypeImage %int 2D 0 0 0 2 Rgba32i",  //
+                                                Dim::k2d, Format::kRgba32Sint},
+                             StorageTextureCase{"%1 = OpTypeImage %uint 2D 0 0 0 2 Rgba32ui",  //
+                                                Dim::k2d, Format::kRgba32Uint},
+                             StorageTextureCase{"%1 = OpTypeImage %int 2D 0 0 0 2 Rgba8i",  //
+                                                Dim::k2d, Format::kRgba8Sint},
+                             StorageTextureCase{"%1 = OpTypeImage %float 2D 0 0 0 2 Rgba8Snorm",  //
+                                                Dim::k2d, Format::kRgba8Snorm},
+                             StorageTextureCase{"%1 = OpTypeImage %uint 2D 0 0 0 2 Rgba8ui",  //
+                                                Dim::k2d, Format::kRgba8Uint},
+                             StorageTextureCase{"%1 = OpTypeImage %float 2D 0 0 0 2 Rgba8",  //
+                                                Dim::k2d, Format::kRgba8Unorm}));
+
 // Test that we can emit multiple types.
 // Includes types with the same opcode but different parameters.
 TEST_F(SpvGeneratorImplTest, Type_Multiple) {