diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn index f57a2d73f1..f3fd25bd9d 100644 --- a/src/tint/BUILD.gn +++ b/src/tint/BUILD.gn @@ -535,6 +535,8 @@ libtint_source_set("libtint_core_all_src") { "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 @@ if (tint_build_unittests) { "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 f35aa5734f..2f7bda7912 100644 --- a/src/tint/CMakeLists.txt +++ b/src/tint/CMakeLists.txt @@ -441,6 +441,8 @@ list(APPEND TINT_LIB_SRCS 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 @@ if(TINT_BUILD_TESTS) 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 0000000000..0e19d02601 --- /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 + +#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(); + 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()) { + 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::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::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(); + if (!call) { + return nullptr; + } + auto* builtin = call->Target()->As(); + if (!builtin) { + return nullptr; + } + const auto& signature = builtin->Signature(); + auto texture = signature.Parameter(sem::ParameterUsage::kTexture); + auto* tex = texture->Type()->As(); + 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()) { + 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 args; + int index = 0; + for (auto* arg : c->args) { + if (index == coords_index) { + auto* ctype = call->Arguments()[static_cast(coords_index)]->Type(); + auto* coords = c->args[static_cast(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 0000000000..999982143b --- /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 { + 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 0000000000..83a12bd3a1 --- /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 +// #include + +#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(src, data)); +} + +TEST_F(Texture1DTo2DTest, Global1DDecl) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +@group(0) @binding(1) var s : sampler; +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndSample) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +@group(0) @binding(1) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, 0.5); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, vec2(0.5, 0.5)); +} +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndLoad) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +fn main() -> vec4 { + return textureLoad(t, 1, 0); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +fn main() -> vec4 { + return textureLoad(t, vec2(1, 0), 0); +} +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensions) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +fn main() -> u32 { + return textureDimensions(t); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +fn main() -> u32 { + return textureDimensions(t).x; +} +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureNumLevels) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +fn main() -> u32 { + return textureNumLevels(t); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +fn main() -> u32 { + return textureNumLevels(t); +} +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global1DDeclAndTextureDimensionsInCallStmt) { + auto* src = R"( +@group(0) @binding(0) var t : texture_1d; + +fn main() { + textureDimensions(t); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_2d; + +fn main() { + textureDimensions(t); +} +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, GlobalStorage1DDecl) { + auto* src = R"( +@group(0) @binding(0) var t : texture_storage_1d; +)"; + auto* expect = R"( +@group(0) @binding(0) var t : texture_storage_2d; +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, Global2DDeclAndSample) { + auto* src = R"( +@group(0) @binding(0) var t : texture_2d; + +@group(0) @binding(1) var s : sampler; + +fn main() -> vec4 { + return textureSample(t, s, vec2(0.5, 1.5)); +} +)"; + + DataMap data; + EXPECT_FALSE(ShouldRun(src, data)); +} + +TEST_F(Texture1DTo2DTest, PrivateIntNoop) { + auto* src = R"( +var i : i32; +)"; + + DataMap data; + EXPECT_FALSE(ShouldRun(src, data)); +} + +TEST_F(Texture1DTo2DTest, GlobalMatrixNoop) { + auto* src = R"( +@group(0) @binding(0) var m : mat2x2; +)"; + + DataMap data; + EXPECT_FALSE(ShouldRun(src, data)); +} + +TEST_F(Texture1DTo2DTest, Texture1DFuncParam) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_1d; + +@group(0) @binding(1) var samp : sampler; + +fn f(t : texture_1d, s : sampler) -> vec4 { + return textureSample(t, s, 0.7); +} + +fn main() -> vec4 { + return f(tex, samp); +} +)"; + auto* expect = R"( +@group(0) @binding(0) var tex : texture_2d; + +@group(0) @binding(1) var samp : sampler; + +fn f(t : texture_2d, s : sampler) -> vec4 { + return textureSample(t, s, vec2(0.7, 0.5)); +} + +fn main() -> vec4 { + return f(tex, samp); +} +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(Texture1DTo2DTest, TextureStorage1DFuncParam) { + auto* src = R"( +@group(0) @binding(0) var tex : texture_storage_1d; + +fn f(t : texture_storage_1d) { + textureStore(t, 3, vec4(42.0, 21.0, 84.0, 10.5)); +} + +fn main() { + f(tex); +} +)"; + + auto* expect = R"( +@group(0) @binding(0) var tex : texture_storage_2d; + +fn f(t : texture_storage_2d) { + textureStore(t, vec2(3, 0), vec4(42.0, 21.0, 84.0, 10.5)); +} + +fn main() { + f(tex); +} +)"; + + DataMap data; + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace tint::transform