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.
This commit is contained in:
kuba
2026-07-31 11:09:41 +02:00
committed by GitHub
parent b572738547
commit 539baa66e7
@@ -2706,20 +2706,31 @@ public static partial class Gen5SpirvTranslator
if (applySdwaIntegerModifiers) 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) if ((sdwa.AbsoluteMask & (1u << sourceIndex)) != 0)
{ {
value = Bitcast( value = BitwiseAnd(value, UInt(~signBit));
_uintType,
Ext(5, _intType, Bitcast(_intType, value)));
} }
if ((sdwa.NegateMask & (1u << sourceIndex)) != 0) if ((sdwa.NegateMask & (1u << sourceIndex)) != 0)
{ {
value = _module.AddInstruction( value = _module.AddInstruction(
SpirvOp.ISub, SpirvOp.BitwiseXor,
_uintType, _uintType,
UInt(0), value,
value); UInt(signBit));
} }
} }
} }