diff --git a/tools/build_xlate.py b/tools/build_xlate.py index 3cfa6b86..f72b82da 100644 --- a/tools/build_xlate.py +++ b/tools/build_xlate.py @@ -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"], []): diff --git a/tools/xlate/patches/0023-float-atomic-add.patch b/tools/xlate/patches/0023-float-atomic-add.patch new file mode 100644 index 00000000..547d5c02 --- /dev/null +++ b/tools/xlate/patches/0023-float-atomic-add.patch @@ -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; diff --git a/tools/xlate/patches/0024-red-signed-offset.patch b/tools/xlate/patches/0024-red-signed-offset.patch new file mode 100644 index 00000000..47b318cf --- /dev/null +++ b/tools/xlate/patches/0024-red-signed-offset.patch @@ -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(); + +- (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)); + } diff --git a/tools/xlate/patches/0025-call-falls-through.patch b/tools/xlate/patches/0025-call-falls-through.patch new file mode 100644 index 00000000..f987bfa1 --- /dev/null +++ b/tools/xlate/patches/0025-call-falls-through.patch @@ -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) diff --git a/tools/xlate/patches/0026-subgroup-ops-stay-in-place.patch b/tools/xlate/patches/0026-subgroup-ops-stay-in-place.patch new file mode 100644 index 00000000..ad361d7d --- /dev/null +++ b/tools/xlate/patches/0026-subgroup-ops-stay-in-place.patch @@ -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; + } + }