Implement a Texture1D -> Texture2D transform.

This is required for GLSL ES, which doesn't support Texture1D.

Bug: dawn:1301
Change-Id: Iba08d04a0bc23c278e65618550ea314ca0cbee1c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/114363
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Stephen White <senorblanco@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index f57a2d7..f3fd25b 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -535,6 +535,8 @@
     "transform/std140.h",
     "transform/substitute_override.cc",
     "transform/substitute_override.h",
+    "transform/texture_1d_to_2d.cc",
+    "transform/texture_1d_to_2d.h",
     "transform/transform.cc",
     "transform/transform.h",
     "transform/truncate_interstage_variables.cc",
@@ -1328,6 +1330,7 @@
       "transform/std140_test.cc",
       "transform/substitute_override_test.cc",
       "transform/test_helper.h",
+      "transform/texture_1d_to_2d_test.cc",
       "transform/transform_test.cc",
       "transform/truncate_interstage_variables_test.cc",
       "transform/unshadow_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index f35aa57..2f7bda7 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -441,6 +441,8 @@
   transform/std140.h
   transform/substitute_override.cc
   transform/substitute_override.h
+  transform/texture_1d_to_2d.cc
+  transform/texture_1d_to_2d.h
   transform/transform.cc
   transform/transform.h
   transform/truncate_interstage_variables.cc
@@ -1261,6 +1263,7 @@
       transform/std140_test.cc
       transform/substitute_override_test.cc
       transform/test_helper.h
+      transform/texture_1d_to_2d_test.cc
       transform/truncate_interstage_variables_test.cc
       transform/unshadow_test.cc
       transform/var_for_dynamic_index_test.cc
