From 1dc99ba0a2c332285ed8d1d04049288501b1d43a Mon Sep 17 00:00:00 2001 From: Karan Vora <52519664+karanvora2599@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:23:47 -0400 Subject: [PATCH] perf(shader): drop the input flush that rcp, rsq, sqrt, exp and log results never use The SPIR-V emitters of FPRecip32, FPRecipSqrt32, FPSqrt, FPExp2 and FPLog2 flushed a denormal input to its signed zero before the host operation. Since #537, unaryFloatSpecials replaces the host result for every input below 2^-126 with the hardware's result for a zero of the same sign: rcp and rsq give a signed infinity, sqrt the signed zero, log -inf and exp 1.0. floatUnary and float16Unary both go through it, so the flushed input could only change a value that is then discarded. v_rcp_iflag_f32 gets the same special values and already went without the flush. The pixel prologue computes PositionW as FPRecip32 of FragCoord.w without those special values, so it now flushes FragCoord.w in the IR first and gives the same result as before. --- .../SpirvBackend/SpirvEmitterHelpers.hpp | 1 - .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 14 ++++++------- .../SpirvBackend/src/SpirvModuleEmitter.cpp | 20 ------------------- .../Translation/src/InstructionTranslator.cpp | 5 +++-- 4 files changed, 9 insertions(+), 31 deletions(-) diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp index eceb3b3d6..517c77771 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp @@ -131,7 +131,6 @@ F32Class EmitClassifyF32(SpirvEmitterState& state, std::uint32_t value); std::uint32_t EmitClassMaskBitMatch(SpirvEmitterState& state, std::uint32_t mask, std::uint32_t bit, std::uint32_t classMatch); std::uint32_t EmitClassMaskF32(SpirvEmitterState& state, std::uint32_t value, std::uint32_t mask); std::uint32_t EmitMinMaxF32Value(SpirvEmitterState& state, std::uint32_t lhs, std::uint32_t rhs, bool maxValue); -std::uint32_t EmitFlushF32DenormToSignedZero(SpirvEmitterState& state, std::uint32_t value); std::uint32_t EmitTrigCycleF32(SpirvEmitterState& state, std::uint32_t src, bool preserveSignedZero); std::uint32_t EmitF16BitsToF32(SpirvEmitterState& state, std::uint32_t bits); void EmitProgram(SpirvEmitterState& state); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 9936d3199..66af8ee29 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -842,8 +842,7 @@ std::uint32_t EmitFPMedTri32(SpirvEmitterState& state, std::uint32_t arg0, std:: } std::uint32_t EmitFPRecip32(SpirvEmitterState& state, std::uint32_t arg0) { - const auto source = EmitFlushF32DenormToSignedZero(state, arg0); - return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), source); + return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), arg0); } std::uint32_t EmitFPRecipIFlag32(SpirvEmitterState& state, std::uint32_t arg0) { @@ -851,15 +850,15 @@ std::uint32_t EmitFPRecipIFlag32(SpirvEmitterState& state, std::uint32_t arg0) { } std::uint32_t EmitFPRecipSqrt32(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitExt(state, TypeF32(state), GLSLstd450InverseSqrt, {EmitFlushF32DenormToSignedZero(state, arg0)}); + return EmitExt(state, TypeF32(state), GLSLstd450InverseSqrt, {arg0}); } std::uint32_t EmitFPSqrt(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitExt(state, TypeF32(state), GLSLstd450Sqrt, {EmitFlushF32DenormToSignedZero(state, arg0)}); + return EmitExt(state, TypeF32(state), GLSLstd450Sqrt, {arg0}); } std::uint32_t EmitFPExp2(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitExt(state, TypeF32(state), GLSLstd450Exp2, {EmitFlushF32DenormToSignedZero(state, arg0)}); + return EmitExt(state, TypeF32(state), GLSLstd450Exp2, {arg0}); } std::uint32_t Log2SeriesNearOne(SpirvEmitterState& state, std::uint32_t difference) { @@ -872,9 +871,8 @@ std::uint32_t Log2SeriesNearOne(SpirvEmitterState& state, std::uint32_t differen } std::uint32_t EmitFPLog2(SpirvEmitterState& state, std::uint32_t arg0) { - const auto source = EmitFlushF32DenormToSignedZero(state, arg0); - const auto value = EmitExt(state, TypeF32(state), GLSLstd450Log2, {source}); - const auto difference = Exact(state, Binary(state, spv::OpFSub, TypeF32(state), source, ConstantF32(state, 0x3f800000u))); + const auto value = EmitExt(state, TypeF32(state), GLSLstd450Log2, {arg0}); + const auto difference = Exact(state, Binary(state, spv::OpFSub, TypeF32(state), arg0, ConstantF32(state, 0x3f800000u))); const auto series = Log2SeriesNearOne(state, difference); const auto valueBits = Unary(state, spv::OpBitcast, TypeU32(state), value); const auto seriesBits = Unary(state, spv::OpBitcast, TypeU32(state), series); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 236724c00..b131b685e 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -940,26 +940,6 @@ std::uint32_t EmitMinMaxF32Value(SpirvEmitterState& state, std::uint32_t lhs, st return Unary(state, spv::OpBitcast, TypeF32(state), resultBits); } -std::uint32_t EmitFlushF32DenormToSignedZero(SpirvEmitterState& state, std::uint32_t value) { - const auto bits = state.module.AllocateId(); - const auto absBits = state.module.AllocateId(); - const auto signBits = state.module.AllocateId(); - const auto nonZero = state.module.AllocateId(); - const auto subnormal = state.module.AllocateId(); - const auto flush = state.module.AllocateId(); - const auto selected = state.module.AllocateId(); - const auto result = state.module.AllocateId(); - state.module.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); - state.module.AddFunction(spv::OpBitwiseAnd, TypeU32(state), absBits, bits, ConstantU32(state, 0x7fffffffu)); - state.module.AddFunction(spv::OpBitwiseAnd, TypeU32(state), signBits, bits, ConstantU32(state, 0x80000000u)); - state.module.AddFunction(spv::OpINotEqual, TypeBool(state), nonZero, absBits, ConstantU32(state, 0u)); - state.module.AddFunction(spv::OpULessThan, TypeBool(state), subnormal, absBits, ConstantU32(state, 0x00800000u)); - state.module.AddFunction(spv::OpLogicalAnd, TypeBool(state), flush, nonZero, subnormal); - state.module.AddFunction(spv::OpSelect, TypeU32(state), selected, flush, signBits, bits); - state.module.AddFunction(spv::OpBitcast, TypeF32(state), result, selected); - return result; -} - std::uint32_t EmitTrigCycleF32(SpirvEmitterState& state, std::uint32_t src, bool preserveSignedZero) { const auto rounded = state.module.AllocateId(); const auto reduced = state.module.AllocateId(); diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index e3455f44e..11b1ba5db 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -356,8 +356,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO setVector(static_cast(vgpr(PixelInput::PositionZ)), builtin(StageInputKind::FragCoord, 2u)); } if (loaded(PixelInput::PositionW)) { - IrValue& reciprocalW = entryIr.BitCastF32(builtin(StageInputKind::FragCoord, 3u)); - IrValue& w = entryIr.Emit(IrOpcode::FPRecip32, IrOpcodeType(IrOpcode::FPRecip32), {&reciprocalW}); + IrValue& reciprocalBits = builtin(StageInputKind::FragCoord, 3u); + IrValue& flushed = entryIr.Select(entryIr.IEqual(entryIr.BitwiseAnd(reciprocalBits, entryIr.Constant(0x7f800000u)), entryIr.Constant(0u)), entryIr.BitwiseAnd(reciprocalBits, entryIr.Constant(0x80000000u)), reciprocalBits); + IrValue& w = entryIr.Emit(IrOpcode::FPRecip32, IrOpcodeType(IrOpcode::FPRecip32), {&entryIr.BitCastF32(flushed)}); setVector(static_cast(vgpr(PixelInput::PositionW)), entryIr.BitCastU32(w)); } if (loaded(PixelInput::FrontFace)) {