mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Translate float atomic adds, signed RED offsets, calls and subgroup operations correctly.
Patch 0023 turns a global RED or ATOM .F32 add, which the translator dropped, into a compare-and-swap loop with a float add once GlobalToStorage knows its buffer; the light and fog accumulators of Ocarina use them, and their loss lit Kokiri Forest's fog as if nothing shaded it. Patch 0024 reads RED's 20-bit offset as signed, as ATOM already did. Patch 0025 keeps the block after a CAL as its successor, so code after a call is no longer dropped. Patch 0026 keeps ballot, shuffle and vote operations where they are, so they see every lane. build_xlate.py now expects 25 patches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -99,8 +99,8 @@ def prepare_sources() -> Path:
|
||||
for junk in list(src.glob("src/*/obj")) + list(src.glob("src/*/bin")):
|
||||
shutil.rmtree(junk)
|
||||
patches = sorted((XLATE / "patches").glob("*.patch"))
|
||||
if len(patches) != 21:
|
||||
sys.exit(f"expected 21 patches, found {len(patches)}")
|
||||
if len(patches) != 25:
|
||||
sys.exit(f"expected 25 patches, found {len(patches)}")
|
||||
for patch in patches:
|
||||
# git apply works outside a repository when given a directory; --check first so a failure names the patch.
|
||||
for extra in (["--check"], []):
|
||||
|
||||
48
tools/xlate/patches/0023-float-atomic-add.patch
Normal file
48
tools/xlate/patches/0023-float-atomic-add.patch
Normal file
@@ -0,0 +1,48 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
|
||||
@@ -217,6 +217,13 @@
|
||||
{
|
||||
res = context.AtomicAdd(storageKind, e0, e1, value);
|
||||
}
|
||||
+ else if (type == AtomSize.F32FtzRn && storageKind == StorageKind.GlobalMemory)
|
||||
+ {
|
||||
+ // NX2NAVI: a float add (RED/ATOM .F32.FTZ.RN), which accumulators of light and fog use, was
|
||||
+ // dropped here. It is marked FP32 and becomes a compare-and-swap loop with a float add once
|
||||
+ // GlobalToStorage knows its buffer (TryGenerateStorageOp).
|
||||
+ res = context.Add(Instruction.AtomicAdd | Instruction.FP32, storageKind, Local(), e0, e1, value);
|
||||
+ }
|
||||
else
|
||||
{
|
||||
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
|
||||
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
|
||||
@@ -448,6 +448,13 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
+ // NX2NAVI: a float atomic add has no inline form (the SPIR-V backend only adds integers): it takes the
|
||||
+ // helper path, whose TryGenerateStorageOp writes a compare-and-swap loop
|
||||
+ if (operation.Inst == (Instruction.AtomicAdd | Instruction.FP32))
|
||||
+ {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
return (operation.Inst != Instruction.AtomicMaxS32 &&
|
||||
operation.Inst != Instruction.AtomicMinS32) || targetLanguage == TargetLanguage.Spirv;
|
||||
}
|
||||
@@ -928,6 +935,15 @@
|
||||
case Instruction.AtomicAdd:
|
||||
resultValue = context.AtomicAdd(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
|
||||
break;
|
||||
+ case Instruction.AtomicAdd | Instruction.FP32:
|
||||
+ // NX2NAVI: the word is read, the float sum swapped in if the word is still the one read,
|
||||
+ // and the loop repeats otherwise (InstEmitMemory EmitAtomicOp marks these)
|
||||
+ resultValue = GenerateAtomicCasLoop(context, wordOffset, binding, (memValue) =>
|
||||
+ {
|
||||
+ return context.FPAdd(memValue, value);
|
||||
+ });
|
||||
+
|
||||
+ break;
|
||||
case Instruction.AtomicAnd:
|
||||
resultValue = context.AtomicAnd(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
|
||||
break;
|
||||
15
tools/xlate/patches/0024-red-signed-offset.patch
Normal file
15
tools/xlate/patches/0024-red-signed-offset.patch
Normal file
@@ -0,0 +1,15 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
|
||||
@@ -134,7 +134,11 @@
|
||||
{
|
||||
InstRed op = context.GetOp<InstRed>();
|
||||
|
||||
- (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(op.SrcA, RegisterType.Gpr), op.E, op.Imm20);
|
||||
+ // NX2NAVI: the 20-bit offset is signed, as Atom reads it; unsigned, a reduction at -12 (the culling's group
|
||||
+ // counts written before a record) landed 1 MiB further on
|
||||
+ int sOffset = (op.Imm20 << 12) >> 12;
|
||||
+
|
||||
+ (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(op.SrcA, RegisterType.Gpr), op.E, sOffset);
|
||||
|
||||
EmitAtomicOp(context, StorageKind.GlobalMemory, (AtomOp)op.RedOp, op.RedSize, addrLow, addrHigh, GetDest(op.SrcB));
|
||||
}
|
||||
14
tools/xlate/patches/0025-call-falls-through.patch
Normal file
14
tools/xlate/patches/0025-call-falls-through.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs
|
||||
@@ -500,7 +500,10 @@
|
||||
|
||||
public static bool IsUnconditionalBranch(ref InstOp op)
|
||||
{
|
||||
- return IsUnconditional(ref op) && op.Props.HasFlag(InstProps.Bra);
|
||||
+ // NX2NAVI: a call returns to the next instruction, so the block after a CAL is its successor. Taken for
|
||||
+ // a branch, that block lost its only predecessor and was dropped (AmpereLowering emits CAL for SM86
|
||||
+ // CALL/RET; Ocarina's culling writes its draw lists after one).
|
||||
+ return IsUnconditional(ref op) && op.Props.HasFlag(InstProps.Bra) && op.Name != InstName.Cal;
|
||||
}
|
||||
|
||||
private static bool IsUnconditional(ref InstOp op)
|
||||
19
tools/xlate/patches/0026-subgroup-ops-stay-in-place.patch
Normal file
19
tools/xlate/patches/0026-subgroup-ops-stay-in-place.patch
Normal file
@@ -0,0 +1,19 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs
|
||||
@@ -69,6 +69,16 @@
|
||||
case Instruction.AtomicXor:
|
||||
case Instruction.Call:
|
||||
case Instruction.ImageAtomic:
|
||||
+ // NX2NAVI: a subgroup operation reads the other lanes, so it must stay where it was: moved into a
|
||||
+ // branch it sees only the lanes that took the branch (Ocarina's culling counted 1 of 32)
|
||||
+ case Instruction.Ballot:
|
||||
+ case Instruction.Shuffle:
|
||||
+ case Instruction.ShuffleDown:
|
||||
+ case Instruction.ShuffleUp:
|
||||
+ case Instruction.ShuffleXor:
|
||||
+ case Instruction.VoteAll:
|
||||
+ case Instruction.VoteAllEqual:
|
||||
+ case Instruction.VoteAny:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user