[hlsl] Add `textureDimensions` support.

This Cl adds support to the HLSL IR backend for `textureDimensions`

Bug: 42251045
Change-Id: I579cdc337d0eb2c81c07fa5fa2d175ed47c4506f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/197176
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/hlsl/writer/builtin_test.cc b/src/tint/lang/hlsl/writer/builtin_test.cc
index 12edc94..6cac608 100644
--- a/src/tint/lang/hlsl/writer/builtin_test.cc
+++ b/src/tint/lang/hlsl/writer/builtin_test.cc
@@ -30,6 +30,7 @@
 #include "src/tint/lang/core/number.h"
 #include "src/tint/lang/core/type/depth_multisampled_texture.h"
 #include "src/tint/lang/core/type/sampled_texture.h"
+#include "src/tint/lang/core/type/texture_dimension.h"
 #include "src/tint/lang/hlsl/writer/helper_test.h"
 
 #include "gtest/gtest.h"
@@ -841,6 +842,116 @@
 )");
 }
 
+TEST_F(HlslWriterTest, BuiltinTextureDimension1D) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k1d, ty.f32()));
+
+    auto* func = b.Function("foo", ty.void_());
+    func->SetParams({t});
+
+    b.Append(func->Block(), [&] {
+        b.Let("d", b.Call(ty.u32(), core::BuiltinFn::kTextureDimensions, t));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+void foo(Texture1D<float4> t) {
+  uint v = 0u;
+  t.GetDimensions(v);
+  uint d = v;
+}
+
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, BuiltinTextureDimension2D) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32()));
+
+    auto* func = b.Function("foo", ty.void_());
+    func->SetParams({t});
+
+    b.Append(func->Block(), [&] {
+        b.Let("d", b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, t));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+void foo(Texture2D<float4> t) {
+  uint2 v = (0u).xx;
+  t.GetDimensions(v[0u], v[1u]);
+  uint2 d = v;
+}
+
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, BuiltinTextureDimension2dLOD) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32()));
+
+    auto* func = b.Function("foo", ty.void_());
+    func->SetParams({t});
+
+    b.Append(func->Block(), [&] {
+        b.Let("d", b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, t, 1_i));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+void foo(Texture2D<float4> t) {
+  uint3 v = (0u).xxx;
+  t.GetDimensions(0u, v[0u], v[1u], v[2u]);
+  uint3 v_1 = (0u).xxx;
+  t.GetDimensions(uint(min(uint(1), (v.z - 1u))), v_1[0u], v_1[1u], v_1[2u]);
+  uint2 d = v_1.xy;
+}
+
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, BuiltinTextureDimension3D) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k3d, ty.f32()));
+
+    auto* func = b.Function("foo", ty.void_());
+    func->SetParams({t});
+
+    b.Append(func->Block(), [&] {
+        b.Let("d", b.Call(ty.vec3<u32>(), core::BuiltinFn::kTextureDimensions, t));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+void foo(Texture3D<float4> t) {
+  uint3 v = (0u).xxx;
+  t.GetDimensions(v[0u], v[1u], v[2u]);
+  uint3 d = v;
+}
+
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+}
+
+)");
+}
+
 TEST_F(HlslWriterTest, BuiltinTextureLayers2dArray) {
     auto* t = b.FunctionParam(
         "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2dArray, ty.f32()));
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
index 2fc9499..9dc5ea8 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
@@ -38,6 +38,8 @@
 #include "src/tint/lang/core/type/manager.h"
 #include "src/tint/lang/core/type/multisampled_texture.h"
 #include "src/tint/lang/core/type/texture.h"
+#include "src/tint/lang/core/type/u32.h"
+#include "src/tint/lang/core/type/vector.h"
 #include "src/tint/lang/hlsl/builtin_fn.h"
 #include "src/tint/lang/hlsl/ir/builtin_call.h"
 #include "src/tint/lang/hlsl/ir/member_builtin_call.h"
@@ -82,6 +84,7 @@
                 switch (call->Func()) {
                     case core::BuiltinFn::kSelect:
                     case core::BuiltinFn::kSign:
+                    case core::BuiltinFn::kTextureDimensions:
                     case core::BuiltinFn::kTextureNumLayers:
                     case core::BuiltinFn::kTextureNumLevels:
                     case core::BuiltinFn::kTextureNumSamples:
@@ -121,6 +124,9 @@
                 case core::BuiltinFn::kSign:
                     Sign(call);
                     break;
+                case core::BuiltinFn::kTextureDimensions:
+                    TextureDimensions(call);
+                    break;
                 case core::BuiltinFn::kTextureNumLayers:
                     TextureNumLayers(call);
                     break;
@@ -465,6 +471,91 @@
         call->Destroy();
     }
 
+    void TextureDimensions(core::ir::CoreBuiltinCall* call) {
+        auto* tex = call->Args()[0];
+        auto* tex_type = tex->Type()->As<core::type::Texture>();
+        bool has_level = call->Args().Length() > 1;
+        bool is_ms =
+            tex_type
+                ->IsAnyOf<core::type::MultisampledTexture, core::type::DepthMultisampledTexture>();
+
+        Vector<uint32_t, 2> swizzle{};
+        uint32_t query_size = 0;
+        switch (tex_type->dim()) {
+            case core::type::TextureDimension::kNone:
+                TINT_ICE() << "texture dimension is kNone";
+            case core::type::TextureDimension::k1d:
+                query_size = 1;
+                break;
+            case core::type::TextureDimension::k2d:
+                if (is_ms) {
+                    query_size = 3;
+                    swizzle = {0_u, 1_u};
+                } else {
+                    query_size = 2;
+                }
+                break;
+            case core::type::TextureDimension::k2dArray:
+                query_size = is_ms ? 4 : 3;
+                swizzle = {0_u, 1_u};
+                break;
+            case core::type::TextureDimension::k3d:
+                query_size = 3;
+                break;
+            case core::type::TextureDimension::kCube:
+                query_size = 2;
+                break;
+            case core::type::TextureDimension::kCubeArray:
+                query_size = 3;
+                swizzle = {0_u, 1_u};
+                break;
+        }
+
+        // Query with a `level` adds a `number_of_levels` output parameter
+        if (has_level) {
+            // If there was no swizzle, we will need to swizzle out the required query parameters as
+            // the query will increase by one item.
+            if (swizzle.IsEmpty()) {
+                for (uint32_t i = 0; i < query_size; ++i) {
+                    swizzle.Push(i);
+                }
+            }
+            query_size += 1;
+        }
+
+        const core::type::Type* query_ty = ty.u32();
+        if (query_size > 1) {
+            query_ty = ty.vec(query_ty, query_size);
+        }
+
+        b.InsertBefore(call, [&] {
+            Vector<core::ir::Value*, 5> args;
+
+            // Push the level if needed
+            if (has_level) {
+                args.Push(b.Convert(ty.u32(), call->Args()[1])->Result(0));
+            }
+
+            core::ir::Instruction* query = b.Var(ty.ptr(function, query_ty));
+            if (query_size == 1) {
+                args.Push(query->Result(0));
+            } else {
+                for (uint32_t i = 0; i < query_size; ++i) {
+                    args.Push(b.Access(ty.ptr<function, u32>(), query, u32(i))->Result(0));
+                }
+            }
+
+            b.MemberCall<hlsl::ir::MemberBuiltinCall>(ty.void_(), hlsl::BuiltinFn::kGetDimensions,
+                                                      tex, args);
+            query = b.Load(query);
+            if (!swizzle.IsEmpty()) {
+                query = b.Swizzle(ty.vec2<u32>(), query, swizzle);
+            }
+            call->Result(0)->ReplaceAllUsesWith(query->Result(0));
+        });
+        call->Destroy();
+    }
+
     void TextureNumSamples(core::ir::CoreBuiltinCall* call) {
         auto* tex = call->Args()[0];
         auto* tex_type = tex->Type()->As<core::type::Texture>();
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
index 1cf2910..92d0469 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
@@ -578,5 +578,163 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(HlslWriter_BuiltinPolyfillTest, TextureDimensions_1d) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k1d, ty.f32()));
+    auto* func = b.Function("foo", ty.u32());
+    func->SetParams({t});
+    b.Append(func->Block(), [&] {
+        auto* result = b.Call<u32>(core::BuiltinFn::kTextureDimensions, t);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_1d<f32>):u32 {
+  $B1: {
+    %3:u32 = textureDimensions %t
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_1d<f32>):u32 {
+  $B1: {
+    %3:ptr<function, u32, read_write> = var
+    %4:void = %t.GetDimensions %3
+    %5:u32 = load %3
+    ret %5
+  }
+}
+)";
+
+    capabilities = core::ir::Capabilities{core::ir::Capability::kAllowVectorElementPointer};
+    Run(BuiltinPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, TextureDimensions_2d_WithoutLod) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32()));
+    auto* func = b.Function("foo", ty.vec2<u32>());
+    func->SetParams({t});
+    b.Append(func->Block(), [&] {
+        auto* result = b.Call<vec2<u32>>(core::BuiltinFn::kTextureDimensions, t);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_2d<f32>):vec2<u32> {
+  $B1: {
+    %3:vec2<u32> = textureDimensions %t
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_2d<f32>):vec2<u32> {
+  $B1: {
+    %3:ptr<function, vec2<u32>, read_write> = var
+    %4:ptr<function, u32, read_write> = access %3, 0u
+    %5:ptr<function, u32, read_write> = access %3, 1u
+    %6:void = %t.GetDimensions %4, %5
+    %7:vec2<u32> = load %3
+    ret %7
+  }
+}
+)";
+
+    capabilities = core::ir::Capabilities{core::ir::Capability::kAllowVectorElementPointer};
+    Run(BuiltinPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, TextureDimensions_2d_WithI32Lod) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32()));
+    auto* func = b.Function("foo", ty.vec2<u32>());
+    func->SetParams({t});
+    b.Append(func->Block(), [&] {
+        auto* result = b.Call<vec2<u32>>(core::BuiltinFn::kTextureDimensions, t, 3_i);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_2d<f32>):vec2<u32> {
+  $B1: {
+    %3:vec2<u32> = textureDimensions %t, 3i
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_2d<f32>):vec2<u32> {
+  $B1: {
+    %3:u32 = convert 3i
+    %4:ptr<function, vec3<u32>, read_write> = var
+    %5:ptr<function, u32, read_write> = access %4, 0u
+    %6:ptr<function, u32, read_write> = access %4, 1u
+    %7:ptr<function, u32, read_write> = access %4, 2u
+    %8:void = %t.GetDimensions %3, %5, %6, %7
+    %9:vec3<u32> = load %4
+    %10:vec2<u32> = swizzle %9, xy
+    ret %10
+  }
+}
+)";
+
+    capabilities = core::ir::Capabilities{core::ir::Capability::kAllowVectorElementPointer};
+    Run(BuiltinPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, TextureDimensions_3d) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k3d, ty.f32()));
+    auto* func = b.Function("foo", ty.vec3<u32>());
+    func->SetParams({t});
+    b.Append(func->Block(), [&] {
+        auto* result = b.Call<vec3<u32>>(core::BuiltinFn::kTextureDimensions, t);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_3d<f32>):vec3<u32> {
+  $B1: {
+    %3:vec3<u32> = textureDimensions %t
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_3d<f32>):vec3<u32> {
+  $B1: {
+    %3:ptr<function, vec3<u32>, read_write> = var
+    %4:ptr<function, u32, read_write> = access %3, 0u
+    %5:ptr<function, u32, read_write> = access %3, 1u
+    %6:ptr<function, u32, read_write> = access %3, 2u
+    %7:void = %t.GetDimensions %4, %5, %6
+    %8:vec3<u32> = load %3
+    ret %8
+  }
+}
+)";
+
+    capabilities = core::ir::Capabilities{core::ir::Capability::kAllowVectorElementPointer};
+    Run(BuiltinPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace
 }  // namespace tint::hlsl::writer::raise