diff --git a/src/tint/transform/texture_1d_to_2d.cc b/src/tint/transform/texture_1d_to_2d.cc
new file mode 100644
index 0000000..0e19d02
--- /dev/null
+++ b/src/tint/transform/texture_1d_to_2d.cc
@@ -0,0 +1,186 @@
+// Copyright 2022 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 "src/tint/transform/texture_1d_to_2d.h"
+
+#include <utility>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/statement.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::Texture1DTo2D);
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::transform {
+
+namespace {
+
+bool ShouldRun(const Program* program) {
+    for (auto* fn : program->AST().Functions()) {
+        if (auto* sem_fn = program->Sem().Get(fn)) {
+            for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
+                const auto& signature = builtin->Signature();
+                auto texture = signature.Parameter(sem::ParameterUsage::kTexture);
+                if (texture) {
+                    auto* tex = texture->Type()->As<type::Texture>();
+                    if (tex->dim() == ast::TextureDimension::k1d) {
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+    for (auto* var : program->AST().GlobalVariables()) {
+        if (Switch(
+                program->Sem().Get(var->type),
+                [&](const type::SampledTexture* tex) {
+                    return tex->dim() == ast::TextureDimension::k1d;
+                },
+                [&](const type::StorageTexture* storage_tex) {
+                    return storage_tex->dim() == ast::TextureDimension::k1d;
+                })) {
+            return true;
+        }
+    }
+    return false;
+}
+
+}  // namespace
+
+/// PIMPL state for the transform
+struct Texture1DTo2D::State {
+    /// The source program
+    const Program* const src;
+    /// The target program builder
+    ProgramBuilder b;
+    /// The clone context
+    CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+
+    /// Constructor
+    /// @param program the source program
+    explicit State(const Program* program) : src(program) {}
+
+    /// Runs the transform
+    /// @returns the new program or SkipTransform if the transform is not required
+    ApplyResult Run() {
+        auto& sem = src->Sem();
+
+        if (!ShouldRun(ctx.src)) {
+            return SkipTransform;
+        }
+
+        auto create_var = [&](const ast::Variable* v, ast::Type* type) -> const ast::Variable* {
+            if (v->As<ast::Parameter>()) {
+                return ctx.dst->Param(ctx.Clone(v->symbol), type, ctx.Clone(v->attributes));
+            } else {
+                return ctx.dst->Var(ctx.Clone(v->symbol), type, ctx.Clone(v->attributes));
+            }
+        };
+
+        ctx.ReplaceAll([&](const ast::Variable* v) -> const ast::Variable* {
+            const ast::Variable* r = Switch(
+                sem.Get(v->type),
+                [&](const type::SampledTexture* tex) -> const ast::Variable* {
+                    if (tex->dim() == ast::TextureDimension::k1d) {
+                        auto* type = ctx.dst->create<ast::SampledTexture>(
+                            ast::TextureDimension::k2d, CreateASTTypeFor(ctx, tex->type()));
+                        return create_var(v, type);
+                    } else {
+                        return nullptr;
+                    }
+                },
+                [&](const type::StorageTexture* storage_tex) -> const ast::Variable* {
+                    if (storage_tex->dim() == ast::TextureDimension::k1d) {
+                        auto* type = ctx.dst->create<ast::StorageTexture>(
+                            ast::TextureDimension::k2d, storage_tex->texel_format(),
+                            CreateASTTypeFor(ctx, storage_tex->type()), storage_tex->access());
+                        return create_var(v, type);
+                    } else {
+                        return nullptr;
+                    }
+                },
+                [](Default) { return nullptr; });
+            return r;
+        });
+
+        ctx.ReplaceAll([&](const ast::CallExpression* c) -> const ast::Expression* {
+            auto* call = sem.Get(c)->UnwrapMaterialize()->As<sem::Call>();
+            if (!call) {
+                return nullptr;
+            }
+            auto* builtin = call->Target()->As<sem::Builtin>();
+            if (!builtin) {
+                return nullptr;
+            }
+            const auto& signature = builtin->Signature();
+            auto texture = signature.Parameter(sem::ParameterUsage::kTexture);
+            auto* tex = texture->Type()->As<type::Texture>();
+            if (tex->dim() != ast::TextureDimension::k1d) {
+                return nullptr;
+            }
+
+            if (builtin->Type() == sem::BuiltinType::kTextureDimensions) {
+                // If this textureDimensions() call is in a CallStatement, we can leave it
+                // unmodified since the return value will be dropped on the floor anyway.
+                if (call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
+                    return nullptr;
+                }
+                auto* new_call = ctx.CloneWithoutTransform(c);
+                return ctx.dst->MemberAccessor(new_call, "x");
+            }
+
+            auto coords_index = signature.IndexOf(sem::ParameterUsage::kCoords);
+            if (coords_index == -1) {
+                return nullptr;
+            }
+
+            utils::Vector<const ast::Expression*, 8> args;
+            int index = 0;
+            for (auto* arg : c->args) {
+                if (index == coords_index) {
+                    auto* ctype = call->Arguments()[static_cast<size_t>(coords_index)]->Type();
+                    auto* coords = c->args[static_cast<size_t>(coords_index)];
+
+                    const ast::LiteralExpression* half = nullptr;
+                    if (ctype->is_integer_scalar()) {
+                        half = ctx.dst->Expr(0_a);
+                    } else {
+                        half = ctx.dst->Expr(0.5_a);
+                    }
+                    args.Push(
+                        ctx.dst->vec(CreateASTTypeFor(ctx, ctype), 2u, ctx.Clone(coords), half));
+                } else {
+                    args.Push(ctx.Clone(arg));
+                }
+                index++;
+            }
+            return ctx.dst->Call(ctx.Clone(c->target.name), args);
+        });
+
+        ctx.Clone();
+        return Program(std::move(b));
+    }
+};
+
+Texture1DTo2D::Texture1DTo2D() = default;
+
+Texture1DTo2D::~Texture1DTo2D() = default;
+
+Transform::ApplyResult Texture1DTo2D::Apply(const Program* src, const DataMap&, DataMap&) const {
+    return State(src).Run();
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/texture_1d_to_2d.h b/src/tint/transform/texture_1d_to_2d.h
new file mode 100644
index 0000000..9999821
--- /dev/null
+++ b/src/tint/transform/texture_1d_to_2d.h
@@ -0,0 +1,43 @@
+// Copyright 2022 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.
+
+#ifndef SRC_TINT_TRANSFORM_TEXTURE_1D_TO_2D_H_
+#define SRC_TINT_TRANSFORM_TEXTURE_1D_TO_2D_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// This transform converts all 1D texture types and accesses to 2D.
+/// This is required for GLSL ES, which does not support 1D textures.
+class Texture1DTo2D final : public Castable<Texture1DTo2D, Transform> {
+  public:
+    /// Constructor
+    Texture1DTo2D();
+
+    /// Destructor
+    ~Texture1DTo2D() override;
+
+    /// @copydoc Transform::Apply
+    ApplyResult Apply(const Program* program,
+                      const DataMap& inputs,
+                      DataMap& outputs) const override;
+
+  private:
+    struct State;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_TEXTURE_1D_TO_2D_H_
diff --git a/src/tint/transform/texture_1d_to_2d_test.cc b/src/tint/transform/texture_1d_to_2d_test.cc
new file mode 100644
index 0000000..83a12bd
--- /dev/null
+++ b/src/tint/transform/texture_1d_to_2d_test.cc
@@ -0,0 +1,279 @@
+// Copyright 2022 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 "src/tint/transform/texture_1d_to_2d.h"
+
+// #include <memory>
+// #include <utility>
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint::transform {
+namespace {
+
+using Texture1DTo2DTest = TransformTest;
+
+TEST_F(Texture1DTo2DTest, EmptyModule) {
+    auto* src = "";
+
+    DataMap data;
+    EXPECT_FALSE(ShouldRun<Texture1DTo2D>(src, data));
+}
+
+TEST_F(Texture1DTo2DTest, Global1DDecl) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, Global1DDeclAndSample) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t, s, 0.5);
+}
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(0.5, 0.5));
+}
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, Global1DDeclAndLoad) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+fn main() -> vec4<f32> {
+  return textureLoad(t, 1, 0);
+}
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn main() -> vec4<f32> {
+  return textureLoad(t, vec2<i32>(1, 0), 0);
+}
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensions) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+fn main() -> u32 {
+  return textureDimensions(t);
+}
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn main() -> u32 {
+  return textureDimensions(t).x;
+}
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureNumLevels) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+fn main() -> u32 {
+  return textureNumLevels(t);
+}
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn main() -> u32 {
+  return textureNumLevels(t);
+}
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensionsInCallStmt) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+fn main() {
+  textureDimensions(t);
+}
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn main() {
+  textureDimensions(t);
+}
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, GlobalStorage1DDecl) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_storage_1d<r32float, write>;
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var t : texture_storage_2d<r32float, write>;
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, Global2DDeclAndSample) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn main() -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(0.5, 1.5));
+}
+)";
+
+    DataMap data;
+    EXPECT_FALSE(ShouldRun<Texture1DTo2D>(src, data));
+}
+
+TEST_F(Texture1DTo2DTest, PrivateIntNoop) {
+    auto* src = R"(
+var<private> i : i32;
+)";
+
+    DataMap data;
+    EXPECT_FALSE(ShouldRun<Texture1DTo2D>(src, data));
+}
+
+TEST_F(Texture1DTo2DTest, GlobalMatrixNoop) {
+    auto* src = R"(
+@group(0) @binding(0) var<uniform> m : mat2x2<f32>;
+)";
+
+    DataMap data;
+    EXPECT_FALSE(ShouldRun<Texture1DTo2D>(src, data));
+}
+
+TEST_F(Texture1DTo2DTest, Texture1DFuncParam) {
+    auto* src = R"(
+@group(0) @binding(0) var tex : texture_1d<f32>;
+
+@group(0) @binding(1) var samp : sampler;
+
+fn f(t : texture_1d<f32>, s : sampler) -> vec4<f32> {
+  return textureSample(t, s, 0.7);
+}
+
+fn main() -> vec4<f32> {
+  return f(tex, samp);
+}
+)";
+    auto* expect = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+@group(0) @binding(1) var samp : sampler;
+
+fn f(t : texture_2d<f32>, s : sampler) -> vec4<f32> {
+  return textureSample(t, s, vec2<f32>(0.7, 0.5));
+}
+
+fn main() -> vec4<f32> {
+  return f(tex, samp);
+}
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Texture1DTo2DTest, TextureStorage1DFuncParam) {
+    auto* src = R"(
+@group(0) @binding(0) var tex : texture_storage_1d<rgba8unorm, write>;
+
+fn f(t : texture_storage_1d<rgba8unorm, write>) {
+  textureStore(t, 3, vec4<f32>(42.0, 21.0, 84.0, 10.5));
+}
+
+fn main() {
+  f(tex);
+}
+)";
+
+    auto* expect = R"(
+@group(0) @binding(0) var tex : texture_storage_2d<rgba8unorm, write>;
+
+fn f(t : texture_storage_2d<rgba8unorm, write>) {
+  textureStore(t, vec2<i32>(3, 0), vec4<f32>(42.0, 21.0, 84.0, 10.5));
+}
+
+fn main() {
+  f(tex);
+}
+)";
+
+    DataMap data;
+    auto got = Run<Texture1DTo2D>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace tint::transform