From 539baa66e7cba91c3c274db0f575d272329141aa Mon Sep 17 00:00:00 2001 From: kuba Date: Fri, 31 Jul 2026 11:09:41 +0200 Subject: [PATCH] Apply SDWA ABS/NEG as float sign-bit, not integer, modifiers (#713) SDWA's ABS and NEG source modifiers are floating-point sign-bit operations on GCN: ABS clears the sign bit, NEG flips it. We applied them as integer operations instead - SAbs, and a two's-complement negate of the raw bit pattern. That turns 1.0 into -4.0 and -3.0 into 1.5. UE4 compiles the final line of DrawRectangle, OutPosition.xy *= float2(1,-1), into a single V_MOV_B32 with SDWA NEG, so every UE fullscreen pass had its clip-space Y silently skewed. The canonical fullscreen triangle (1,-1) (-3,-1) (1,3) became (1,-4) (-3,-4) (1,1.5), which covers 6/11 of the viewport instead of all of it. That reproduces the measured defect exactly, on four independent quantities: hypotenuse slope 8/11, crossings of y=+1 and y=-1 at x=+7/11 and x=-9/11, and covered area 6/11 = 54.55% of the 2304x1296 viewport. It also explains why the edge was resolution-independent and identical across six unrelated shaders - it is the same instruction in every one of them. The distinguishing evidence is the transform's fixed point. A wrong scale would hold NDC -1 in x and +1 in y; the observed transform holds the opposite corner in both. Independently, and using only the measured line rather than any assumed vertex position: a wrong multiplier alone leaves a residual of -18 whatever the multiplier, and a wrong addend alone forces slope 1, not 8/11. Both terms had to be wrong at once, which only a Y-only sign-bit corruption produces. Float instructions are unaffected: GetFloatSource passes applySdwaIntegerModifiers: false and applies its own modifiers, so this path only ever fed raw-source reads - where the hardware behaviour is the sign-bit one regardless of the opcode being a bit-move. The sign bit is selected by the SDWA source-select width so a 16-bit select flips bit 15 rather than bit 31. Verified: emitted SPIR-V for the same shader changes from OpISub %uint %uint_0 %2147 to OpBitwiseXor %uint %2147 %uint_2147483648; 25-program synthetic conformance gate passes; 805 tests green; three 100-110s live runs with no crashes and no new shader failures. The end-to-end pixel re-measurement is NOT yet closed - see task #26. --- .../Gen5SpirvTranslator.Alu.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs index 771eb4e4..6c692696 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs @@ -2706,20 +2706,31 @@ public static partial class Gen5SpirvTranslator if (applySdwaIntegerModifiers) { + // SDWA ABS/NEG are floating-point sign-bit modifiers even on + // a bit-move opcode: ABS clears the sign bit, NEG flips it. + // Two's-complement negating the raw bits instead turns 1.0 + // into -4.0 and -3.0 into 1.5, which silently skews every + // pass that y-flips its clip position with an SDWA-negated + // V_MOV_B32 - the whole of UE's DrawRectangle. + var signBit = selector switch + { + <= 3 => 0x80u, + 4 or 5 => 0x8000u, + _ => 0x80000000u, + }; + if ((sdwa.AbsoluteMask & (1u << sourceIndex)) != 0) { - value = Bitcast( - _uintType, - Ext(5, _intType, Bitcast(_intType, value))); + value = BitwiseAnd(value, UInt(~signBit)); } if ((sdwa.NegateMask & (1u << sourceIndex)) != 0) { value = _module.AddInstruction( - SpirvOp.ISub, + SpirvOp.BitwiseXor, _uintType, - UInt(0), - value); + value, + UInt(signBit)); } } }