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 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-07 03:57:39 -05:00
parent 99de7b943f
commit f76d896ed5
3 changed files with 69 additions and 5 deletions

View File

@@ -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 <key>.job (one request line) with its code, to run the
// translator on by hand (nx2navi_xlate --serve < <key>.job)
// translator on by hand (nx2navi_xlate --serve < <key>.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);

View File

@@ -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"], []):

View File

@@ -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<int, int> _sbSlots;
private readonly Dictionary<int, int> _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);
}
}