// 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" #include "src/transform/unshadow.h" namespace tint { namespace transform { namespace { using CanonicalizeEntryPointIOTest = TransformTest; TEST_F(CanonicalizeEntryPointIOTest, Error_MissingUnshadow) { auto* src = ""; auto* expect = "error: tint::transform::CanonicalizeEntryPointIO depends on " "tint::transform::Unshadow but the dependency was not run"; auto got = Run(src); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Error_MissingTransformData) { auto* src = ""; auto* expect = "error: missing transform data for " "tint::transform::CanonicalizeEntryPointIO"; auto got = Run(src); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, NoShaderIO) { // Test that we do not introduce wrapper functions when there is no shader IO // to process. auto* src = R"( [[stage(fragment)]] fn frag_main() { } [[stage(compute), workgroup_size(1)]] fn comp_main() { } )"; auto* expect = src; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Parameters_Spirv) { auto* src = R"( [[stage(fragment)]] fn frag_main([[location(1)]] loc1 : f32, [[location(2)]] loc2 : vec4, [[builtin(position)]] coord : vec4) { var col : f32 = (coord.x * loc1); } )"; auto* expect = R"( [[location(1), internal(disable_validation__ignore_storage_class)]] var loc1_1 : f32; [[location(2), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var loc2_1 : vec4; [[builtin(position), internal(disable_validation__ignore_storage_class)]] var coord_1 : vec4; fn frag_main_inner(loc1 : f32, loc2 : vec4, coord : vec4) { var col : f32 = (coord.x * loc1); } [[stage(fragment)]] fn frag_main() { frag_main_inner(loc1_1, loc2_1, coord_1); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Parameters_Msl) { auto* src = R"( [[stage(fragment)]] fn frag_main([[location(1)]] loc1 : f32, [[location(2)]] loc2 : vec4, [[builtin(position)]] coord : vec4) { var col : f32 = (coord.x * loc1); } )"; auto* expect = R"( struct tint_symbol_1 { [[location(1)]] loc1 : f32; [[location(2)]] loc2 : vec4; }; fn frag_main_inner(loc1 : f32, loc2 : vec4, coord : vec4) { var col : f32 = (coord.x * loc1); } [[stage(fragment)]] fn frag_main([[builtin(position)]] coord : vec4, tint_symbol : tint_symbol_1) { frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, coord); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Parameters_Hlsl) { auto* src = R"( [[stage(fragment)]] fn frag_main([[location(1)]] loc1 : f32, [[location(2)]] loc2 : vec4, [[builtin(position)]] coord : vec4) { var col : f32 = (coord.x * loc1); } )"; auto* expect = R"( struct tint_symbol_1 { [[location(1)]] loc1 : f32; [[location(2)]] loc2 : vec4; [[builtin(position)]] coord : vec4; }; fn frag_main_inner(loc1 : f32, loc2 : vec4, coord : vec4) { var col : f32 = (coord.x * loc1); } [[stage(fragment)]] fn frag_main(tint_symbol : tint_symbol_1) { frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, tint_symbol.coord); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); 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) { var x : myf32 = loc1; } )"; auto* expect = R"( type myf32 = f32; struct tint_symbol_1 { [[location(1)]] loc1 : f32; }; fn frag_main_inner(loc1 : myf32) { var x : myf32 = loc1; } [[stage(fragment)]] fn frag_main(tint_symbol : tint_symbol_1) { frag_main_inner(tint_symbol.loc1); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Spirv) { auto* src = R"( struct FragBuiltins { [[builtin(position)]] coord : vec4; }; struct FragLocations { [[location(1)]] loc1 : f32; [[location(2)]] loc2 : vec4; }; [[stage(fragment)]] fn frag_main([[location(0)]] loc0 : f32, locations : FragLocations, builtins : FragBuiltins) { var col : f32 = ((builtins.coord.x * locations.loc1) + loc0); } )"; auto* expect = R"( [[location(0), internal(disable_validation__ignore_storage_class)]] var loc0_1 : f32; [[location(1), internal(disable_validation__ignore_storage_class)]] var loc1_1 : f32; [[location(2), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var loc2_1 : vec4; [[builtin(position), internal(disable_validation__ignore_storage_class)]] var coord_1 : vec4; struct FragBuiltins { coord : vec4; }; struct FragLocations { loc1 : f32; loc2 : vec4; }; fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) { var col : f32 = ((builtins.coord.x * locations.loc1) + loc0); } [[stage(fragment)]] fn frag_main() { frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1)); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, StructParameters_kMsl) { auto* src = R"( struct FragBuiltins { [[builtin(position)]] coord : vec4; }; struct FragLocations { [[location(1)]] loc1 : f32; [[location(2)]] loc2 : vec4; }; [[stage(fragment)]] fn frag_main([[location(0)]] loc0 : f32, locations : FragLocations, builtins : FragBuiltins) { 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_1 { [[location(0)]] loc0 : f32; [[location(1)]] loc1 : f32; [[location(2)]] loc2 : vec4; }; fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) { var col : f32 = ((builtins.coord.x * locations.loc1) + loc0); } [[stage(fragment)]] fn frag_main([[builtin(position)]] coord : vec4, tint_symbol : tint_symbol_1) { frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord)); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Hlsl) { auto* src = R"( struct FragBuiltins { [[builtin(position)]] coord : vec4; }; struct FragLocations { [[location(1)]] loc1 : f32; [[location(2)]] loc2 : vec4; }; [[stage(fragment)]] fn frag_main([[location(0)]] loc0 : f32, locations : FragLocations, builtins : FragBuiltins) { 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_1 { [[location(0)]] loc0 : f32; [[location(1)]] loc1 : f32; [[location(2)]] loc2 : vec4; [[builtin(position)]] coord : vec4; }; fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) { var col : f32 = ((builtins.coord.x * locations.loc1) + loc0); } [[stage(fragment)]] fn frag_main(tint_symbol : tint_symbol_1) { frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord)); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Spirv) { auto* src = R"( [[stage(fragment)]] fn frag_main() -> [[builtin(frag_depth)]] f32 { return 1.0; } )"; auto* expect = R"( [[builtin(frag_depth), internal(disable_validation__ignore_storage_class)]] var value : f32; fn frag_main_inner() -> f32 { return 1.0; } [[stage(fragment)]] fn frag_main() { let inner_result = frag_main_inner(); value = inner_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Msl) { auto* src = R"( [[stage(fragment)]] fn frag_main() -> [[builtin(frag_depth)]] f32 { return 1.0; } )"; auto* expect = R"( struct tint_symbol { [[builtin(frag_depth)]] value : f32; }; fn frag_main_inner() -> f32 { return 1.0; } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.value = inner_result; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Return_NonStruct_Hlsl) { auto* src = R"( [[stage(fragment)]] fn frag_main() -> [[builtin(frag_depth)]] f32 { return 1.0; } )"; auto* expect = R"( struct tint_symbol { [[builtin(frag_depth)]] value : f32; }; fn frag_main_inner() -> f32 { return 1.0; } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.value = inner_result; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Spirv) { auto* src = R"( struct FragOutput { [[location(0)]] color : vec4; [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] mask : u32; }; [[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"( [[location(0), internal(disable_validation__ignore_storage_class)]] var color_1 : vec4; [[builtin(frag_depth), internal(disable_validation__ignore_storage_class)]] var depth_1 : f32; [[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var mask_1 : array; struct FragOutput { color : vec4; depth : f32; mask : u32; }; fn frag_main_inner() -> FragOutput { var output : FragOutput; output.depth = 1.0; output.mask = 7u; output.color = vec4(0.5, 0.5, 0.5, 1.0); return output; } [[stage(fragment)]] fn frag_main() { let inner_result = frag_main_inner(); color_1 = inner_result.color; depth_1 = inner_result.depth; mask_1[0] = inner_result.mask; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Msl) { auto* src = R"( struct FragOutput { [[location(0)]] color : vec4; [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] mask : u32; }; [[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 { color : vec4; depth : f32; mask : u32; }; struct tint_symbol { [[location(0)]] color : vec4; [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] mask : u32; }; fn frag_main_inner() -> FragOutput { var output : FragOutput; output.depth = 1.0; output.mask = 7u; output.color = vec4(0.5, 0.5, 0.5, 1.0); return output; } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.color = inner_result.color; wrapper_result.depth = inner_result.depth; wrapper_result.mask = inner_result.mask; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Return_Struct_Hlsl) { auto* src = R"( struct FragOutput { [[location(0)]] color : vec4; [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] mask : u32; }; [[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 { color : vec4; depth : f32; mask : u32; }; struct tint_symbol { [[location(0)]] color : vec4; [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] mask : u32; }; fn frag_main_inner() -> FragOutput { var output : FragOutput; output.depth = 1.0; output.mask = 7u; output.color = vec4(0.5, 0.5, 0.5, 1.0); return output; } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.color = inner_result.color; wrapper_result.depth = inner_result.depth; wrapper_result.mask = inner_result.mask; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, StructParameters_SharedDeviceFunction_Spirv) { 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) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main2(inputs : FragmentInput) { var x : f32 = foo(inputs); } )"; auto* expect = R"( [[location(0), internal(disable_validation__ignore_storage_class)]] var value_1 : f32; [[location(1), internal(disable_validation__ignore_storage_class)]] var mul_1 : f32; [[location(0), internal(disable_validation__ignore_storage_class)]] var value_2 : f32; [[location(1), internal(disable_validation__ignore_storage_class)]] var mul_2 : f32; struct FragmentInput { value : f32; mul : f32; }; fn foo(x : FragmentInput) -> f32 { return (x.value * x.mul); } fn frag_main1_inner(inputs : FragmentInput) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main1() { frag_main1_inner(FragmentInput(value_1, mul_1)); } fn frag_main2_inner(inputs : FragmentInput) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main2() { frag_main2_inner(FragmentInput(value_2, mul_2)); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, StructParameters_SharedDeviceFunction_Msl) { 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) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main2(inputs : FragmentInput) { 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_1 { [[location(0)]] value : f32; [[location(1)]] mul : f32; }; fn frag_main1_inner(inputs : FragmentInput) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main1(tint_symbol : tint_symbol_1) { frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul)); } struct tint_symbol_3 { [[location(0)]] value : f32; [[location(1)]] mul : f32; }; fn frag_main2_inner(inputs : FragmentInput) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main2(tint_symbol_2 : tint_symbol_3) { frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul)); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, StructParameters_SharedDeviceFunction_Hlsl) { 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) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main2(inputs : FragmentInput) { 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_1 { [[location(0)]] value : f32; [[location(1)]] mul : f32; }; fn frag_main1_inner(inputs : FragmentInput) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main1(tint_symbol : tint_symbol_1) { frag_main1_inner(FragmentInput(tint_symbol.value, tint_symbol.mul)); } struct tint_symbol_3 { [[location(0)]] value : f32; [[location(1)]] mul : f32; }; fn frag_main2_inner(inputs : FragmentInput) { var x : f32 = foo(inputs); } [[stage(fragment)]] fn frag_main2(tint_symbol_2 : tint_symbol_3) { frag_main2_inner(FragmentInput(tint_symbol_2.value, tint_symbol_2.mul)); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); 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) { 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_1 { [[location(0)]] col1 : f32; [[location(1)]] col2 : f32; }; fn frag_main1_inner(inputs : FragmentInput) { global_inputs = inputs; var r : f32 = foo(); var g : f32 = bar(); } [[stage(fragment)]] fn frag_main1(tint_symbol : tint_symbol_1) { frag_main1_inner(FragmentInput(tint_symbol.col1, tint_symbol.col2)); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); 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_1 { [[location(0)]] col1 : f32; [[location(1)]] col2 : f32; }; struct tint_symbol_2 { [[location(0)]] col1 : f32; [[location(1)]] col2 : f32; }; fn frag_main_inner(inputs : MyFragmentInput) -> MyFragmentOutput { var x : myf32 = foo(inputs); return MyFragmentOutput(x, inputs.col2); } [[stage(fragment)]] fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 { let inner_result = frag_main_inner(MyFragmentInput(tint_symbol.col1, tint_symbol.col2)); var wrapper_result : tint_symbol_2; wrapper_result.col1 = inner_result.col1; wrapper_result.col2 = inner_result.col2; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes) { auto* src = R"( struct VertexOut { [[builtin(position)]] pos : vec4; [[location(1), interpolate(flat)]] loc1: f32; [[location(2), interpolate(linear, sample)]] loc2 : f32; [[location(3), interpolate(perspective, centroid)]] loc3 : f32; }; struct FragmentIn { [[location(1), interpolate(flat)]] loc1: f32; [[location(2), interpolate(linear, sample)]] loc2 : f32; }; [[stage(vertex)]] fn vert_main() -> VertexOut { return VertexOut(); } [[stage(fragment)]] fn frag_main(inputs : FragmentIn, [[location(3), interpolate(perspective, centroid)]] loc3 : f32) { let x = inputs.loc1 + inputs.loc2 + loc3; } )"; auto* expect = R"( struct VertexOut { pos : vec4; loc1 : f32; loc2 : f32; loc3 : f32; }; struct FragmentIn { loc1 : f32; loc2 : f32; }; struct tint_symbol { [[location(1), interpolate(flat)]] loc1 : f32; [[location(2), interpolate(linear, sample)]] loc2 : f32; [[location(3), interpolate(perspective, centroid)]] loc3 : f32; [[builtin(position)]] pos : vec4; }; fn vert_main_inner() -> VertexOut { return VertexOut(); } [[stage(vertex)]] fn vert_main() -> tint_symbol { let inner_result = vert_main_inner(); var wrapper_result : tint_symbol; wrapper_result.pos = inner_result.pos; wrapper_result.loc1 = inner_result.loc1; wrapper_result.loc2 = inner_result.loc2; wrapper_result.loc3 = inner_result.loc3; return wrapper_result; } struct tint_symbol_2 { [[location(1), interpolate(flat)]] loc1 : f32; [[location(2), interpolate(linear, sample)]] loc2 : f32; [[location(3), interpolate(perspective, centroid)]] loc3 : f32; }; fn frag_main_inner(inputs : FragmentIn, loc3 : f32) { let x = ((inputs.loc1 + inputs.loc2) + loc3); } [[stage(fragment)]] fn frag_main(tint_symbol_1 : tint_symbol_2) { frag_main_inner(FragmentIn(tint_symbol_1.loc1, tint_symbol_1.loc2), tint_symbol_1.loc3); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, InterpolateAttributes_Integers_Spirv) { // Test that we add a Flat attribute to integers that are vertex outputs and // fragment inputs, but not vertex inputs or fragment outputs. auto* src = R"( struct VertexIn { [[location(0)]] i : i32; [[location(1)]] u : u32; [[location(2)]] vi : vec4; [[location(3)]] vu : vec4; }; struct VertexOut { [[location(0)]] i : i32; [[location(1)]] u : u32; [[location(2)]] vi : vec4; [[location(3)]] vu : vec4; [[builtin(position)]] pos : vec4; }; struct FragmentInterface { [[location(0)]] i : i32; [[location(1)]] u : u32; [[location(2)]] vi : vec4; [[location(3)]] vu : vec4; }; [[stage(vertex)]] fn vert_main(in : VertexIn) -> VertexOut { return VertexOut(in.i, in.u, in.vi, in.vu, vec4()); } [[stage(fragment)]] fn frag_main(inputs : FragmentInterface) -> FragmentInterface { return inputs; } )"; auto* expect = R"( [[location(0), internal(disable_validation__ignore_storage_class)]] var i_1 : i32; [[location(1), internal(disable_validation__ignore_storage_class)]] var u_1 : u32; [[location(2), internal(disable_validation__ignore_storage_class)]] var vi_1 : vec4; [[location(3), internal(disable_validation__ignore_storage_class)]] var vu_1 : vec4; [[location(0), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var i_2 : i32; [[location(1), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var u_2 : u32; [[location(2), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var vi_2 : vec4; [[location(3), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var vu_2 : vec4; [[builtin(position), internal(disable_validation__ignore_storage_class)]] var pos_1 : vec4; [[location(0), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var i_3 : i32; [[location(1), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var u_3 : u32; [[location(2), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var vi_3 : vec4; [[location(3), interpolate(flat), internal(disable_validation__ignore_storage_class)]] var vu_3 : vec4; [[location(0), internal(disable_validation__ignore_storage_class)]] var i_4 : i32; [[location(1), internal(disable_validation__ignore_storage_class)]] var u_4 : u32; [[location(2), internal(disable_validation__ignore_storage_class)]] var vi_4 : vec4; [[location(3), internal(disable_validation__ignore_storage_class)]] var vu_4 : vec4; struct VertexIn { i : i32; u : u32; vi : vec4; vu : vec4; }; struct VertexOut { i : i32; u : u32; vi : vec4; vu : vec4; pos : vec4; }; struct FragmentInterface { i : i32; u : u32; vi : vec4; vu : vec4; }; fn vert_main_inner(in : VertexIn) -> VertexOut { return VertexOut(in.i, in.u, in.vi, in.vu, vec4()); } [[stage(vertex)]] fn vert_main() { let inner_result = vert_main_inner(VertexIn(i_1, u_1, vi_1, vu_1)); i_2 = inner_result.i; u_2 = inner_result.u; vi_2 = inner_result.vi; vu_2 = inner_result.vu; pos_1 = inner_result.pos; } fn frag_main_inner(inputs : FragmentInterface) -> FragmentInterface { return inputs; } [[stage(fragment)]] fn frag_main() { let inner_result_1 = frag_main_inner(FragmentInterface(i_3, u_3, vi_3, vu_3)); i_4 = inner_result_1.i; u_4 = inner_result_1.u; vi_4 = inner_result_1.vi; vu_4 = inner_result_1.vu; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, InvariantAttributes) { auto* src = R"( struct VertexOut { [[builtin(position), invariant]] pos : vec4; }; [[stage(vertex)]] fn main1() -> VertexOut { return VertexOut(); } [[stage(vertex)]] fn main2() -> [[builtin(position), invariant]] vec4 { return vec4(); } )"; auto* expect = R"( struct VertexOut { pos : vec4; }; struct tint_symbol { [[builtin(position), invariant]] pos : vec4; }; fn main1_inner() -> VertexOut { return VertexOut(); } [[stage(vertex)]] fn main1() -> tint_symbol { let inner_result = main1_inner(); var wrapper_result : tint_symbol; wrapper_result.pos = inner_result.pos; return wrapper_result; } struct tint_symbol_1 { [[builtin(position), invariant]] value : vec4; }; fn main2_inner() -> vec4 { return vec4(); } [[stage(vertex)]] fn main2() -> tint_symbol_1 { let inner_result_1 = main2_inner(); var wrapper_result_1 : tint_symbol_1; wrapper_result_1.value = inner_result_1; return wrapper_result_1; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, Struct_LayoutDecorations) { auto* src = R"( struct FragmentInput { [[size(16), location(1)]] value : f32; [[builtin(position)]] [[align(32)]] coord : vec4; [[location(0), interpolate(linear, sample)]] [[align(128)]] loc0 : f32; }; struct FragmentOutput { [[size(16), location(1), interpolate(flat)]] value : f32; }; [[stage(fragment)]] fn frag_main(inputs : FragmentInput) -> FragmentOutput { return FragmentOutput(inputs.coord.x * inputs.value + inputs.loc0); } )"; auto* expect = R"( struct FragmentInput { [[size(16)]] value : f32; [[align(32)]] coord : vec4; [[align(128)]] loc0 : f32; }; struct FragmentOutput { [[size(16)]] value : f32; }; struct tint_symbol_1 { [[location(0), interpolate(linear, sample)]] loc0 : f32; [[location(1)]] value : f32; [[builtin(position)]] coord : vec4; }; struct tint_symbol_2 { [[location(1), interpolate(flat)]] value : f32; }; fn frag_main_inner(inputs : FragmentInput) -> FragmentOutput { return FragmentOutput(((inputs.coord.x * inputs.value) + inputs.loc0)); } [[stage(fragment)]] fn frag_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 { let inner_result = frag_main_inner(FragmentInput(tint_symbol.value, tint_symbol.coord, tint_symbol.loc0)); var wrapper_result : tint_symbol_2; wrapper_result.value = inner_result.value; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, SortedMembers) { auto* src = R"( struct VertexOutput { [[location(1)]] b : u32; [[builtin(position)]] pos : vec4; [[location(3)]] d : u32; [[location(0)]] a : f32; [[location(2)]] c : i32; }; struct FragmentInputExtra { [[location(3)]] d : u32; [[builtin(position)]] pos : vec4; [[location(0)]] a : f32; }; [[stage(vertex)]] fn vert_main() -> VertexOutput { return VertexOutput(); } [[stage(fragment)]] fn frag_main([[builtin(front_facing)]] ff : bool, [[location(2)]] c : i32, inputs : FragmentInputExtra, [[location(1)]] b : u32) { } )"; auto* expect = R"( struct VertexOutput { b : u32; pos : vec4; d : u32; a : f32; c : i32; }; struct FragmentInputExtra { d : u32; pos : vec4; a : f32; }; struct tint_symbol { [[location(0)]] a : f32; [[location(1)]] b : u32; [[location(2)]] c : i32; [[location(3)]] d : u32; [[builtin(position)]] pos : vec4; }; fn vert_main_inner() -> VertexOutput { return VertexOutput(); } [[stage(vertex)]] fn vert_main() -> tint_symbol { let inner_result = vert_main_inner(); var wrapper_result : tint_symbol; wrapper_result.b = inner_result.b; wrapper_result.pos = inner_result.pos; wrapper_result.d = inner_result.d; wrapper_result.a = inner_result.a; wrapper_result.c = inner_result.c; return wrapper_result; } struct tint_symbol_2 { [[location(0)]] a : f32; [[location(1)]] b : u32; [[location(2)]] c : i32; [[location(3)]] d : u32; [[builtin(position)]] pos : vec4; [[builtin(front_facing)]] ff : bool; }; fn frag_main_inner(ff : bool, c : i32, inputs : FragmentInputExtra, b : u32) { } [[stage(fragment)]] fn frag_main(tint_symbol_1 : tint_symbol_2) { frag_main_inner(tint_symbol_1.ff, tint_symbol_1.c, FragmentInputExtra(tint_symbol_1.d, tint_symbol_1.pos, tint_symbol_1.a), tint_symbol_1.b); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, DontRenameSymbols) { auto* src = R"( [[stage(fragment)]] fn tint_symbol_1([[location(0)]] col : f32) { } )"; auto* expect = R"( struct tint_symbol_2 { [[location(0)]] col : f32; }; fn tint_symbol_1_inner(col : f32) { } [[stage(fragment)]] fn tint_symbol_1(tint_symbol : tint_symbol_2) { tint_symbol_1_inner(tint_symbol.col); } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_VoidNoReturn) { auto* src = R"( [[stage(fragment)]] fn frag_main() { } )"; auto* expect = R"( struct tint_symbol { [[builtin(sample_mask)]] fixed_sample_mask : u32; }; fn frag_main_inner() { } [[stage(fragment)]] fn frag_main() -> tint_symbol { frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.fixed_sample_mask = 3u; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_VoidWithReturn) { auto* src = R"( [[stage(fragment)]] fn frag_main() { return; } )"; auto* expect = R"( struct tint_symbol { [[builtin(sample_mask)]] fixed_sample_mask : u32; }; fn frag_main_inner() { return; } [[stage(fragment)]] fn frag_main() -> tint_symbol { frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.fixed_sample_mask = 3u; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_WithAuthoredMask) { auto* src = R"( [[stage(fragment)]] fn frag_main() -> [[builtin(sample_mask)]] u32 { return 7u; } )"; auto* expect = R"( struct tint_symbol { [[builtin(sample_mask)]] value : u32; }; fn frag_main_inner() -> u32 { return 7u; } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.value = (inner_result & 3u); return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_WithoutAuthoredMask) { auto* src = R"( [[stage(fragment)]] fn frag_main() -> [[location(0)]] f32 { return 1.0; } )"; auto* expect = R"( struct tint_symbol { [[location(0)]] value : f32; [[builtin(sample_mask)]] fixed_sample_mask : u32; }; fn frag_main_inner() -> f32 { return 1.0; } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.value = inner_result; wrapper_result.fixed_sample_mask = 3u; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_StructWithAuthoredMask) { auto* src = R"( struct Output { [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] mask : u32; [[location(0)]] value : f32; }; [[stage(fragment)]] fn frag_main() -> Output { return Output(0.5, 7u, 1.0); } )"; auto* expect = R"( struct Output { depth : f32; mask : u32; value : f32; }; struct tint_symbol { [[location(0)]] value : f32; [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] mask : u32; }; fn frag_main_inner() -> Output { return Output(0.5, 7u, 1.0); } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.depth = inner_result.depth; wrapper_result.mask = (inner_result.mask & 3u); wrapper_result.value = inner_result.value; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_StructWithoutAuthoredMask) { auto* src = R"( struct Output { [[builtin(frag_depth)]] depth : f32; [[location(0)]] value : f32; }; [[stage(fragment)]] fn frag_main() -> Output { return Output(0.5, 1.0); } )"; auto* expect = R"( struct Output { depth : f32; value : f32; }; struct tint_symbol { [[location(0)]] value : f32; [[builtin(frag_depth)]] depth : f32; [[builtin(sample_mask)]] fixed_sample_mask : u32; }; fn frag_main_inner() -> Output { return Output(0.5, 1.0); } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.depth = inner_result.depth; wrapper_result.value = inner_result.value; wrapper_result.fixed_sample_mask = 3u; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_MultipleShaders) { auto* src = R"( [[stage(fragment)]] fn frag_main1() -> [[builtin(sample_mask)]] u32 { return 7u; } [[stage(fragment)]] fn frag_main2() -> [[location(0)]] f32 { return 1.0; } [[stage(vertex)]] fn vert_main1() -> [[builtin(position)]] vec4 { return vec4(); } [[stage(compute), workgroup_size(1)]] fn comp_main1() { } )"; auto* expect = R"( struct tint_symbol { [[builtin(sample_mask)]] value : u32; }; fn frag_main1_inner() -> u32 { return 7u; } [[stage(fragment)]] fn frag_main1() -> tint_symbol { let inner_result = frag_main1_inner(); var wrapper_result : tint_symbol; wrapper_result.value = (inner_result & 3u); return wrapper_result; } struct tint_symbol_1 { [[location(0)]] value : f32; [[builtin(sample_mask)]] fixed_sample_mask : u32; }; fn frag_main2_inner() -> f32 { return 1.0; } [[stage(fragment)]] fn frag_main2() -> tint_symbol_1 { let inner_result_1 = frag_main2_inner(); var wrapper_result_1 : tint_symbol_1; wrapper_result_1.value = inner_result_1; wrapper_result_1.fixed_sample_mask = 3u; return wrapper_result_1; } struct tint_symbol_2 { [[builtin(position)]] value : vec4; }; fn vert_main1_inner() -> vec4 { return vec4(); } [[stage(vertex)]] fn vert_main1() -> tint_symbol_2 { let inner_result_2 = vert_main1_inner(); var wrapper_result_2 : tint_symbol_2; wrapper_result_2.value = inner_result_2; return wrapper_result_2; } [[stage(compute), workgroup_size(1)]] fn comp_main1() { } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03u); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, FixedSampleMask_AvoidNameClash) { auto* src = R"( struct FragOut { [[location(0)]] fixed_sample_mask : vec4; [[location(1)]] fixed_sample_mask_1 : vec4; }; [[stage(fragment)]] fn frag_main() -> FragOut { return FragOut(); } )"; auto* expect = R"( struct FragOut { fixed_sample_mask : vec4; fixed_sample_mask_1 : vec4; }; struct tint_symbol { [[location(0)]] fixed_sample_mask : vec4; [[location(1)]] fixed_sample_mask_1 : vec4; [[builtin(sample_mask)]] fixed_sample_mask_2 : u32; }; fn frag_main_inner() -> FragOut { return FragOut(); } [[stage(fragment)]] fn frag_main() -> tint_symbol { let inner_result = frag_main_inner(); var wrapper_result : tint_symbol; wrapper_result.fixed_sample_mask = inner_result.fixed_sample_mask; wrapper_result.fixed_sample_mask_1 = inner_result.fixed_sample_mask_1; wrapper_result.fixed_sample_mask_2 = 3u; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0x03); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnNonStruct_Spirv) { auto* src = R"( [[stage(vertex)]] fn vert_main() -> [[builtin(position)]] vec4 { return vec4(); } )"; auto* expect = R"( [[builtin(position), internal(disable_validation__ignore_storage_class)]] var value : vec4; [[builtin(pointsize), internal(disable_validation__ignore_storage_class)]] var vertex_point_size : f32; fn vert_main_inner() -> vec4 { return vec4(); } [[stage(vertex)]] fn vert_main() { let inner_result = vert_main_inner(); value = inner_result; vertex_point_size = 1.0; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnNonStruct_Msl) { auto* src = R"( [[stage(vertex)]] fn vert_main() -> [[builtin(position)]] vec4 { return vec4(); } )"; auto* expect = R"( struct tint_symbol { [[builtin(position)]] value : vec4; [[builtin(pointsize)]] vertex_point_size : f32; }; fn vert_main_inner() -> vec4 { return vec4(); } [[stage(vertex)]] fn vert_main() -> tint_symbol { let inner_result = vert_main_inner(); var wrapper_result : tint_symbol; wrapper_result.value = inner_result; wrapper_result.vertex_point_size = 1.0; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct_Spirv) { auto* src = R"( struct VertOut { [[builtin(position)]] pos : vec4; }; [[stage(vertex)]] fn vert_main() -> VertOut { return VertOut(); } )"; auto* expect = R"( [[builtin(position), internal(disable_validation__ignore_storage_class)]] var pos_1 : vec4; [[builtin(pointsize), internal(disable_validation__ignore_storage_class)]] var vertex_point_size : f32; struct VertOut { pos : vec4; }; fn vert_main_inner() -> VertOut { return VertOut(); } [[stage(vertex)]] fn vert_main() { let inner_result = vert_main_inner(); pos_1 = inner_result.pos; vertex_point_size = 1.0; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_ReturnStruct_Msl) { auto* src = R"( struct VertOut { [[builtin(position)]] pos : vec4; }; [[stage(vertex)]] fn vert_main() -> VertOut { return VertOut(); } )"; auto* expect = R"( struct VertOut { pos : vec4; }; struct tint_symbol { [[builtin(position)]] pos : vec4; [[builtin(pointsize)]] vertex_point_size : f32; }; fn vert_main_inner() -> VertOut { return VertOut(); } [[stage(vertex)]] fn vert_main() -> tint_symbol { let inner_result = vert_main_inner(); var wrapper_result : tint_symbol; wrapper_result.pos = inner_result.pos; wrapper_result.vertex_point_size = 1.0; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Spirv) { auto* src = R"( var vertex_point_size : f32; var vertex_point_size_1 : f32; var vertex_point_size_2 : f32; struct VertIn1 { [[location(0)]] collide : f32; }; struct VertIn2 { [[location(1)]] collide : f32; }; struct VertOut { [[location(0)]] vertex_point_size : f32; [[builtin(position)]] vertex_point_size_1 : vec4; }; [[stage(vertex)]] fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut { let x = collide.collide + collide_1.collide; return VertOut(); } )"; auto* expect = R"( [[location(0), internal(disable_validation__ignore_storage_class)]] var collide_2 : f32; [[location(1), internal(disable_validation__ignore_storage_class)]] var collide_3 : f32; [[location(0), internal(disable_validation__ignore_storage_class)]] var vertex_point_size_3 : f32; [[builtin(position), internal(disable_validation__ignore_storage_class)]] var vertex_point_size_1_1 : vec4; [[builtin(pointsize), internal(disable_validation__ignore_storage_class)]] var vertex_point_size_4 : f32; var vertex_point_size : f32; var vertex_point_size_1 : f32; var vertex_point_size_2 : f32; struct VertIn1 { collide : f32; }; struct VertIn2 { collide : f32; }; struct VertOut { vertex_point_size : f32; vertex_point_size_1 : vec4; }; fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut { let x = (collide.collide + collide_1.collide); return VertOut(); } [[stage(vertex)]] fn vert_main() { let inner_result = vert_main_inner(VertIn1(collide_2), VertIn2(collide_3)); vertex_point_size_3 = inner_result.vertex_point_size; vertex_point_size_1_1 = inner_result.vertex_point_size_1; vertex_point_size_4 = 1.0; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv, 0xFFFFFFFF, true); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Msl) { auto* src = R"( struct VertIn1 { [[location(0)]] collide : f32; }; struct VertIn2 { [[location(1)]] collide : f32; }; struct VertOut { [[location(0)]] vertex_point_size : vec4; [[builtin(position)]] vertex_point_size_1 : vec4; }; [[stage(vertex)]] fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut { let x = collide.collide + collide_1.collide; return VertOut(); } )"; auto* expect = R"( struct VertIn1 { collide : f32; }; struct VertIn2 { collide : f32; }; struct VertOut { vertex_point_size : vec4; vertex_point_size_1 : vec4; }; struct tint_symbol_1 { [[location(0)]] collide : f32; [[location(1)]] collide_2 : f32; }; struct tint_symbol_2 { [[location(0)]] vertex_point_size : vec4; [[builtin(position)]] vertex_point_size_1 : vec4; [[builtin(pointsize)]] vertex_point_size_2 : f32; }; fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut { let x = (collide.collide + collide_1.collide); return VertOut(); } [[stage(vertex)]] fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 { let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2)); var wrapper_result : tint_symbol_2; wrapper_result.vertex_point_size = inner_result.vertex_point_size; wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1; wrapper_result.vertex_point_size_2 = 1.0; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kMsl, 0xFFFFFFFF, true); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, EmitVertexPointSize_AvoidNameClash_Hlsl) { auto* src = R"( struct VertIn1 { [[location(0)]] collide : f32; }; struct VertIn2 { [[location(1)]] collide : f32; }; struct VertOut { [[location(0)]] vertex_point_size : vec4; [[builtin(position)]] vertex_point_size_1 : vec4; }; [[stage(vertex)]] fn vert_main(collide : VertIn1, collide_1 : VertIn2) -> VertOut { let x = collide.collide + collide_1.collide; return VertOut(); } )"; auto* expect = R"( struct VertIn1 { collide : f32; }; struct VertIn2 { collide : f32; }; struct VertOut { vertex_point_size : vec4; vertex_point_size_1 : vec4; }; struct tint_symbol_1 { [[location(0)]] collide : f32; [[location(1)]] collide_2 : f32; }; struct tint_symbol_2 { [[location(0)]] vertex_point_size : vec4; [[builtin(position)]] vertex_point_size_1 : vec4; [[builtin(pointsize)]] vertex_point_size_2 : f32; }; fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut { let x = (collide.collide + collide_1.collide); return VertOut(); } [[stage(vertex)]] fn vert_main(tint_symbol : tint_symbol_1) -> tint_symbol_2 { let inner_result = vert_main_inner(VertIn1(tint_symbol.collide), VertIn2(tint_symbol.collide_2)); var wrapper_result : tint_symbol_2; wrapper_result.vertex_point_size = inner_result.vertex_point_size; wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1; wrapper_result.vertex_point_size_2 = 1.0; return wrapper_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kHlsl, 0xFFFFFFFF, true); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } TEST_F(CanonicalizeEntryPointIOTest, SpirvSampleMaskBuiltins) { auto* src = R"( [[stage(fragment)]] fn main([[builtin(sample_index)]] sample_index : u32, [[builtin(sample_mask)]] mask_in : u32 ) -> [[builtin(sample_mask)]] u32 { return mask_in; } )"; auto* expect = R"( [[builtin(sample_index), internal(disable_validation__ignore_storage_class)]] var sample_index_1 : u32; [[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var mask_in_1 : array; [[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var value : array; fn main_inner(sample_index : u32, mask_in : u32) -> u32 { return mask_in; } [[stage(fragment)]] fn main() { let inner_result = main_inner(sample_index_1, mask_in_1[0]); value[0] = inner_result; } )"; DataMap data; data.Add( CanonicalizeEntryPointIO::ShaderStyle::kSpirv); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); } } // namespace } // namespace transform } // namespace tint