From f76d896ed5d8cd4a671a0c57266f5ab1d419b07f Mon Sep 17 00:00:00 2001 From: chrg6 Date: Sat, 7 Nov 2026 03:57:39 -0500 Subject: [PATCH] Give the translator 64 storage buffer slots and keep it converting a block after a store it cannot place. Ocarina's NVN2 compute programs reach more than 16 buffers through their pushed pointers and pointer tables; past the 16th the translator found no slot, dropped the store, and its loop then read the next node of a node it had just removed, so every later global access in the block stayed global and the whole program failed with "Invalid storage kind GlobalMemory". Ten compute programs of Kokiri Forest were skipped on every dispatch and retried on every one, which also cost 10-25 ms frames. Now one program still fails (an Ampere MATCH instruction the lowering does not handle). NX2NAVI_XLATE_KEEP=2 keeps every translated job with its code for running the translator by hand. Co-Authored-By: Claude Opus 5.5 --- runtime/gfx_shader.c | 10 +++- tools/build_xlate.py | 4 +- .../0029-more-storage-buffer-slots.patch | 60 +++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 tools/xlate/patches/0029-more-storage-buffer-slots.patch diff --git a/runtime/gfx_shader.c b/runtime/gfx_shader.c index 3628ce27..85fa0571 100644 --- a/runtime/gfx_shader.c +++ b/runtime/gfx_shader.c @@ -1485,10 +1485,14 @@ static const GfxShader *translate_one(const Req *r, int stage, const uint8_t *co build_request(req, hex, r->stage_name, code_path, r->c1, r->c1_len, a, control); int ok = svc_request(req, reply, sizeof reply); // NX2NAVI_XLATE_KEEP=1: a last-frame variant's job stays as .job (one request line) with its code, to run the - // translator on by hand (nx2navi_xlate --serve < .job) + // translator on by hand (nx2navi_xlate --serve < .job); 2 keeps every job translated in this run (delete a + // key's .res.json and .spv from the cache to have it translated, and kept, again) static int keep = -1; - if (keep < 0) keep = rt_knob("XLATE_KEEP", "0"); - int kept = keep && a && a->mvprev; + if (keep < 0) { + const char *k = cfg_get("NX2NAVI_XLATE_KEEP"); + keep = k && *k ? atoi(k) : 0; + } + int kept = keep >= 2 || (keep && a && a->mvprev); if (kept) { char job_path[PATH_CAP + 80]; snprintf(job_path, sizeof job_path, "%s/%s.job", cache_dir, hex); diff --git a/tools/build_xlate.py b/tools/build_xlate.py index b4655775..2e0768f1 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) != 27: - sys.exit(f"expected 27 patches, found {len(patches)}") + if len(patches) != 28: + sys.exit(f"expected 28 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/0029-more-storage-buffer-slots.patch b/tools/xlate/patches/0029-more-storage-buffer-slots.patch new file mode 100644 index 00000000..1a5191a0 --- /dev/null +++ b/tools/xlate/patches/0029-more-storage-buffer-slots.patch @@ -0,0 +1,60 @@ +--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs ++++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs +@@ -281,7 +281,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations + } + else + { +- Utils.DeleteNode(node, operation); ++ // NX2NAVI: the store becomes an unused copy in place instead of leaving the list: the ++ // loop goes on from node.Next, which a removed node no longer has, so every global ++ // access after it in the block stayed global and failed the whole translation. ++ for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) ++ { ++ operation.SetSource(srcIndex, null); ++ } ++ ++ node.Value = new Operation(Instruction.Copy, Local(), Const(0)); + } + } + else +--- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs ++++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs +@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.Translation + + private readonly SetBindingPair[] _cbSlotToBindingMap; + private readonly SetBindingPair[] _sbSlotToBindingMap; +- private uint _sbSlotWritten; ++ private ulong _sbSlotWritten; + + private readonly Dictionary _sbSlots; + private readonly Dictionary _sbSlotsReverse; +@@ -66,7 +66,10 @@ namespace Ryujinx.Graphics.Shader.Translation + _stagePrefix = GetShaderStagePrefix(stage); + + _cbSlotToBindingMap = new SetBindingPair[32]; // NX2NAVI: slots 18-31 for Transforms/MotionPrevious.cs +- _sbSlotToBindingMap = new SetBindingPair[16]; ++ // NX2NAVI: 64 storage buffer slots (the width of _sbSlotWritten), not 16: Ocarina's NVN2 compute programs ++ // reach more than 16 buffers through their pushed pointers and pointer tables, and every access to a buffer ++ // past the 16th found no slot. ++ _sbSlotToBindingMap = new SetBindingPair[64]; + _cbSlotToBindingMap.AsSpan().Fill(new(-1, -1)); + _sbSlotToBindingMap.AsSpan().Fill(new(-1, -1)); + +@@ -193,7 +196,7 @@ namespace Ryujinx.Graphics.Shader.Translation + + if (write) + { +- _sbSlotWritten |= 1u << slot; ++ _sbSlotWritten |= 1ul << slot; + } + + binding = setAndBinding.Binding; +@@ -503,7 +506,7 @@ namespace Ryujinx.Graphics.Shader.Translation + if (setAndBinding.Binding >= 0) + { + (int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key); +- BufferUsageFlags flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None; ++ BufferUsageFlags flags = (_sbSlotWritten & (1ul << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None; + descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot, sbCbSlot, sbCbOffset, flags); + } + }