writer/hlsl: Emit texture builtin functions

Fixes: tint:146
Change-Id: I2a59fd369c767163ac1b72976883a577fd51a632
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33422
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index dd73a2f..e0789e4 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1161,6 +1161,7 @@
     "src/writer/hlsl/generator_impl_if_test.cc",
     "src/writer/hlsl/generator_impl_import_test.cc",
     "src/writer/hlsl/generator_impl_intrinsic_test.cc",
+    "src/writer/hlsl/generator_impl_intrinsic_texture_test.cc",
     "src/writer/hlsl/generator_impl_loop_test.cc",
     "src/writer/hlsl/generator_impl_member_accessor_test.cc",
     "src/writer/hlsl/generator_impl_module_constant_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fed1220..94aceb0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -675,6 +675,7 @@
     writer/hlsl/generator_impl_identifier_test.cc
     writer/hlsl/generator_impl_if_test.cc
     writer/hlsl/generator_impl_intrinsic_test.cc
+    writer/hlsl/generator_impl_intrinsic_texture_test.cc
     writer/hlsl/generator_impl_import_test.cc
     writer/hlsl/generator_impl_loop_test.cc
     writer/hlsl/generator_impl_member_accessor_test.cc
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 5d81f2c..5eb1a3b 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -103,6 +103,20 @@
   return 0;
 }
 
+ast::TypeConstructorExpression* AsVectorConstructor(ast::Expression* expr) {
+  if (!expr->IsConstructor())
+    return nullptr;
+  auto* constructor = expr->AsConstructor();
+  if (!constructor->IsTypeConstructor()) {
+    return nullptr;
+  }
+  auto* type_constructor = constructor->AsTypeConstructor();
+  if (!type_constructor->type()->IsVector()) {
+    return nullptr;
+  }
+  return type_constructor;
+}
+
 }  // namespace
 
 GeneratorImpl::GeneratorImpl(Context* ctx, ast::Module* module)
