diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc index b668f2e6a8..95dd6feb66 100644 --- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc +++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc @@ -121,6 +121,15 @@ struct State { worklist.Push(builtin); } break; + case core::BuiltinFn::kReflect: + if (config.reflect_vec2_f32) { + // Polyfill for vec2. See crbug.com/tint/1798 + auto* vec_ty = builtin->Result(0)->Type()->As(); + if (vec_ty->Width() == 2 && vec_ty->Type()->Is()) { + worklist.Push(builtin); + } + } + break; case core::BuiltinFn::kSaturate: if (config.saturate) { worklist.Push(builtin); @@ -201,6 +210,9 @@ struct State { case core::BuiltinFn::kRadians: Radians(builtin); break; + case core::BuiltinFn::kReflect: + Reflect(builtin); + break; case core::BuiltinFn::kSaturate: Saturate(builtin); break; @@ -670,6 +682,35 @@ struct State { call->Destroy(); } + /// Polyfill a `reflect()` builtin call. + /// @param call the builtin call instruction + void Reflect(ir::CoreBuiltinCall* call) { + auto* e1 = call->Args()[0]; + auto* e2 = call->Args()[1]; + auto* vec_ty = e1->Type()->As(); + // Only polyfills vec2 (crbug.com/tint/1798) + TINT_ASSERT(vec_ty && vec_ty->Width() == 2 && vec_ty->Type()->Is()); + + b.InsertBefore(call, [&] { + // The generated HLSL must effectively be emitted as: + // e1 + (-2 * dot(e1,e2) * e2) + // Rather than the mathemetically equivalent: + // e1 - 2 * dot(e2,e2) * e2 + // + // When FXC compiles HLSL reflect, or the second case above, + // it emits a `dp4` instruction for `2 * dot(e1,e2)`, which is + // miscompiled by certain Intel drivers. The workaround (first + // case above) results in FXC emitting a `dp2` for the dot, + // followed by a `mul 2`, which works around the bug. + auto* dot = b.Call(ty.f32(), core::BuiltinFn::kDot, e1, e2); + auto* factor = b.Multiply(ty.f32(), -2.0_f, dot); + auto* vfactor = b.Construct(vec_ty, factor); + auto* mul = b.Multiply(vec_ty, vfactor, e2); + b.AddWithResult(call->DetachResult(), e1, mul); + }); + call->Destroy(); + } + /// Polyfill a `saturate()` builtin call. /// @param call the builtin call instruction void Saturate(ir::CoreBuiltinCall* call) { diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.h b/src/tint/lang/core/ir/transform/builtin_polyfill.h index acab62aa1e..c6c6dc6718 100644 --- a/src/tint/lang/core/ir/transform/builtin_polyfill.h +++ b/src/tint/lang/core/ir/transform/builtin_polyfill.h @@ -72,6 +72,8 @@ struct BuiltinPolyfillConfig { BuiltinPolyfillLevel insert_bits = BuiltinPolyfillLevel::kNone; /// Should `radians()` be polyfilled? bool radians = false; + /// Should `reflect()` be polyfilled for vec2? + bool reflect_vec2_f32 = false; /// Should `saturate()` be polyfilled? bool saturate = false; /// Should `textureSampleBaseClampToEdge()` be polyfilled for texture_2d textures? diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc index 6dfbd61110..7a4091b1ca 100644 --- a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc +++ b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc @@ -2454,5 +2454,157 @@ TEST_F(IR_BuiltinPolyfillTest, Dot4U8Packed) { EXPECT_EQ(expect, str()); } +TEST_F(IR_BuiltinPolyfillTest, Reflect_NoPolyfill) { + Build(core::BuiltinFn::kReflect, ty.vec2(), Vector{ty.vec2(), ty.vec2()}); + auto* src = R"( +%foo = func(%arg:vec2, %arg_1:vec2):vec2 { # %arg_1: 'arg' + $B1: { + %result:vec2 = reflect %arg, %arg_1 + ret %result + } +} +)"; + auto* expect = src; + + EXPECT_EQ(src, str()); + + BuiltinPolyfillConfig config; + config.reflect_vec2_f32 = false; + Run(BuiltinPolyfill, config); + EXPECT_EQ(expect, str()); +} + +// Reflect polyfill (reflect_vec2_f32) should only affect vec2 +TEST_F(IR_BuiltinPolyfillTest, Reflect_Vec2F32) { + Build(core::BuiltinFn::kReflect, ty.vec2(), Vector{ty.vec2(), ty.vec2()}); + auto* src = R"( +%foo = func(%arg:vec2, %arg_1:vec2):vec2 { # %arg_1: 'arg' + $B1: { + %result:vec2 = reflect %arg, %arg_1 + ret %result + } +} +)"; + auto* expect = R"( +%foo = func(%arg:vec2, %arg_1:vec2):vec2 { # %arg_1: 'arg' + $B1: { + %4:f32 = dot %arg, %arg_1 + %5:f32 = mul -2.0f, %4 + %6:vec2 = construct %5 + %7:vec2 = mul %6, %arg_1 + %result:vec2 = add %arg, %7 + ret %result + } +} +)"; + + EXPECT_EQ(src, str()); + + BuiltinPolyfillConfig config; + config.reflect_vec2_f32 = true; + Run(BuiltinPolyfill, config); + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_BuiltinPolyfillTest, Reflect_Vec3F32) { + Build(core::BuiltinFn::kReflect, ty.vec3(), Vector{ty.vec3(), ty.vec3()}); + auto* src = R"( +%foo = func(%arg:vec3, %arg_1:vec3):vec3 { # %arg_1: 'arg' + $B1: { + %result:vec3 = reflect %arg, %arg_1 + ret %result + } +} +)"; + auto* expect = src; + + EXPECT_EQ(src, str()); + + BuiltinPolyfillConfig config; + config.reflect_vec2_f32 = true; + Run(BuiltinPolyfill, config); + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_BuiltinPolyfillTest, Reflect_Vec4F32) { + Build(core::BuiltinFn::kReflect, ty.vec4(), Vector{ty.vec4(), ty.vec4()}); + auto* src = R"( +%foo = func(%arg:vec4, %arg_1:vec4):vec4 { # %arg_1: 'arg' + $B1: { + %result:vec4 = reflect %arg, %arg_1 + ret %result + } +} +)"; + auto* expect = src; + + EXPECT_EQ(src, str()); + + BuiltinPolyfillConfig config; + config.reflect_vec2_f32 = true; + Run(BuiltinPolyfill, config); + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_BuiltinPolyfillTest, Reflect_Vec2F16) { + Build(core::BuiltinFn::kReflect, ty.vec2(), Vector{ty.vec2(), ty.vec2()}); + auto* src = R"( +%foo = func(%arg:vec2, %arg_1:vec2):vec2 { # %arg_1: 'arg' + $B1: { + %result:vec2 = reflect %arg, %arg_1 + ret %result + } +} +)"; + auto* expect = src; + + EXPECT_EQ(src, str()); + + BuiltinPolyfillConfig config; + config.reflect_vec2_f32 = true; + Run(BuiltinPolyfill, config); + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_BuiltinPolyfillTest, Reflect_Vec3F16) { + Build(core::BuiltinFn::kReflect, ty.vec3(), Vector{ty.vec3(), ty.vec3()}); + auto* src = R"( +%foo = func(%arg:vec3, %arg_1:vec3):vec3 { # %arg_1: 'arg' + $B1: { + %result:vec3 = reflect %arg, %arg_1 + ret %result + } +} +)"; + auto* expect = src; + + EXPECT_EQ(src, str()); + + BuiltinPolyfillConfig config; + config.reflect_vec2_f32 = true; + Run(BuiltinPolyfill, config); + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_BuiltinPolyfillTest, Reflect_Vec4F16) { + Build(core::BuiltinFn::kReflect, ty.vec4(), Vector{ty.vec4(), ty.vec4()}); + auto* src = R"( +%foo = func(%arg:vec4, %arg_1:vec4):vec4 { # %arg_1: 'arg' + $B1: { + %result:vec4 = reflect %arg, %arg_1 + ret %result + } +} +)"; + auto* expect = src; + + EXPECT_EQ(src, str()); + + BuiltinPolyfillConfig config; + config.reflect_vec2_f32 = true; + Run(BuiltinPolyfill, config); + EXPECT_EQ(expect, str()); +} + } // namespace } // namespace tint::core::ir::transform diff --git a/src/tint/lang/hlsl/writer/builtin_test.cc b/src/tint/lang/hlsl/writer/builtin_test.cc index 25ade75258..6d08881942 100644 --- a/src/tint/lang/hlsl/writer/builtin_test.cc +++ b/src/tint/lang/hlsl/writer/builtin_test.cc @@ -4443,5 +4443,60 @@ void foo() { )"); } +TEST_F(HlslWriterTest, BuiltinReflect_Vec2f32_NoPolyfill) { + auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(func->Block(), [&] { + auto* vec_ty = ty.vec2(); + auto* x = b.Let("x", b.MatchWidth(1_f, vec_ty)); + auto* y = b.Let("y", b.MatchWidth(2_f, vec_ty)); + + auto* c = b.Call(vec_ty, core::BuiltinFn::kReflect, x, y); + b.Let("w", c); + b.Return(func); + }); + + tint::hlsl::writer::Options options; + options.polyfill_reflect_vec2_f32 = false; + ASSERT_TRUE(Generate(options)) << err_ << output_.hlsl; + EXPECT_EQ(output_.hlsl, R"( +void foo() { + float2 x = (1.0f).xx; + float2 y = (2.0f).xx; + float2 w = reflect(x, y); +} + +)"); +} + +// The generated HLSL must effectively be emitted as: +// x + (-2.0 * dot(x,y) * y) +// Rather than: +// x - 2.0 * dot(x,y) * y +// See crbug.com/tint/1798 +TEST_F(HlslWriterTest, BuiltinReflect_Vec2f32_Polyfill) { + auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(func->Block(), [&] { + auto* vec_ty = ty.vec2(); + auto* x = b.Let("x", b.MatchWidth(1_f, vec_ty)); + auto* y = b.Let("y", b.MatchWidth(2_f, vec_ty)); + + auto* c = b.Call(vec_ty, core::BuiltinFn::kReflect, x, y); + b.Let("w", c); + b.Return(func); + }); + + tint::hlsl::writer::Options options; + options.polyfill_reflect_vec2_f32 = true; + ASSERT_TRUE(Generate(options)) << err_ << output_.hlsl; + EXPECT_EQ(output_.hlsl, R"( +void foo() { + float2 x = (1.0f).xx; + float2 y = (2.0f).xx; + float2 w = (x + (float2(((-2.0f * dot(x, y))).xx) * y)); +} + +)"); +} + } // namespace } // namespace tint::hlsl::writer diff --git a/src/tint/lang/hlsl/writer/raise/raise.cc b/src/tint/lang/hlsl/writer/raise/raise.cc index 5778fdf3b4..279a37a6d8 100644 --- a/src/tint/lang/hlsl/writer/raise/raise.cc +++ b/src/tint/lang/hlsl/writer/raise/raise.cc @@ -109,8 +109,7 @@ Result Raise(core::ir::Module& module, const Options& options) { core_polyfills.pack_4xu8_clamp = true; core_polyfills.pack_unpack_4x8 = options.polyfill_pack_unpack_4x8; core_polyfills.radians = true; - // TODO(crbug.com/377357960): Implement reflect_vec2_f32 - // core_polyfills.reflect_vec2_f32 = options.polyfill_reflect_vec2_f32; + core_polyfills.reflect_vec2_f32 = options.polyfill_reflect_vec2_f32; core_polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true; RUN_TRANSFORM(core::ir::transform::BuiltinPolyfill, module, core_polyfills); }