| // Copyright 2024 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <utility> |
| |
| #include "src/tint/lang/core/ir/transform/helper_test.h" |
| #include "src/tint/lang/core/type/struct.h" |
| #include "src/tint/lang/hlsl/writer/raise/shader_io.h" |
| |
| namespace tint::hlsl::writer::raise { |
| namespace { |
| |
| using namespace tint::core::fluent_types; // NOLINT |
| using namespace tint::core::number_suffixes; // NOLINT |
| |
| using HlslWriterTransformTest = core::ir::transform::TransformTest; |
| |
| TEST_F(HlslWriterTransformTest, ShaderIONoInputsOrOutputs) { |
| auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kCompute); |
| ep->SetWorkgroupSize(1, 1, 1); |
| b.Append(ep->Block(), [&] { b.Return(ep); }); |
| |
| auto* src = R"( |
| %foo = @compute @workgroup_size(1, 1, 1) func():void { |
| $B1: { |
| ret |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = src; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_NonStruct) { |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing); |
| |
| auto* position = b.FunctionParam("position", ty.vec4<f32>()); |
| position->SetBuiltin(core::BuiltinValue::kPosition); |
| position->SetInvariant(true); |
| |
| auto* color1 = b.FunctionParam("color1", ty.f32()); |
| color1->SetLocation(0); |
| |
| auto* color2 = b.FunctionParam("color2", ty.f32()); |
| color2->SetLocation(1); |
| color2->SetInterpolation(core::Interpolation{core::InterpolationType::kLinear, |
| core::InterpolationSampling::kSample}); |
| |
| auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing, position, color1, color2}); |
| |
| b.Append(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.Append(ifelse->True(), [&] { |
| b.Multiply(ty.vec4<f32>(), position, b.Add(ty.f32(), color1, color2)); |
| b.ExitIf(ifelse); |
| }); |
| b.Return(ep); |
| }); |
| |
| auto* src = R"( |
| %foo = @fragment func(%front_facing:bool [@front_facing], %position:vec4<f32> [@invariant, @position], %color1:f32 [@location(0)], %color2:f32 [@location(1), @interpolate(linear, sample)]):void { |
| $B1: { |
| if %front_facing [t: $B2] { # if_1 |
| $B2: { # true |
| %6:f32 = add %color1, %color2 |
| %7:vec4<f32> = mul %position, %6 |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| foo_inputs = struct @align(16) { |
| color1:f32 @offset(0), @location(0) |
| color2:f32 @offset(4), @location(1), @interpolate(linear, sample) |
| position:vec4<f32> @offset(16), @invariant, @builtin(position) |
| front_facing:bool @offset(32), @builtin(front_facing) |
| } |
| |
| %foo_inner = func(%front_facing:bool, %position:vec4<f32>, %color1:f32, %color2:f32):void { |
| $B1: { |
| if %front_facing [t: $B2] { # if_1 |
| $B2: { # true |
| %6:f32 = add %color1, %color2 |
| %7:vec4<f32> = mul %position, %6 |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| %foo = @fragment func(%inputs:foo_inputs):void { |
| $B3: { |
| %10:bool = access %inputs, 3u |
| %11:vec4<f32> = access %inputs, 2u |
| %12:f32 = access %11, 3u |
| %13:f32 = div 1.0f, %12 |
| %14:vec3<f32> = swizzle %11, xyz |
| %15:vec4<f32> = construct %14, %13 |
| %16:f32 = access %inputs, 0u |
| %17:f32 = access %inputs, 1u |
| %18:void = call %foo_inner, %10, %15, %16, %17 |
| ret |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_Struct) { |
| auto* str_ty = ty.Struct(mod.symbols.New("Inputs"), |
| { |
| { |
| mod.symbols.New("front_facing"), |
| ty.bool_(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kFrontFacing, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("position"), |
| ty.vec4<f32>(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kPosition, |
| /* interpolation */ std::nullopt, |
| /* invariant */ true, |
| }, |
| }, |
| { |
| mod.symbols.New("color1"), |
| ty.f32(), |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("color2"), |
| ty.f32(), |
| core::IOAttributes{ |
| /* location */ 1u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ |
| core::Interpolation{ |
| core::InterpolationType::kLinear, |
| core::InterpolationSampling::kSample, |
| }, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* str_param = b.FunctionParam("inputs", str_ty); |
| |
| auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| ep->SetParams({str_param}); |
| |
| b.Append(ep->Block(), [&] { |
| auto* ifelse = b.If(b.Access(ty.bool_(), str_param, 0_i)); |
| b.Append(ifelse->True(), [&] { |
| auto* position = b.Access(ty.vec4<f32>(), str_param, 1_i); |
| auto* color1 = b.Access(ty.f32(), str_param, 2_i); |
| auto* color2 = b.Access(ty.f32(), str_param, 3_i); |
| b.Multiply(ty.vec4<f32>(), position, b.Add(ty.f32(), color1, color2)); |
| b.ExitIf(ifelse); |
| }); |
| b.Return(ep); |
| }); |
| |
| auto* src = R"( |
| Inputs = struct @align(16) { |
| front_facing:bool @offset(0), @builtin(front_facing) |
| position:vec4<f32> @offset(16), @invariant, @builtin(position) |
| color1:f32 @offset(32), @location(0) |
| color2:f32 @offset(36), @location(1), @interpolate(linear, sample) |
| } |
| |
| %foo = @fragment func(%inputs:Inputs):void { |
| $B1: { |
| %3:bool = access %inputs, 0i |
| if %3 [t: $B2] { # if_1 |
| $B2: { # true |
| %4:vec4<f32> = access %inputs, 1i |
| %5:f32 = access %inputs, 2i |
| %6:f32 = access %inputs, 3i |
| %7:f32 = add %5, %6 |
| %8:vec4<f32> = mul %4, %7 |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Inputs = struct @align(16) { |
| front_facing:bool @offset(0) |
| position:vec4<f32> @offset(16) |
| color1:f32 @offset(32) |
| color2:f32 @offset(36) |
| } |
| |
| foo_inputs = struct @align(16) { |
| Inputs_color1:f32 @offset(0), @location(0) |
| Inputs_color2:f32 @offset(4), @location(1), @interpolate(linear, sample) |
| Inputs_position:vec4<f32> @offset(16), @invariant, @builtin(position) |
| Inputs_front_facing:bool @offset(32), @builtin(front_facing) |
| } |
| |
| %foo_inner = func(%inputs:Inputs):void { |
| $B1: { |
| %3:bool = access %inputs, 0i |
| if %3 [t: $B2] { # if_1 |
| $B2: { # true |
| %4:vec4<f32> = access %inputs, 1i |
| %5:f32 = access %inputs, 2i |
| %6:f32 = access %inputs, 3i |
| %7:f32 = add %5, %6 |
| %8:vec4<f32> = mul %4, %7 |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| %foo = @fragment func(%inputs_1:foo_inputs):void { # %inputs_1: 'inputs' |
| $B3: { |
| %11:bool = access %inputs_1, 3u |
| %12:vec4<f32> = access %inputs_1, 2u |
| %13:f32 = access %12, 3u |
| %14:f32 = div 1.0f, %13 |
| %15:vec3<f32> = swizzle %12, xyz |
| %16:vec4<f32> = construct %15, %14 |
| %17:f32 = access %inputs_1, 0u |
| %18:f32 = access %inputs_1, 1u |
| %19:Inputs = construct %11, %16, %17, %18 |
| %20:void = call %foo_inner, %19 |
| ret |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_Mixed) { |
| auto* str_ty = ty.Struct(mod.symbols.New("Inputs"), |
| { |
| { |
| mod.symbols.New("position"), |
| ty.vec4<f32>(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kPosition, |
| /* interpolation */ std::nullopt, |
| /* invariant */ true, |
| }, |
| }, |
| { |
| mod.symbols.New("color1"), |
| ty.f32(), |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* front_facing = b.FunctionParam("front_facing", ty.bool_()); |
| front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing); |
| |
| auto* str_param = b.FunctionParam("inputs", str_ty); |
| |
| auto* color2 = b.FunctionParam("color2", ty.f32()); |
| color2->SetLocation(1); |
| color2->SetInterpolation(core::Interpolation{core::InterpolationType::kLinear, |
| core::InterpolationSampling::kSample}); |
| |
| auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| ep->SetParams({front_facing, str_param, color2}); |
| |
| b.Append(ep->Block(), [&] { |
| auto* ifelse = b.If(front_facing); |
| b.Append(ifelse->True(), [&] { |
| auto* position = b.Access(ty.vec4<f32>(), str_param, 0_i); |
| auto* color1 = b.Access(ty.f32(), str_param, 1_i); |
| b.Multiply(ty.vec4<f32>(), position, b.Add(ty.f32(), color1, color2)); |
| b.ExitIf(ifelse); |
| }); |
| b.Return(ep); |
| }); |
| |
| auto* src = R"( |
| Inputs = struct @align(16) { |
| position:vec4<f32> @offset(0), @invariant, @builtin(position) |
| color1:f32 @offset(16), @location(0) |
| } |
| |
| %foo = @fragment func(%front_facing:bool [@front_facing], %inputs:Inputs, %color2:f32 [@location(1), @interpolate(linear, sample)]):void { |
| $B1: { |
| if %front_facing [t: $B2] { # if_1 |
| $B2: { # true |
| %5:vec4<f32> = access %inputs, 0i |
| %6:f32 = access %inputs, 1i |
| %7:f32 = add %6, %color2 |
| %8:vec4<f32> = mul %5, %7 |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Inputs = struct @align(16) { |
| position:vec4<f32> @offset(0) |
| color1:f32 @offset(16) |
| } |
| |
| foo_inputs = struct @align(16) { |
| Inputs_color1:f32 @offset(0), @location(0) |
| color2:f32 @offset(4), @location(1), @interpolate(linear, sample) |
| Inputs_position:vec4<f32> @offset(16), @invariant, @builtin(position) |
| front_facing:bool @offset(32), @builtin(front_facing) |
| } |
| |
| %foo_inner = func(%front_facing:bool, %inputs:Inputs, %color2:f32):void { |
| $B1: { |
| if %front_facing [t: $B2] { # if_1 |
| $B2: { # true |
| %5:vec4<f32> = access %inputs, 0i |
| %6:f32 = access %inputs, 1i |
| %7:f32 = add %6, %color2 |
| %8:vec4<f32> = mul %5, %7 |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| %foo = @fragment func(%inputs_1:foo_inputs):void { # %inputs_1: 'inputs' |
| $B3: { |
| %11:bool = access %inputs_1, 3u |
| %12:vec4<f32> = access %inputs_1, 2u |
| %13:f32 = access %12, 3u |
| %14:f32 = div 1.0f, %13 |
| %15:vec3<f32> = swizzle %12, xyz |
| %16:vec4<f32> = construct %15, %14 |
| %17:f32 = access %inputs_1, 0u |
| %18:Inputs = construct %16, %17 |
| %19:f32 = access %inputs_1, 1u |
| %20:void = call %foo_inner, %11, %18, %19 |
| ret |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOReturnValue_NonStructBuiltin) { |
| auto* ep = b.Function("foo", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex); |
| ep->SetReturnBuiltin(core::BuiltinValue::kPosition); |
| ep->SetReturnInvariant(true); |
| |
| b.Append(ep->Block(), [&] { b.Return(ep, b.Construct(ty.vec4<f32>(), 0.5_f)); }); |
| |
| auto* src = R"( |
| %foo = @vertex func():vec4<f32> [@invariant, @position] { |
| $B1: { |
| %2:vec4<f32> = construct 0.5f |
| ret %2 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| foo_outputs = struct @align(16) { |
| tint_symbol:vec4<f32> @offset(0), @invariant, @builtin(position) |
| } |
| |
| %foo_inner = func():vec4<f32> { |
| $B1: { |
| %2:vec4<f32> = construct 0.5f |
| ret %2 |
| } |
| } |
| %foo = @vertex func():foo_outputs { |
| $B2: { |
| %4:vec4<f32> = call %foo_inner |
| %5:foo_outputs = construct %4 |
| ret %5 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOReturnValue_NonStructLocation) { |
| auto* ep = b.Function("foo", ty.vec4<f32>(), core::ir::Function::PipelineStage::kFragment); |
| ep->SetReturnLocation(1u); |
| |
| b.Append(ep->Block(), [&] { b.Return(ep, b.Construct(ty.vec4<f32>(), 0.5_f)); }); |
| |
| auto* src = R"( |
| %foo = @fragment func():vec4<f32> [@location(1)] { |
| $B1: { |
| %2:vec4<f32> = construct 0.5f |
| ret %2 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| foo_outputs = struct @align(16) { |
| tint_symbol:vec4<f32> @offset(0), @location(1) |
| } |
| |
| %foo_inner = func():vec4<f32> { |
| $B1: { |
| %2:vec4<f32> = construct 0.5f |
| ret %2 |
| } |
| } |
| %foo = @fragment func():foo_outputs { |
| $B2: { |
| %4:vec4<f32> = call %foo_inner |
| %5:foo_outputs = construct %4 |
| ret %5 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOReturnValue_Struct) { |
| auto* str_ty = ty.Struct(mod.symbols.New("Outputs"), |
| { |
| { |
| mod.symbols.New("position"), |
| ty.vec4<f32>(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kPosition, |
| /* interpolation */ std::nullopt, |
| /* invariant */ true, |
| }, |
| }, |
| { |
| mod.symbols.New("color1"), |
| ty.f32(), |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("color2"), |
| ty.f32(), |
| core::IOAttributes{ |
| /* location */ 1u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ |
| core::Interpolation{ |
| core::InterpolationType::kLinear, |
| core::InterpolationSampling::kSample, |
| }, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* ep = b.Function("foo", str_ty, core::ir::Function::PipelineStage::kVertex); |
| |
| b.Append(ep->Block(), [&] { |
| b.Return(ep, b.Construct(str_ty, b.Construct(ty.vec4<f32>(), 0_f), 0.25_f, 0.75_f)); |
| }); |
| |
| auto* src = R"( |
| Outputs = struct @align(16) { |
| position:vec4<f32> @offset(0), @invariant, @builtin(position) |
| color1:f32 @offset(16), @location(0) |
| color2:f32 @offset(20), @location(1), @interpolate(linear, sample) |
| } |
| |
| %foo = @vertex func():Outputs { |
| $B1: { |
| %2:vec4<f32> = construct 0.0f |
| %3:Outputs = construct %2, 0.25f, 0.75f |
| ret %3 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Outputs = struct @align(16) { |
| position:vec4<f32> @offset(0) |
| color1:f32 @offset(16) |
| color2:f32 @offset(20) |
| } |
| |
| foo_outputs = struct @align(16) { |
| Outputs_color1:f32 @offset(0), @location(0) |
| Outputs_color2:f32 @offset(4), @location(1), @interpolate(linear, sample) |
| Outputs_position:vec4<f32> @offset(16), @invariant, @builtin(position) |
| } |
| |
| %foo_inner = func():Outputs { |
| $B1: { |
| %2:vec4<f32> = construct 0.0f |
| %3:Outputs = construct %2, 0.25f, 0.75f |
| ret %3 |
| } |
| } |
| %foo = @vertex func():foo_outputs { |
| $B2: { |
| %5:Outputs = call %foo_inner |
| %6:vec4<f32> = access %5, 0u |
| %7:f32 = access %5, 1u |
| %8:f32 = access %5, 2u |
| %9:foo_outputs = construct %7, %8, %6 |
| ret %9 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOReturnValue_DualSourceBlending) { |
| auto* str_ty = |
| ty.Struct(mod.symbols.New("Output"), { |
| { |
| mod.symbols.New("color1"), |
| ty.f32(), |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ 0u, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("color2"), |
| ty.f32(), |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ 1u, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* ep = b.Function("foo", str_ty, core::ir::Function::PipelineStage::kFragment); |
| b.Append(ep->Block(), [&] { b.Return(ep, b.Construct(str_ty, 0.25_f, 0.75_f)); }); |
| |
| auto* src = R"( |
| Output = struct @align(4) { |
| color1:f32 @offset(0), @location(0) |
| color2:f32 @offset(4), @location(0) |
| } |
| |
| %foo = @fragment func():Output { |
| $B1: { |
| %2:Output = construct 0.25f, 0.75f |
| ret %2 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Output = struct @align(4) { |
| color1:f32 @offset(0) |
| color2:f32 @offset(4) |
| } |
| |
| foo_outputs = struct @align(4) { |
| Output_color1:f32 @offset(0), @location(0) |
| Output_color2:f32 @offset(4), @location(0) |
| } |
| |
| %foo_inner = func():Output { |
| $B1: { |
| %2:Output = construct 0.25f, 0.75f |
| ret %2 |
| } |
| } |
| %foo = @fragment func():foo_outputs { |
| $B2: { |
| %4:Output = call %foo_inner |
| %5:f32 = access %4, 0u |
| %6:f32 = access %4, 1u |
| %7:foo_outputs = construct %5, %6 |
| ret %7 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOStruct_SharedByVertexAndFragment) { |
| auto* str_ty = ty.Struct(mod.symbols.New("Interface"), |
| { |
| { |
| mod.symbols.New("position"), |
| ty.vec4<f32>(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kPosition, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("color"), |
| ty.vec3<f32>(), |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| // Vertex shader. |
| { |
| auto* ep = b.Function("vert", str_ty, core::ir::Function::PipelineStage::kVertex); |
| |
| b.Append(ep->Block(), [&] { |
| auto* position = b.Construct(ty.vec4<f32>(), 0_f); |
| auto* color = b.Construct(ty.vec3<f32>(), 1_f); |
| b.Return(ep, b.Construct(str_ty, position, color)); |
| }); |
| } |
| |
| // Fragment shader. |
| { |
| auto* inputs = b.FunctionParam("inputs", str_ty); |
| |
| auto* ep = b.Function("frag", ty.vec4<f32>(), core::ir::Function::PipelineStage::kFragment); |
| ep->SetParams({inputs}); |
| ep->SetReturnLocation(0u); |
| |
| b.Append(ep->Block(), [&] { |
| auto* position = b.Access(ty.vec4<f32>(), inputs, 0_u); |
| auto* color = b.Access(ty.vec3<f32>(), inputs, 1_u); |
| b.Return(ep, b.Add(ty.vec4<f32>(), position, b.Construct(ty.vec4<f32>(), color, 1_f))); |
| }); |
| } |
| |
| auto* src = R"( |
| Interface = struct @align(16) { |
| position:vec4<f32> @offset(0), @builtin(position) |
| color:vec3<f32> @offset(16), @location(0) |
| } |
| |
| %vert = @vertex func():Interface { |
| $B1: { |
| %2:vec4<f32> = construct 0.0f |
| %3:vec3<f32> = construct 1.0f |
| %4:Interface = construct %2, %3 |
| ret %4 |
| } |
| } |
| %frag = @fragment func(%inputs:Interface):vec4<f32> [@location(0)] { |
| $B2: { |
| %7:vec4<f32> = access %inputs, 0u |
| %8:vec3<f32> = access %inputs, 1u |
| %9:vec4<f32> = construct %8, 1.0f |
| %10:vec4<f32> = add %7, %9 |
| ret %10 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Interface = struct @align(16) { |
| position:vec4<f32> @offset(0) |
| color:vec3<f32> @offset(16) |
| } |
| |
| vert_outputs = struct @align(16) { |
| Interface_color:vec3<f32> @offset(0), @location(0) |
| Interface_position:vec4<f32> @offset(16), @builtin(position) |
| } |
| |
| frag_inputs = struct @align(16) { |
| Interface_color:vec3<f32> @offset(0), @location(0) |
| Interface_position:vec4<f32> @offset(16), @builtin(position) |
| } |
| |
| frag_outputs = struct @align(16) { |
| tint_symbol:vec4<f32> @offset(0), @location(0) |
| } |
| |
| %vert_inner = func():Interface { |
| $B1: { |
| %2:vec4<f32> = construct 0.0f |
| %3:vec3<f32> = construct 1.0f |
| %4:Interface = construct %2, %3 |
| ret %4 |
| } |
| } |
| %frag_inner = func(%inputs:Interface):vec4<f32> { |
| $B2: { |
| %7:vec4<f32> = access %inputs, 0u |
| %8:vec3<f32> = access %inputs, 1u |
| %9:vec4<f32> = construct %8, 1.0f |
| %10:vec4<f32> = add %7, %9 |
| ret %10 |
| } |
| } |
| %vert = @vertex func():vert_outputs { |
| $B3: { |
| %12:Interface = call %vert_inner |
| %13:vec4<f32> = access %12, 0u |
| %14:vec3<f32> = access %12, 1u |
| %15:vert_outputs = construct %14, %13 |
| ret %15 |
| } |
| } |
| %frag = @fragment func(%inputs_1:frag_inputs):frag_outputs { # %inputs_1: 'inputs' |
| $B4: { |
| %18:vec4<f32> = access %inputs_1, 1u |
| %19:f32 = access %18, 3u |
| %20:f32 = div 1.0f, %19 |
| %21:vec3<f32> = swizzle %18, xyz |
| %22:vec4<f32> = construct %21, %20 |
| %23:vec3<f32> = access %inputs_1, 0u |
| %24:Interface = construct %22, %23 |
| %25:vec4<f32> = call %frag_inner, %24 |
| %26:frag_outputs = construct %25 |
| ret %26 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOStruct_SharedWithBuffer) { |
| auto* str_ty = ty.Struct(mod.symbols.New("Outputs"), |
| { |
| { |
| mod.symbols.New("position"), |
| ty.vec4<f32>(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kPosition, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("color"), |
| ty.vec3<f32>(), |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* var = b.Var(ty.ptr(storage, str_ty, read)); |
| var->SetBindingPoint(0, 0); |
| |
| auto* buffer = mod.root_block->Append(var); |
| |
| auto* ep = b.Function("vert", str_ty, core::ir::Function::PipelineStage::kVertex); |
| |
| b.Append(ep->Block(), [&] { b.Return(ep, b.Load(buffer)); }); |
| |
| auto* src = R"( |
| Outputs = struct @align(16) { |
| position:vec4<f32> @offset(0), @builtin(position) |
| color:vec3<f32> @offset(16), @location(0) |
| } |
| |
| $B1: { # root |
| %1:ptr<storage, Outputs, read> = var @binding_point(0, 0) |
| } |
| |
| %vert = @vertex func():Outputs { |
| $B2: { |
| %3:Outputs = load %1 |
| ret %3 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Outputs = struct @align(16) { |
| position:vec4<f32> @offset(0) |
| color:vec3<f32> @offset(16) |
| } |
| |
| vert_outputs = struct @align(16) { |
| Outputs_color:vec3<f32> @offset(0), @location(0) |
| Outputs_position:vec4<f32> @offset(16), @builtin(position) |
| } |
| |
| $B1: { # root |
| %1:ptr<storage, Outputs, read> = var @binding_point(0, 0) |
| } |
| |
| %vert_inner = func():Outputs { |
| $B2: { |
| %3:Outputs = load %1 |
| ret %3 |
| } |
| } |
| %vert = @vertex func():vert_outputs { |
| $B3: { |
| %5:Outputs = call %vert_inner |
| %6:vec4<f32> = access %5, 0u |
| %7:vec3<f32> = access %5, 1u |
| %8:vert_outputs = construct %7, %6 |
| ret %8 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| // Test that IO attributes are stripped from structures that are not used for the shader interface. |
| TEST_F(HlslWriterTransformTest, ShaderIOStructWithAttributes_NotUsedForInterface) { |
| auto* vec4f = ty.vec4<f32>(); |
| auto* str_ty = ty.Struct(mod.symbols.New("Outputs"), |
| { |
| { |
| mod.symbols.New("position"), |
| vec4f, |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kPosition, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("color"), |
| vec4f, |
| core::IOAttributes{ |
| /* location */ 0u, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ std::nullopt, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* var = b.Var(ty.ptr(storage, str_ty, read)); |
| var->SetBindingPoint(0, 0); |
| |
| auto* buffer = mod.root_block->Append(var); |
| |
| auto* ep = b.Function("frag", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| |
| b.Append(ep->Block(), [&] { |
| b.Store(buffer, b.Construct(str_ty)); |
| b.Return(ep); |
| }); |
| |
| auto* src = R"( |
| Outputs = struct @align(16) { |
| position:vec4<f32> @offset(0), @builtin(position) |
| color:vec4<f32> @offset(16), @location(0) |
| } |
| |
| $B1: { # root |
| %1:ptr<storage, Outputs, read> = var @binding_point(0, 0) |
| } |
| |
| %frag = @fragment func():void { |
| $B2: { |
| %3:Outputs = construct |
| store %1, %3 |
| ret |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Outputs = struct @align(16) { |
| position:vec4<f32> @offset(0) |
| color:vec4<f32> @offset(16) |
| } |
| |
| $B1: { # root |
| %1:ptr<storage, Outputs, read> = var @binding_point(0, 0) |
| } |
| |
| %frag = @fragment func():void { |
| $B2: { |
| %3:Outputs = construct |
| store %1, %3 |
| ret |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOCompute) { |
| auto* invoc = b.FunctionParam("invoc_id", ty.vec3<u32>()); |
| invoc->SetBuiltin(core::BuiltinValue::kLocalInvocationId); |
| |
| auto* ep = b.Function("cmp", ty.void_(), core::ir::Function::PipelineStage::kCompute); |
| ep->SetParams({invoc}); |
| ep->SetWorkgroupSize(1, 1, 1); |
| |
| b.Append(ep->Block(), [&] { |
| b.Let("a", invoc); |
| b.Return(ep); |
| }); |
| |
| auto* src = R"( |
| %cmp = @compute @workgroup_size(1, 1, 1) func(%invoc_id:vec3<u32> [@local_invocation_id]):void { |
| $B1: { |
| %a:vec3<u32> = let %invoc_id |
| ret |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| cmp_inputs = struct @align(16) { |
| invoc_id:vec3<u32> @offset(0), @builtin(local_invocation_id) |
| } |
| |
| %cmp_inner = func(%invoc_id:vec3<u32>):void { |
| $B1: { |
| %a:vec3<u32> = let %invoc_id |
| ret |
| } |
| } |
| %cmp = @compute @workgroup_size(1, 1, 1) func(%inputs:cmp_inputs):void { |
| $B2: { |
| %6:vec3<u32> = access %inputs, 0u |
| %7:void = call %cmp_inner, %6 |
| ret |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_Subgroup_NonStruct) { |
| auto* subgroup_invocation_id = b.FunctionParam("id", ty.u32()); |
| subgroup_invocation_id->SetBuiltin(core::BuiltinValue::kSubgroupInvocationId); |
| |
| auto* subgroup_size = b.FunctionParam("size", ty.u32()); |
| subgroup_size->SetBuiltin(core::BuiltinValue::kSubgroupSize); |
| |
| auto* ep = b.Function("foo", ty.u32(), core::ir::Function::PipelineStage::kFragment); |
| ep->SetParams({subgroup_invocation_id, subgroup_size}); |
| |
| b.Append(ep->Block(), [&] { |
| auto* r = b.Multiply(ty.u32(), subgroup_invocation_id, subgroup_size); |
| b.Return(ep, r); |
| }); |
| |
| auto* src = R"( |
| %foo = @fragment func(%id:u32 [@subgroup_invocation_id], %size:u32 [@subgroup_size]):u32 { |
| $B1: { |
| %4:u32 = mul %id, %size |
| ret %4 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| foo_outputs = struct @align(4) { |
| tint_symbol:u32 @offset(0) |
| } |
| |
| %foo_inner = func(%id:u32, %size:u32):u32 { |
| $B1: { |
| %4:u32 = mul %id, %size |
| ret %4 |
| } |
| } |
| %foo = @fragment func():foo_outputs { |
| $B2: { |
| %6:u32 = hlsl.WaveGetLaneIndex |
| %7:u32 = hlsl.WaveGetLaneCount |
| %8:u32 = call %foo_inner, %6, %7 |
| %9:foo_outputs = construct %8 |
| ret %9 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_Subgroup_Struct) { |
| auto* str_ty = ty.Struct(mod.symbols.New("Inputs"), |
| { |
| { |
| mod.symbols.New("id"), |
| ty.u32(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kSubgroupInvocationId, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| { |
| mod.symbols.New("size"), |
| ty.u32(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kSubgroupSize, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* str_param = b.FunctionParam("inputs", str_ty); |
| |
| auto* ep = b.Function("foo", ty.u32(), core::ir::Function::PipelineStage::kFragment); |
| ep->SetParams({str_param}); |
| |
| b.Append(ep->Block(), [&] { |
| auto* subgroup_invocation_id = b.Access(ty.u32(), str_param, 0_i); |
| auto* subgroup_size = b.Access(ty.u32(), str_param, 1_i); |
| auto* r = b.Multiply(ty.u32(), subgroup_invocation_id, subgroup_size); |
| b.Return(ep, r); |
| }); |
| |
| auto* src = R"( |
| Inputs = struct @align(4) { |
| id:u32 @offset(0), @builtin(subgroup_invocation_id) |
| size:u32 @offset(4), @builtin(subgroup_size) |
| } |
| |
| %foo = @fragment func(%inputs:Inputs):u32 { |
| $B1: { |
| %3:u32 = access %inputs, 0i |
| %4:u32 = access %inputs, 1i |
| %5:u32 = mul %3, %4 |
| ret %5 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Inputs = struct @align(4) { |
| id:u32 @offset(0) |
| size:u32 @offset(4) |
| } |
| |
| foo_outputs = struct @align(4) { |
| tint_symbol:u32 @offset(0) |
| } |
| |
| %foo_inner = func(%inputs:Inputs):u32 { |
| $B1: { |
| %3:u32 = access %inputs, 0i |
| %4:u32 = access %inputs, 1i |
| %5:u32 = mul %3, %4 |
| ret %5 |
| } |
| } |
| %foo = @fragment func():foo_outputs { |
| $B2: { |
| %7:u32 = hlsl.WaveGetLaneIndex |
| %8:u32 = hlsl.WaveGetLaneCount |
| %9:Inputs = construct %7, %8 |
| %10:u32 = call %foo_inner, %9 |
| %11:foo_outputs = construct %10 |
| ret %11 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_NonStruct) { |
| auto* num_workgroups = b.FunctionParam("num_wgs", ty.vec3<u32>()); |
| num_workgroups->SetBuiltin(core::BuiltinValue::kNumWorkgroups); |
| |
| auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute); |
| ep->SetParams({num_workgroups}); |
| ep->SetWorkgroupSize(1, 1, 1); |
| |
| b.Append(ep->Block(), [&] { |
| auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups); |
| b.Return(ep, r); |
| }); |
| |
| auto* src = R"( |
| %foo = @compute @workgroup_size(1, 1, 1) func(%num_wgs:vec3<u32> [@num_workgroups]):vec3<u32> { |
| $B1: { |
| %3:vec3<u32> = mul %num_wgs, %num_wgs |
| ret %3 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| foo_outputs = struct @align(16) { |
| tint_symbol:vec3<u32> @offset(0) |
| } |
| |
| $B1: { # root |
| %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(0, 0) |
| } |
| |
| %foo_inner = func(%num_wgs:vec3<u32>):vec3<u32> { |
| $B2: { |
| %4:vec3<u32> = mul %num_wgs, %num_wgs |
| ret %4 |
| } |
| } |
| %foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs { |
| $B3: { |
| %6:vec3<u32> = load %tint_num_workgroups |
| %7:vec3<u32> = call %foo_inner, %6 |
| %8:foo_outputs = construct %7 |
| ret %8 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_Struct) { |
| auto* str_ty = ty.Struct(mod.symbols.New("Inputs"), |
| { |
| { |
| mod.symbols.New("num_wgs"), |
| ty.vec3<u32>(), |
| core::IOAttributes{ |
| /* location */ std::nullopt, |
| /* blend_src */ std::nullopt, |
| /* color */ std::nullopt, |
| /* builtin */ core::BuiltinValue::kNumWorkgroups, |
| /* interpolation */ std::nullopt, |
| /* invariant */ false, |
| }, |
| }, |
| }); |
| |
| auto* str_param = b.FunctionParam("inputs", str_ty); |
| |
| auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute); |
| ep->SetParams({str_param}); |
| ep->SetWorkgroupSize(1, 1, 1); |
| |
| b.Append(ep->Block(), [&] { |
| auto* num_workgroups = b.Access(ty.vec3<u32>(), str_param, 0_i); |
| auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups); |
| b.Return(ep, r); |
| }); |
| |
| auto* src = R"( |
| Inputs = struct @align(16) { |
| num_wgs:vec3<u32> @offset(0), @builtin(num_workgroups) |
| } |
| |
| %foo = @compute @workgroup_size(1, 1, 1) func(%inputs:Inputs):vec3<u32> { |
| $B1: { |
| %3:vec3<u32> = access %inputs, 0i |
| %4:vec3<u32> = mul %3, %3 |
| ret %4 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| Inputs = struct @align(16) { |
| num_wgs:vec3<u32> @offset(0) |
| } |
| |
| foo_outputs = struct @align(16) { |
| tint_symbol:vec3<u32> @offset(0) |
| } |
| |
| $B1: { # root |
| %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(0, 0) |
| } |
| |
| %foo_inner = func(%inputs:Inputs):vec3<u32> { |
| $B2: { |
| %4:vec3<u32> = access %inputs, 0i |
| %5:vec3<u32> = mul %4, %4 |
| ret %5 |
| } |
| } |
| %foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs { |
| $B3: { |
| %7:vec3<u32> = load %tint_num_workgroups |
| %8:Inputs = construct %7 |
| %9:vec3<u32> = call %foo_inner, %8 |
| %10:foo_outputs = construct %9 |
| ret %10 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_ExplicitBinding) { |
| auto* num_workgroups = b.FunctionParam("num_wgs", ty.vec3<u32>()); |
| num_workgroups->SetBuiltin(core::BuiltinValue::kNumWorkgroups); |
| |
| auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute); |
| ep->SetParams({num_workgroups}); |
| ep->SetWorkgroupSize(1, 1, 1); |
| |
| b.Append(ep->Block(), [&] { |
| auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups); |
| b.Return(ep, r); |
| }); |
| |
| auto* src = R"( |
| %foo = @compute @workgroup_size(1, 1, 1) func(%num_wgs:vec3<u32> [@num_workgroups]):vec3<u32> { |
| $B1: { |
| %3:vec3<u32> = mul %num_wgs, %num_wgs |
| ret %3 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| foo_outputs = struct @align(16) { |
| tint_symbol:vec3<u32> @offset(0) |
| } |
| |
| $B1: { # root |
| %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(1, 23) |
| } |
| |
| %foo_inner = func(%num_wgs:vec3<u32>):vec3<u32> { |
| $B2: { |
| %4:vec3<u32> = mul %num_wgs, %num_wgs |
| ret %4 |
| } |
| } |
| %foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs { |
| $B3: { |
| %6:vec3<u32> = load %tint_num_workgroups |
| %7:vec3<u32> = call %foo_inner, %6 |
| %8:foo_outputs = construct %7 |
| ret %8 |
| } |
| } |
| )"; |
| |
| ShaderIOConfig config; |
| config.num_workgroups_binding = {1u, 23u}; |
| Run(ShaderIO, config); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_AutoBinding) { |
| auto* num_workgroups = b.FunctionParam("num_wgs", ty.vec3<u32>()); |
| num_workgroups->SetBuiltin(core::BuiltinValue::kNumWorkgroups); |
| |
| auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute); |
| ep->SetParams({num_workgroups}); |
| ep->SetWorkgroupSize(1, 1, 1); |
| |
| b.Append(ep->Block(), [&] { |
| auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups); |
| b.Return(ep, r); |
| }); |
| |
| b.Append(mod.root_block, [&] { |
| for (uint32_t group = 0; group < 10; ++group) { |
| auto* v = b.Var<core::AddressSpace::kStorage, i32>(); |
| v->SetBindingPoint(group, group + 1u); |
| } |
| }); |
| |
| auto* src = R"( |
| $B1: { # root |
| %1:ptr<storage, i32, read_write> = var @binding_point(0, 1) |
| %2:ptr<storage, i32, read_write> = var @binding_point(1, 2) |
| %3:ptr<storage, i32, read_write> = var @binding_point(2, 3) |
| %4:ptr<storage, i32, read_write> = var @binding_point(3, 4) |
| %5:ptr<storage, i32, read_write> = var @binding_point(4, 5) |
| %6:ptr<storage, i32, read_write> = var @binding_point(5, 6) |
| %7:ptr<storage, i32, read_write> = var @binding_point(6, 7) |
| %8:ptr<storage, i32, read_write> = var @binding_point(7, 8) |
| %9:ptr<storage, i32, read_write> = var @binding_point(8, 9) |
| %10:ptr<storage, i32, read_write> = var @binding_point(9, 10) |
| } |
| |
| %foo = @compute @workgroup_size(1, 1, 1) func(%num_wgs:vec3<u32> [@num_workgroups]):vec3<u32> { |
| $B2: { |
| %13:vec3<u32> = mul %num_wgs, %num_wgs |
| ret %13 |
| } |
| } |
| )"; |
| EXPECT_EQ(src, str()); |
| |
| auto* expect = R"( |
| foo_outputs = struct @align(16) { |
| tint_symbol:vec3<u32> @offset(0) |
| } |
| |
| $B1: { # root |
| %1:ptr<storage, i32, read_write> = var @binding_point(0, 1) |
| %2:ptr<storage, i32, read_write> = var @binding_point(1, 2) |
| %3:ptr<storage, i32, read_write> = var @binding_point(2, 3) |
| %4:ptr<storage, i32, read_write> = var @binding_point(3, 4) |
| %5:ptr<storage, i32, read_write> = var @binding_point(4, 5) |
| %6:ptr<storage, i32, read_write> = var @binding_point(5, 6) |
| %7:ptr<storage, i32, read_write> = var @binding_point(6, 7) |
| %8:ptr<storage, i32, read_write> = var @binding_point(7, 8) |
| %9:ptr<storage, i32, read_write> = var @binding_point(8, 9) |
| %10:ptr<storage, i32, read_write> = var @binding_point(9, 10) |
| %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(10, 0) |
| } |
| |
| %foo_inner = func(%num_wgs:vec3<u32>):vec3<u32> { |
| $B2: { |
| %14:vec3<u32> = mul %num_wgs, %num_wgs |
| ret %14 |
| } |
| } |
| %foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs { |
| $B3: { |
| %16:vec3<u32> = load %tint_num_workgroups |
| %17:vec3<u32> = call %foo_inner, %16 |
| %18:foo_outputs = construct %17 |
| ret %18 |
| } |
| } |
| )"; |
| |
| Run(ShaderIO, ShaderIOConfig{}); |
| |
| EXPECT_EQ(expect, str()); |
| } |
| |
| } // namespace |
| } // namespace tint::hlsl::writer::raise |