Add a transform for canonicalizing entry point interfaces

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.

This will be used to sanitize entry points for the MSL and HLSL
generators.

Change-Id: I7d2fcfba961d2e20326086964207747d573b6e05
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/46500
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
James Price 2021-03-31 17:44:27 +00:00 committed by Commit Bot service account
parent c87ff9c238
commit 3f985b984d
6 changed files with 828 additions and 0 deletions

View File

@ -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",

View File

@ -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

View File

@ -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 <utility>
#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<type::Struct>()) {
// 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<ast::BuiltinDecoration, ast::LocationDecoration>();
});
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<type::Struct>(
ctx.Clone(struct_ty->symbol()),
ctx.dst->create<ast::Struct>(
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<type::Struct>()) {
// 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<type::Struct>()) {
TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct";
}
ast::DecorationList new_decorations = RemoveDecorations(
&ctx, member->decorations(), [](const ast::Decoration* deco) {
return !deco->IsAnyOf<ast::BuiltinDecoration,
ast::LocationDecoration>();
});
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<ast::BuiltinDecoration,
ast::LocationDecoration>();
});
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<ast::Expression>(user->Declaration(),
ctx.dst->Expr(func_const_symbol));
}
}
// Create the new struct type.
auto* in_struct = ctx.dst->create<type::Struct>(
ctx.dst->Symbols().New(),
ctx.dst->create<ast::Struct>(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<type::Void>()) {
new_ret_type = ctx.dst->ty.void_();
} else {
ast::StructMemberList new_struct_members;
if (auto* struct_ty = ret_type->As<type::Struct>()) {
// Rebuild struct with only the entry point IO attributes.
for (auto* member : struct_ty->impl()->members()) {
if (member->type()->UnwrapAll()->Is<type::Struct>()) {
TINT_ICE(ctx.dst->Diagnostics()) << "nested pipeline IO struct";
}
ast::DecorationList new_decorations = RemoveDecorations(
&ctx, member->decorations(), [](const ast::Decoration* deco) {
return !deco->IsAnyOf<ast::BuiltinDecoration,
ast::LocationDecoration>();
});
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<type::Struct>(
ctx.dst->Symbols().New(),
ctx.dst->create<ast::Struct>(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<type::Struct>()) {
if (!ret->value()->Is<ast::IdentifierExpression>()) {
// 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<ast::ReturnStatement>(
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<ast::Function>(
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

View File

@ -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<u32>;
/// };
///
/// [[stage(fragment)]]
/// fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>,
/// locations : Locations) -> [[location(0)]] f32 {
/// var col : f32 = (coord.x * locations.loc1);
/// return col;
/// }
/// ```
///
/// After:
/// ```
/// struct Locations{
/// loc1 : f32;
/// loc2 : vec4<u32>;
/// };
///
/// struct frag_main_in {
/// [[builtin(frag_coord)]] coord : vec4<f32>;
/// [[location(1)]] loc1 : f32;
/// [[location(2)]] loc2 : vec4<u32>
/// };
///
/// 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_

View File

@ -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<f32>,
[[location(1)]] loc1 : f32,
[[location(2)]] loc2 : vec4<u32>) -> void {
var col : f32 = (coord.x * loc1);
}
)";
auto* expect = R"(
struct tint_symbol_5 {
[[builtin(frag_coord)]]
coord : vec4<f32>;
[[location(1)]]
loc1 : f32;
[[location(2)]]
loc2 : vec4<u32>;
};
[[stage(fragment)]]
fn frag_main(tint_symbol_1 : tint_symbol_5) -> void {
const coord : vec4<f32> = tint_symbol_1.coord;
const loc1 : f32 = tint_symbol_1.loc1;
const loc2 : vec4<u32> = tint_symbol_1.loc2;
var col : f32 = (coord.x * loc1);
}
)";
auto got = Run<CanonicalizeEntryPointIO>(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<CanonicalizeEntryPointIO>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(CanonicalizeEntryPointIOTest, Parameters_EmptyBody) {
auto* src = R"(
[[stage(fragment)]]
fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>,
[[location(1)]] loc1 : f32,
[[location(2)]] loc2 : vec4<u32>) -> void {
}
)";
auto* expect = R"(
struct tint_symbol_5 {
[[builtin(frag_coord)]]
coord : vec4<f32>;
[[location(1)]]
loc1 : f32;
[[location(2)]]
loc2 : vec4<u32>;
};
[[stage(fragment)]]
fn frag_main(tint_symbol_1 : tint_symbol_5) -> void {
}
)";
auto got = Run<CanonicalizeEntryPointIO>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(CanonicalizeEntryPointIOTest, StructParameters) {
auto* src = R"(
struct FragBuiltins {
[[builtin(frag_coord)]] coord : vec4<f32>;
};
struct FragLocations {
[[location(1)]] loc1 : f32;
[[location(2)]] loc2 : vec4<u32>;
};
[[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<f32>;
};
struct FragLocations {
loc1 : f32;
loc2 : vec4<u32>;
};
struct tint_symbol_10 {
[[builtin(frag_coord)]]
coord : vec4<f32>;
[[location(1)]]
loc1 : f32;
[[location(2)]]
loc2 : vec4<u32>;
[[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<CanonicalizeEntryPointIO>(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<CanonicalizeEntryPointIO>(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<f32>;
};
[[stage(fragment)]]
fn frag_main() -> FragOutput {
var output : FragOutput;
output.depth = 1.0;
output.mask = 7u;
output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
return output;
}
)";
auto* expect = R"(
struct FragOutput {
depth : f32;
mask : u32;
color : vec4<f32>;
};
struct tint_symbol_5 {
[[builtin(frag_depth)]]
depth : f32;
[[builtin(sample_mask_out)]]
mask : u32;
[[location(0)]]
color : vec4<f32>;
};
[[stage(fragment)]]
fn frag_main() -> tint_symbol_5 {
var output : FragOutput;
output.depth = 1.0;
output.mask = 7u;
output.color = vec4<f32>(0.5, 0.5, 0.5, 1.0);
return tint_symbol_5(output.depth, output.mask, output.color);
}
)";
auto got = Run<CanonicalizeEntryPointIO>(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<CanonicalizeEntryPointIO>(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<private> 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<private> 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<CanonicalizeEntryPointIO>(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<CanonicalizeEntryPointIO>(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<f32>;
};
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<f32>;
};
struct FragmentOutput {
[[size(16)]]
value : f32;
};
struct tint_symbol_7 {
[[location(1)]]
value : f32;
[[builtin(frag_coord)]]
coord : vec4<f32>;
};
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<CanonicalizeEntryPointIO>(src);
EXPECT_EQ(expect, str(got));
}
} // namespace
} // namespace transform
} // namespace tint

View File

@ -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",