@@ -634,8 +648,7 @@
       auto name = generate_intrinsic_name(ident->intrinsic());
       if (name.empty()) {
         if (ast::intrinsic::IsTextureIntrinsic(ident->intrinsic())) {
-          error_ = "Textures not implemented yet";
-          return false;
+          return EmitTextureCall(pre, out, expr);
         }
         name = generate_builtin_name(expr);
         if (name.empty()) {
@@ -713,6 +726,110 @@
   return true;
 }
 
+bool GeneratorImpl::EmitTextureCall(std::ostream& pre,
+                                    std::ostream& out,
+                                    ast::CallExpression* expr) {
+  make_indent(out);
+
+  auto* ident = expr->func()->AsIdentifier();
+
+  auto params = expr->params();
+  auto* signature = static_cast<const ast::intrinsic::TextureSignature*>(
+      ident->intrinsic_signature());
+  auto& pidx = signature->params.idx;
+  auto const kNotUsed = ast::intrinsic::TextureSignature::Parameters::kNotUsed;
+
+  if (!EmitExpression(pre, out, params[pidx.texture]))
+    return false;
+
+  switch (ident->intrinsic()) {
+    case ast::Intrinsic::kTextureSample:
+      out << ".Sample(";
+      break;
+    case ast::Intrinsic::kTextureSampleBias:
+      out << ".SampleBias(";
+      break;
+    case ast::Intrinsic::kTextureSampleLevel:
+      out << ".SampleLevel(";
+      break;
+    case ast::Intrinsic::kTextureSampleGrad:
+      out << ".SampleGrad(";
+      break;
+    case ast::Intrinsic::kTextureSampleCompare:
+      out << ".SampleCmp(";
+      break;
+    default:
+      error_ = "Internal compiler error: Unhandled texture intrinsic '" +
+               ident->name() + "'";
+      break;
+  }
+
+  if (!EmitExpression(pre, out, params[pidx.sampler]))
+    return false;
+
+  out << ", ";
+
+  // TODO(ben-clayton): Refactor this with the near identical code in
+  // src/writer/spirv/builder.cc.
+  if (pidx.array_index != kNotUsed) {
+    // Array index needs to be appended to the coordinates.
+    auto* param_coords = params[pidx.coords];
+    auto* param_array_index = params[pidx.array_index];
+
+    uint32_t packed_coords_size;
+    ast::type::Type* packed_coords_el_ty;  // Currenly must be f32.
+    if (param_coords->result_type()->IsVector()) {
+      auto* vec = param_coords->result_type()->AsVector();
+      packed_coords_size = vec->size() + 1;
+      packed_coords_el_ty = vec->type();
+    } else {
+      packed_coords_size = 2;
+      packed_coords_el_ty = param_coords->result_type();
+    }
+
+    // Cast param_array_index to the vector element type
+    ast::TypeConstructorExpression array_index_cast(packed_coords_el_ty,
+                                                    {param_array_index});
+    array_index_cast.set_result_type(packed_coords_el_ty);
+
+    ast::type::VectorType packed_coords_ty(packed_coords_el_ty,
+                                           packed_coords_size);
+
+    ast::ExpressionList coords;
+    // If the coordinates are already passed in a vector constructor, extract
+    // the elements into the new vector instead of nesting a vector-in-vector.
+    if (auto* vc = AsVectorConstructor(param_coords)) {
+      coords = vc->values();
+    } else {
+      coords.emplace_back(param_coords);
+    }
+    coords.emplace_back(&array_index_cast);
+
+    ast::TypeConstructorExpression constructor{&packed_coords_ty,
+                                               std::move(coords)};
+
+    if (!EmitExpression(pre, out, &constructor))
+      return false;
+
+  } else {
+    if (!EmitExpression(pre, out, params[pidx.coords]))
+      return false;
+  }
+
+  for (auto idx : {pidx.depth_ref, pidx.bias, pidx.level, pidx.ddx, pidx.ddy,
+                   pidx.offset}) {
+    if (idx != kNotUsed) {
+      out << ", ";
+      if (!EmitExpression(pre, out, params[idx]))
+        return false;
+    }
+  }
+
+  out << ")";
+
+  return true;
+}
+
 std::string GeneratorImpl::generate_builtin_name(ast::CallExpression* expr) {
   std::string out;
   auto* ident = expr->func()->AsIdentifier();
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index c51fcf2..a793f77 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -130,6 +130,15 @@
   bool EmitCall(std::ostream& pre,
                 std::ostream& out,
                 ast::CallExpression* expr);
+  /// Handles generating a call to a texture function (`textureSample`,
+  /// `textureSampleGrad`, etc)
+  /// @param pre the preamble for the expression stream
+  /// @param out the output of the expression stream
+  /// @param expr the call expression
+  /// @returns true if the call expression is emitted
+  bool EmitTextureCall(std::ostream& pre,
+                       std::ostream& out,
+                       ast::CallExpression* expr);
   /// Handles a case statement
   /// @param out the output stream
   /// @param stmt the statement
diff --git a/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc b/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
new file mode 100644
index 0000000..3e13608
--- /dev/null
+++ b/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
@@ -0,0 +1,221 @@
+// Copyright 2020 The Tint 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 <memory>
+
+#include "gtest/gtest.h"
+#include "src/ast/builder.h"
+#include "src/ast/intrinsic_texture_helper_test.h"
+#include "src/ast/type/depth_texture_type.h"
+#include "src/ast/type/sampled_texture_type.h"
+#include "src/context.h"
+#include "src/type_determiner.h"
+#include "src/writer/hlsl/generator_impl.h"
+#include "src/writer/spirv/builder.h"
+#include "src/writer/spirv/spv_dump.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+std::string expected_texture_overload(
+    ast::intrinsic::test::ValidTextureOverload overload) {
+  using ValidTextureOverload = ast::intrinsic::test::ValidTextureOverload;
+  switch (overload) {
+    case ValidTextureOverload::kSample1dF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, 1.0f))";
+    case ValidTextureOverload::kSample1dArrayF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float2(1.0f, float(2u))))";
+    case ValidTextureOverload::kSample2dF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float2(1.0f, 2.0f)))";
+    case ValidTextureOverload::kSample2dOffsetF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float2(1.0f, 2.0f), int2(3, 4)))";
+    case ValidTextureOverload::kSample2dArrayF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, float(3u))))";
+    case ValidTextureOverload::kSample2dArrayOffsetF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), int2(4, 5)))";
+    case ValidTextureOverload::kSample3dF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kSample3dOffsetF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), int3(4, 5, 6)))";
+    case ValidTextureOverload::kSampleCubeF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kSampleCubeArrayF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float4(1.0f, 2.0f, 3.0f, float(4u))))";
+    case ValidTextureOverload::kSampleDepth2dF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float2(1.0f, 2.0f)))";
+    case ValidTextureOverload::kSampleDepth2dOffsetF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float2(1.0f, 2.0f), int2(3, 4)))";
+    case ValidTextureOverload::kSampleDepth2dArrayF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, float(3u))))";
+    case ValidTextureOverload::kSampleDepth2dArrayOffsetF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), int2(4, 5)))";
+    case ValidTextureOverload::kSampleDepthCubeF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float3(1.0f, 2.0f, 3.0f)))";
+    case ValidTextureOverload::kSampleDepthCubeArrayF32:
+      return R"(texture_tint_0.Sample(sampler_tint_0, float4(1.0f, 2.0f, 3.0f, float(4u))))";
+    case ValidTextureOverload::kSampleBias2dF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float2(1.0f, 2.0f), 3.0f))";
+    case ValidTextureOverload::kSampleBias2dOffsetF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
+    case ValidTextureOverload::kSampleBias2dArrayF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float3(1.0f, 2.0f, float(4u)), 3.0f))";
+    case ValidTextureOverload::kSampleBias2dArrayOffsetF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), 4.0f, int2(5, 6)))";
+    case ValidTextureOverload::kSampleBias3dF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kSampleBias3dOffsetF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4.0f, int3(5, 6, 7)))";
+    case ValidTextureOverload::kSampleBiasCubeF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kSampleBiasCubeArrayF32:
+      return R"(texture_tint_0.SampleBias(sampler_tint_0, float4(1.0f, 2.0f, 3.0f, float(3u)), 4.0f))";
+    case ValidTextureOverload::kSampleLevel2dF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float2(1.0f, 2.0f), 3.0f))";
+    case ValidTextureOverload::kSampleLevel2dOffsetF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
+    case ValidTextureOverload::kSampleLevel2dArrayF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), 4.0f))";
+    case ValidTextureOverload::kSampleLevel2dArrayOffsetF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), 4.0f, int2(5, 6)))";
+    case ValidTextureOverload::kSampleLevel3dF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kSampleLevel3dOffsetF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4.0f, int3(5, 6, 7)))";
+    case ValidTextureOverload::kSampleLevelCubeF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kSampleLevelCubeArrayF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float4(1.0f, 2.0f, 3.0f, float(4u)), 5.0f))";
+    case ValidTextureOverload::kSampleLevelDepth2dF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float2(1.0f, 2.0f), 3u))";
+    case ValidTextureOverload::kSampleLevelDepth2dOffsetF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float2(1.0f, 2.0f), 3u, int2(4, 5)))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), 4u))";
+    case ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), 4u, int2(5, 6)))";
+    case ValidTextureOverload::kSampleLevelDepthCubeF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4u))";
+    case ValidTextureOverload::kSampleLevelDepthCubeArrayF32:
+      return R"(texture_tint_0.SampleLevel(sampler_tint_0, float4(1.0f, 2.0f, 3.0f, float(4u)), 5u))";
+    case ValidTextureOverload::kSampleGrad2dF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float2(1.0f, 2.0f), float2(3.0f, 4.0f), float2(5.0f, 6.0f)))";
+    case ValidTextureOverload::kSampleGrad2dOffsetF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float2(1.0f, 2.0f), float2(3.0f, 4.0f), float2(5.0f, 6.0f), int2(7, 8)))";
+    case ValidTextureOverload::kSampleGrad2dArrayF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), float2(4.0f, 5.0f), float2(6.0f, 7.0f)))";
+    case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float3(1.0f, 2.0f, float(3u)), float2(4.0f, 5.0f), float2(6.0f, 7.0f), int2(8, 9)))";
+    case ValidTextureOverload::kSampleGrad3dF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)))";
+    case ValidTextureOverload::kSampleGrad3dOffsetF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f), int3(10, 11, 12)))";
+    case ValidTextureOverload::kSampleGradCubeF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), float3(4.0f, 5.0f, 6.0f), float3(7.0f, 8.0f, 9.0f)))";
+    case ValidTextureOverload::kSampleGradCubeArrayF32:
+      return R"(texture_tint_0.SampleGrad(sampler_tint_0, float4(1.0f, 2.0f, 3.0f, float(4u)), float3(5.0f, 6.0f, 7.0f), float3(8.0f, 9.0f, 10.0f)))";
+    case ValidTextureOverload::kSampleGradDepth2dF32:
+      return R"(texture_tint_0.SampleCmp(sampler_tint_0, float2(1.0f, 2.0f), 3.0f))";
+    case ValidTextureOverload::kSampleGradDepth2dOffsetF32:
+      return R"(texture_tint_0.SampleCmp(sampler_tint_0, float2(1.0f, 2.0f), 3.0f, int2(4, 5)))";
+    case ValidTextureOverload::kSampleGradDepth2dArrayF32:
+      return R"(texture_tint_0.SampleCmp(sampler_tint_0, float3(1.0f, 2.0f, float(4u)), 3.0f))";
+    case ValidTextureOverload::kSampleGradDepth2dArrayOffsetF32:
+      return R"(texture_tint_0.SampleCmp(sampler_tint_0, float3(1.0f, 2.0f, float(4u)), 3.0f, int2(5, 6)))";
+    case ValidTextureOverload::kSampleGradDepthCubeF32:
+      return R"(texture_tint_0.SampleCmp(sampler_tint_0, float3(1.0f, 2.0f, 3.0f), 4.0f))";
+    case ValidTextureOverload::kSampleGradDepthCubeArrayF32:
+      return R"(texture_tint_0.SampleCmp(sampler_tint_0, float4(1.0f, 2.0f, 3.0f, float(4u)), 5.0f))";
+  }
+  return "<unmatched texture overload>";
+}  // LINT - Ignore the length of this function
+
+class HlslGeneratorIntrinsicTextureTest
+    : public ast::BuilderWithContextAndModule,
+      public testing::TestWithParam<ast::intrinsic::test::TextureOverloadCase> {
+ protected:
+  void OnVariableBuilt(ast::Variable* var) override {
+    td.RegisterVariableForTesting(var);
+  }
+
+  /// @returns the result string
+  std::string result() const { return out.str(); }
+  /// @returns the pre result string
+  std::string pre_result() const { return pre.str(); }
+
+  /// The type determiner
+  TypeDeterminer td{ctx, mod};
+  /// The generator
+  GeneratorImpl gen{ctx, mod};
+  /// The output stream
+  std::ostringstream out;
+  /// The pre-output stream
+  std::ostringstream pre;
+};
+
+TEST_P(HlslGeneratorIntrinsicTextureTest, Call) {
+  auto param = GetParam();
+
+  ast::type::Type* datatype = nullptr;
+  switch (param.texture_data_type) {
+    case ast::intrinsic::test::TextureDataType::kF32:
+      datatype = ty.f32;
+      break;
+    case ast::intrinsic::test::TextureDataType::kU32:
+      datatype = ty.u32;
+      break;
+    case ast::intrinsic::test::TextureDataType::kI32:
+      datatype = ty.i32;
+      break;
+  }
+
+  ast::type::SamplerType sampler_type{param.sampler_kind};
+  switch (param.texture_kind) {
+    case ast::intrinsic::test::TextureKind::kRegular:
+      Var("texture", ast::StorageClass::kNone,
+          ctx->type_mgr().Get<ast::type::SampledTextureType>(
+              param.texture_dimension, datatype));
+      break;
+
+    case ast::intrinsic::test::TextureKind::kDepth:
+      Var("texture", ast::StorageClass::kNone,
+          ctx->type_mgr().Get<ast::type::DepthTextureType>(
+              param.texture_dimension));
+      break;
+  }
+
+  Var("sampler", ast::StorageClass::kNone, &sampler_type);
+
+  ast::CallExpression call{Expr(param.function), param.args(this)};
+
+  EXPECT_TRUE(td.DetermineResultType(&call)) << td.error();
+
+  ASSERT_TRUE(gen.EmitExpression(pre, out, &call)) << gen.error();
+
+  EXPECT_TRUE(pre_result().empty());
+
+  auto expected = expected_texture_overload(param.overload);
+  EXPECT_EQ(result(), expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorIntrinsicTextureTest,
+    HlslGeneratorIntrinsicTextureTest,
+    testing::ValuesIn(ast::intrinsic::test::TextureOverloadCase::ValidCases()));
+
+}  // namespace
+}  // namespace hlsl
+}  // namespace writer
+}  // namespace tint