diff --git a/src/BUILD.gn b/src/BUILD.gn index 3139e7990d..1af8434ab6 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -401,6 +401,8 @@ source_set("libtint_core_src") { "transform/binding_remapper.h", "transform/bound_array_accessors.cc", "transform/bound_array_accessors.h", + "transform/canonicalize_entry_point_io.cc", + "transform/canonicalize_entry_point_io.h", "transform/emit_vertex_point_size.cc", "transform/emit_vertex_point_size.h", "transform/first_index_offset.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8a27981ac1..e7c512df05 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,6 +216,8 @@ set(TINT_LIB_SRCS transform/binding_remapper.h transform/bound_array_accessors.cc transform/bound_array_accessors.h + transform/canonicalize_entry_point_io.cc + transform/canonicalize_entry_point_io.h transform/emit_vertex_point_size.cc transform/emit_vertex_point_size.h transform/first_index_offset.cc @@ -731,6 +733,7 @@ if(${TINT_BUILD_TESTS}) list(APPEND TINT_TEST_SRCS transform/binding_remapper_test.cc transform/bound_array_accessors_test.cc + transform/canonicalize_entry_point_io_test.cc transform/emit_vertex_point_size_test.cc transform/first_index_offset_test.cc transform/renamer_test.cc diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc new file mode 100644 index 0000000000..ca4af82b97 --- /dev/null +++ b/src/transform/canonicalize_entry_point_io.cc @@ -0,0 +1,227 @@ +// Copyright 2021 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/transform/canonicalize_entry_point_io.h" + +#include + +#include "src/program_builder.h" +#include "src/semantic/function.h" +#include "src/semantic/variable.h" + +namespace tint { +namespace transform { + +CanonicalizeEntryPointIO::CanonicalizeEntryPointIO() = default; +CanonicalizeEntryPointIO::~CanonicalizeEntryPointIO() = default; + +Transform::Output CanonicalizeEntryPointIO::Run(const Program* in, + const DataMap&) { + ProgramBuilder out; + CloneContext ctx(&out, in); + + // Strip entry point IO decorations from struct declarations. + // TODO(jrprice): This code is duplicated with the SPIR-V transform. + for (auto* ty : ctx.src->AST().ConstructedTypes()) { + if (auto* struct_ty = ty->As()) { + // Build new list of struct members without entry point IO decorations. + ast::StructMemberList new_struct_members; + for (auto* member : struct_ty->impl()->members()) { + ast::DecorationList new_decorations = RemoveDecorations( + &ctx, member->decorations(), [](const ast::Decoration* deco) { + return deco + ->IsAnyOf(); + }); + new_struct_members.push_back( + ctx.dst->Member(ctx.src->Symbols().NameFor(member->symbol()), + ctx.Clone(member->type()), new_decorations)); + } + + // Redeclare the struct. + auto* new_struct = ctx.dst->create( + ctx.Clone(struct_ty->symbol()), + ctx.dst->create( + new_struct_members, ctx.Clone(struct_ty->impl()->decorations()))); + ctx.Replace(struct_ty, new_struct); + } + } + + for (auto* func : ctx.src->AST().Functions()) { + if (!func->IsEntryPoint()) { + continue; + } + + ast::VariableList new_parameters; + + if (!func->params().empty()) { + // Collect all parameters and build a list of new struct members. + auto new_struct_param_symbol = ctx.dst->Symbols().New(); + ast::StructMemberList new_struct_members; + for (auto* param : func->params()) { + auto param_name = ctx.src->Symbols().NameFor(param->symbol()); + auto* param_ty = ctx.src->Sem().Get(param)->Type(); + + auto func_const_symbol = ctx.dst->Symbols().Register(param_name); + ast::Expression* func_const_initializer = nullptr; + + if (auto* struct_ty = + param_ty->UnwrapAliasIfNeeded()->As()) { + // Pull out all struct members and build initializer list. + ast::ExpressionList init_values; + for (auto* member : struct_ty->impl()->members()) { + if (member->type()->UnwrapAll()->Is()) { + TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct"; + } + + ast::DecorationList new_decorations = RemoveDecorations( + &ctx, member->decorations(), [](const ast::Decoration* deco) { + return !deco->IsAnyOf(); + }); + auto member_name = ctx.src->Symbols().NameFor(member->symbol()); + new_struct_members.push_back(ctx.dst->Member( + member_name, ctx.Clone(member->type()), new_decorations)); + init_values.push_back( + ctx.dst->MemberAccessor(new_struct_param_symbol, member_name)); + } + + func_const_initializer = + ctx.dst->Construct(ctx.Clone(param_ty), init_values); + } else { + ast::DecorationList new_decorations = RemoveDecorations( + &ctx, param->decorations(), [](const ast::Decoration* deco) { + return !deco->IsAnyOf(); + }); + new_struct_members.push_back(ctx.dst->Member( + param_name, ctx.Clone(param_ty), new_decorations)); + func_const_initializer = + ctx.dst->MemberAccessor(new_struct_param_symbol, param_name); + } + + if (func->body()->empty()) { + // Don't generate a function-scope const if the function is empty. + continue; + } + + // Create a function-scope const to replace the parameter. + // Initialize it with the value extracted from the new struct parameter. + auto* func_const = ctx.dst->Const( + func_const_symbol, ctx.Clone(param_ty), func_const_initializer); + ctx.InsertBefore(*func->body()->begin(), + ctx.dst->WrapInStatement(func_const)); + + // Replace all uses of the function parameter with the function const. + for (auto* user : ctx.src->Sem().Get(param)->Users()) { + ctx.Replace(user->Declaration(), + ctx.dst->Expr(func_const_symbol)); + } + } + + // Create the new struct type. + auto* in_struct = ctx.dst->create( + ctx.dst->Symbols().New(), + ctx.dst->create(new_struct_members, + ast::DecorationList{})); + ctx.InsertBefore(func, in_struct); + + // Create a new function parameter using this struct type. + auto* struct_param = ctx.dst->Var(new_struct_param_symbol, in_struct, + ast::StorageClass::kNone); + new_parameters.push_back(struct_param); + } + + // Handle return type. + auto* ret_type = func->return_type()->UnwrapAliasIfNeeded(); + type::Type* new_ret_type; + if (ret_type->Is()) { + new_ret_type = ctx.dst->ty.void_(); + } else { + ast::StructMemberList new_struct_members; + + if (auto* struct_ty = ret_type->As()) { + // Rebuild struct with only the entry point IO attributes. + for (auto* member : struct_ty->impl()->members()) { + if (member->type()->UnwrapAll()->Is()) { + TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct"; + } + + ast::DecorationList new_decorations = RemoveDecorations( + &ctx, member->decorations(), [](const ast::Decoration* deco) { + return !deco->IsAnyOf(); + }); + auto member_name = ctx.src->Symbols().NameFor(member->symbol()); + new_struct_members.push_back(ctx.dst->Member( + member_name, ctx.Clone(member->type()), new_decorations)); + } + } else { + new_struct_members.push_back( + ctx.dst->Member("value", ctx.Clone(ret_type), + ctx.Clone(func->return_type_decorations()))); + } + + // Create the new struct type. + auto* out_struct = ctx.dst->create( + ctx.dst->Symbols().New(), + ctx.dst->create(new_struct_members, + ast::DecorationList{})); + ctx.InsertBefore(func, out_struct); + new_ret_type = out_struct; + + // Replace all return statements. + auto* sem_func = ctx.src->Sem().Get(func); + for (auto* ret : sem_func->ReturnStatements()) { + // Reconstruct the return value using the newly created struct. + auto* new_ret_value = ctx.Clone(ret->value()); + ast::ExpressionList ret_values; + if (auto* struct_ty = ret_type->As()) { + if (!ret->value()->Is()) { + // Create a const to hold the return value expression to avoid + // re-evaluating it multiple times. + auto temp = ctx.dst->Symbols().New(); + auto* temp_var = ctx.dst->Decl( + ctx.dst->Const(temp, ctx.Clone(ret_type), new_ret_value)); + ctx.InsertBefore(ret, temp_var); + new_ret_value = ctx.dst->Expr(temp); + } + + for (auto* member : struct_ty->impl()->members()) { + ret_values.push_back(ctx.dst->MemberAccessor( + new_ret_value, ctx.Clone(member->symbol()))); + } + } else { + ret_values.push_back(new_ret_value); + } + + auto* new_ret = ctx.dst->create( + ctx.dst->Construct(new_ret_type, ret_values)); + ctx.Replace(ret, new_ret); + } + } + + // Rewrite the function header with the new parameters. + auto* new_func = ctx.dst->create( + func->source(), ctx.Clone(func->symbol()), new_parameters, new_ret_type, + ctx.Clone(func->body()), ctx.Clone(func->decorations()), + ast::DecorationList{}); + ctx.Replace(func, new_func); + } + + ctx.Clone(); + return Output(Program(std::move(out))); +} + +} // namespace transform +} // namespace tint diff --git a/src/transform/canonicalize_entry_point_io.h b/src/transform/canonicalize_entry_point_io.h new file mode 100644 index 0000000000..479cd0c281 --- /dev/null +++ b/src/transform/canonicalize_entry_point_io.h @@ -0,0 +1,87 @@ +// Copyright 2021 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_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_ +#define SRC_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_ + +#include "src/transform/transform.h" + +namespace tint { +namespace transform { + +/// CanonicalizeEntryPointIO is a transform used to rewrite shader entry point +/// interfaces into a form that the generators can handle. After the transform, +/// an entry point's parameters will be aggregated into a single struct, and its +/// return type will either be a struct or void. All structs in the module that +/// have entry point IO decorations will have exactly one pipeline stage usage. +/// +/// Before: +/// ``` +/// struct Locations{ +/// [[location(1)]] loc1 : f32; +/// [[location(2)]] loc2 : vec4; +/// }; +/// +/// [[stage(fragment)]] +/// fn frag_main([[builtin(frag_coord)]] coord : vec4, +/// locations : Locations) -> [[location(0)]] f32 { +/// var col : f32 = (coord.x * locations.loc1); +/// return col; +/// } +/// ``` +/// +/// After: +/// ``` +/// struct Locations{ +/// loc1 : f32; +/// loc2 : vec4; +/// }; +/// +/// struct frag_main_in { +/// [[builtin(frag_coord)]] coord : vec4; +/// [[location(1)]] loc1 : f32; +/// [[location(2)]] loc2 : vec4 +/// }; +/// +/// struct frag_main_out { +/// [[location(0)]] loc0 : f32; +/// }; +/// +/// [[stage(fragment)]] +/// fn frag_main(in : frag_main_in) -> frag_main_out { +/// const coord = in.coord; +/// const locations = Locations(in.loc1, in.loc2); +/// var col : f32 = (coord.x * locations.loc1); +/// var retval : frag_main_out; +/// retval.loc0 = col; +/// return retval; +/// } +/// ``` +class CanonicalizeEntryPointIO : public Transform { + public: + /// Constructor + CanonicalizeEntryPointIO(); + ~CanonicalizeEntryPointIO() override; + + /// Runs the transform on `program`, returning the transformation result. + /// @param program the source program to transform + /// @param data optional extra transform-specific input data + /// @returns the transformation result + Output Run(const Program* program, const DataMap& data = {}) override; +}; + +} // namespace transform +} // namespace tint + +#endif // SRC_TRANSFORM_CANONICALIZE_ENTRY_POINT_IO_H_ diff --git a/src/transform/canonicalize_entry_point_io_test.cc b/src/transform/canonicalize_entry_point_io_test.cc new file mode 100644 index 0000000000..0f50f2d969 --- /dev/null +++ b/src/transform/canonicalize_entry_point_io_test.cc @@ -0,0 +1,508 @@ +// Copyright 2021 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/transform/canonicalize_entry_point_io.h" + +#include "src/transform/test_helper.h" + +namespace tint { +namespace transform { +namespace { + +using CanonicalizeEntryPointIOTest = TransformTest; + +TEST_F(CanonicalizeEntryPointIOTest, Parameters) { + auto* src = R"( +[[stage(fragment)]] +fn frag_main([[builtin(frag_coord)]] coord : vec4, + [[location(1)]] loc1 : f32, + [[location(2)]] loc2 : vec4) -> void { + var col : f32 = (coord.x * loc1); +} +)"; + + auto* expect = R"( +struct tint_symbol_5 { + [[builtin(frag_coord)]] + coord : vec4; + [[location(1)]] + loc1 : f32; + [[location(2)]] + loc2 : vec4; +}; + +[[stage(fragment)]] +fn frag_main(tint_symbol_1 : tint_symbol_5) -> void { + const coord : vec4 = tint_symbol_1.coord; + const loc1 : f32 = tint_symbol_1.loc1; + const loc2 : vec4 = tint_symbol_1.loc2; + var col : f32 = (coord.x * loc1); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, Parameter_TypeAlias) { + auto* src = R"( +type myf32 = f32; + +[[stage(fragment)]] +fn frag_main([[location(1)]] loc1 : myf32) -> void { + var x : myf32 = loc1; +} +)"; + + auto* expect = R"( +type myf32 = f32; + +struct tint_symbol_4 { + [[location(1)]] + loc1 : myf32; +}; + +[[stage(fragment)]] +fn frag_main(tint_symbol_1 : tint_symbol_4) -> void { + const loc1 : myf32 = tint_symbol_1.loc1; + var x : myf32 = loc1; +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, Parameters_EmptyBody) { + auto* src = R"( +[[stage(fragment)]] +fn frag_main([[builtin(frag_coord)]] coord : vec4, + [[location(1)]] loc1 : f32, + [[location(2)]] loc2 : vec4) -> void { +} +)"; + + auto* expect = R"( +struct tint_symbol_5 { + [[builtin(frag_coord)]] + coord : vec4; + [[location(1)]] + loc1 : f32; + [[location(2)]] + loc2 : vec4; +}; + +[[stage(fragment)]] +fn frag_main(tint_symbol_1 : tint_symbol_5) -> void { +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, StructParameters) { + auto* src = R"( +struct FragBuiltins { + [[builtin(frag_coord)]] coord : vec4; +}; +struct FragLocations { + [[location(1)]] loc1 : f32; + [[location(2)]] loc2 : vec4; +}; + +[[stage(fragment)]] +fn frag_main(builtins : FragBuiltins, + locations : FragLocations, + [[location(0)]] loc0 : f32) -> void { + var col : f32 = ((builtins.coord.x * locations.loc1) + loc0); +} +)"; + + auto* expect = R"( +struct FragBuiltins { + coord : vec4; +}; + +struct FragLocations { + loc1 : f32; + loc2 : vec4; +}; + +struct tint_symbol_10 { + [[builtin(frag_coord)]] + coord : vec4; + [[location(1)]] + loc1 : f32; + [[location(2)]] + loc2 : vec4; + [[location(0)]] + loc0 : f32; +}; + +[[stage(fragment)]] +fn frag_main(tint_symbol_6 : tint_symbol_10) -> void { + const builtins : FragBuiltins = FragBuiltins(tint_symbol_6.coord); + const locations : FragLocations = FragLocations(tint_symbol_6.loc1, tint_symbol_6.loc2); + const loc0 : f32 = tint_symbol_6.loc0; + var col : f32 = ((builtins.coord.x * locations.loc1) + loc0); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, Return_Scalar) { + auto* src = R"( +[[stage(fragment)]] +fn frag_main() -> [[builtin(frag_depth)]] f32 { + return 1.0; +} +)"; + + auto* expect = R"( +struct tint_symbol_2 { + [[builtin(frag_depth)]] + value : f32; +}; + +[[stage(fragment)]] +fn frag_main() -> tint_symbol_2 { + return tint_symbol_2(1.0); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, Return_Struct) { + auto* src = R"( +struct FragOutput { + [[builtin(frag_depth)]] depth : f32; + [[builtin(sample_mask_out)]] mask : u32; + [[location(0)]] color : vec4; +}; + +[[stage(fragment)]] +fn frag_main() -> FragOutput { + var output : FragOutput; + output.depth = 1.0; + output.mask = 7u; + output.color = vec4(0.5, 0.5, 0.5, 1.0); + return output; +} +)"; + + auto* expect = R"( +struct FragOutput { + depth : f32; + mask : u32; + color : vec4; +}; + +struct tint_symbol_5 { + [[builtin(frag_depth)]] + depth : f32; + [[builtin(sample_mask_out)]] + mask : u32; + [[location(0)]] + color : vec4; +}; + +[[stage(fragment)]] +fn frag_main() -> tint_symbol_5 { + var output : FragOutput; + output.depth = 1.0; + output.mask = 7u; + output.color = vec4(0.5, 0.5, 0.5, 1.0); + return tint_symbol_5(output.depth, output.mask, output.color); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, StructParameters_SharedDeviceFunction) { + auto* src = R"( +struct FragmentInput { + [[location(0)]] value : f32; + [[location(1)]] mul : f32; +}; + +fn foo(x : FragmentInput) -> f32 { + return x.value * x.mul; +} + +[[stage(fragment)]] +fn frag_main1(inputs : FragmentInput) -> void { + var x : f32 = foo(inputs); +} + +[[stage(fragment)]] +fn frag_main2(inputs : FragmentInput) -> void { + var x : f32 = foo(inputs); +} +)"; + + auto* expect = R"( +struct FragmentInput { + value : f32; + mul : f32; +}; + +fn foo(x : FragmentInput) -> f32 { + return (x.value * x.mul); +} + +struct tint_symbol_6 { + [[location(0)]] + value : f32; + [[location(1)]] + mul : f32; +}; + +[[stage(fragment)]] +fn frag_main1(tint_symbol_4 : tint_symbol_6) -> void { + const inputs : FragmentInput = FragmentInput(tint_symbol_4.value, tint_symbol_4.mul); + var x : f32 = foo(inputs); +} + +struct tint_symbol_11 { + [[location(0)]] + value : f32; + [[location(1)]] + mul : f32; +}; + +[[stage(fragment)]] +fn frag_main2(tint_symbol_10 : tint_symbol_11) -> void { + const inputs : FragmentInput = FragmentInput(tint_symbol_10.value, tint_symbol_10.mul); + var x : f32 = foo(inputs); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, Struct_ModuleScopeVariable) { + auto* src = R"( +struct FragmentInput { + [[location(0)]] col1 : f32; + [[location(1)]] col2 : f32; +}; + +var global_inputs : FragmentInput; + +fn foo() -> f32 { + return global_inputs.col1 * 0.5; +} + +fn bar() -> f32 { + return global_inputs.col2 * 2.0; +} + +[[stage(fragment)]] +fn frag_main1(inputs : FragmentInput) -> void { + global_inputs = inputs; + var r : f32 = foo(); + var g : f32 = bar(); +} +)"; + + auto* expect = R"( +struct FragmentInput { + col1 : f32; + col2 : f32; +}; + +var global_inputs : FragmentInput; + +fn foo() -> f32 { + return (global_inputs.col1 * 0.5); +} + +fn bar() -> f32 { + return (global_inputs.col2 * 2.0); +} + +struct tint_symbol_6 { + [[location(0)]] + col1 : f32; + [[location(1)]] + col2 : f32; +}; + +[[stage(fragment)]] +fn frag_main1(tint_symbol_4 : tint_symbol_6) -> void { + const inputs : FragmentInput = FragmentInput(tint_symbol_4.col1, tint_symbol_4.col2); + global_inputs = inputs; + var r : f32 = foo(); + var g : f32 = bar(); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, Struct_TypeAliases) { + auto* src = R"( +type myf32 = f32; + +struct FragmentInput { + [[location(0)]] col1 : myf32; + [[location(1)]] col2 : myf32; +}; + +struct FragmentOutput { + [[location(0)]] col1 : myf32; + [[location(1)]] col2 : myf32; +}; + +type MyFragmentInput = FragmentInput; + +type MyFragmentOutput = FragmentOutput; + +fn foo(x : MyFragmentInput) -> myf32 { + return x.col1; +} + +[[stage(fragment)]] +fn frag_main(inputs : MyFragmentInput) -> MyFragmentOutput { + var x : myf32 = foo(inputs); + return MyFragmentOutput(x, inputs.col2); +} +)"; + + auto* expect = R"( +type myf32 = f32; + +struct FragmentInput { + col1 : myf32; + col2 : myf32; +}; + +struct FragmentOutput { + col1 : myf32; + col2 : myf32; +}; + +type MyFragmentInput = FragmentInput; + +type MyFragmentOutput = FragmentOutput; + +fn foo(x : MyFragmentInput) -> myf32 { + return x.col1; +} + +struct tint_symbol_9 { + [[location(0)]] + col1 : myf32; + [[location(1)]] + col2 : myf32; +}; + +struct tint_symbol_10 { + [[location(0)]] + col1 : myf32; + [[location(1)]] + col2 : myf32; +}; + +[[stage(fragment)]] +fn frag_main(tint_symbol_6 : tint_symbol_9) -> tint_symbol_10 { + const inputs : MyFragmentInput = MyFragmentInput(tint_symbol_6.col1, tint_symbol_6.col2); + var x : myf32 = foo(inputs); + const tint_symbol_13 : FragmentOutput = MyFragmentOutput(x, inputs.col2); + return tint_symbol_10(tint_symbol_13.col1, tint_symbol_13.col2); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(CanonicalizeEntryPointIOTest, Struct_LayoutDecorations) { + auto* src = R"( +[[block]] +struct FragmentInput { + [[size(16), location(1)]] value : f32; + [[builtin(frag_coord)]] [[align(32)]] coord : vec4; +}; + +struct FragmentOutput { + [[size(16), location(1)]] value : f32; +}; + +[[stage(fragment)]] +fn frag_main(inputs : FragmentInput) -> FragmentOutput { + return FragmentOutput(inputs.coord.x * inputs.value); +} +)"; + + auto* expect = R"( +[[block]] +struct FragmentInput { + [[size(16)]] + value : f32; + [[align(32)]] + coord : vec4; +}; + +struct FragmentOutput { + [[size(16)]] + value : f32; +}; + +struct tint_symbol_7 { + [[location(1)]] + value : f32; + [[builtin(frag_coord)]] + coord : vec4; +}; + +struct tint_symbol_8 { + [[location(1)]] + value : f32; +}; + +[[stage(fragment)]] +fn frag_main(tint_symbol_5 : tint_symbol_7) -> tint_symbol_8 { + const inputs : FragmentInput = FragmentInput(tint_symbol_5.value, tint_symbol_5.coord); + const tint_symbol_10 : FragmentOutput = FragmentOutput((inputs.coord.x * inputs.value)); + return tint_symbol_8(tint_symbol_10.value); +} +)"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace transform +} // namespace tint diff --git a/test/BUILD.gn b/test/BUILD.gn index a806af6025..c981ebf7f9 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -193,6 +193,7 @@ source_set("tint_unittests_core_src") { "../src/traits_test.cc", "../src/transform/binding_remapper_test.cc", "../src/transform/bound_array_accessors_test.cc", + "../src/transform/canonicalize_entry_point_io_test.cc", "../src/transform/emit_vertex_point_size_test.cc", "../src/transform/first_index_offset_test.cc", "../src/transform/renamer_test.cc",