// SPDX-License-Identifier: MIT // Copyright (c) 2026 The Roofer Dev - Beast Roofer Edition. Clean-room integration code. // Built on Ryujinx (MIT). DLSS, DLAA and NIS are NVIDIA technologies; this is integration code only. using Ryujinx.Common.Logging; using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Engine.Threed { /// /// MV++ Phase 0 discovery probe (RYUJINX_MVPP_PROBE=1). Once every couple of seconds, on a /// draw of the scaled 3D pass, scans the bound graphics uniform buffers for 4x4-matrix-shaped /// float windows and fingerprints each candidate. Only candidates whose CONTENT CHANGED since /// the previous sample are logged, so the in-game protocol is: hold the camera still (expect /// silence), then pan it (the candidates that light up are camera-dependent - the /// view/view-projection matrices MV++ needs). Read-only; completely off without the env var. /// static class MvppProbe { private static bool _enabled = Environment.GetEnvironmentVariable("RYUJINX_MVPP_PROBE") == "1"; // Dump mode (RYUJINX_MVPP_DUMP=): instead of scanning, print the first 512 bytes // of stage-0 cbuf as float rows every sample, to map the block's exact layout // (which matrix is view / proj / view-proj / inverses / previous frame). private static readonly int _dumpSlot = int.TryParse(Environment.GetEnvironmentVariable("RYUJINX_MVPP_DUMP"), out int s) ? s : -1; private const int SampleIntervalMs = 2000; private const int MaxBytesPerBuffer = 4096; private const int MaxLoggedPerSample = 12; private static long _lastSampleMs; private static bool _readFailedLogged; private static readonly Dictionary<(int Stage, int Slot, int Offset), uint> _lastFingerprint = new(); public static void OnDraw(GpuChannel channel) { if (!_enabled) { return; } // A diagnostic probe must never take the process down: it runs on the GPU thread, // where an escaped exception is fatal and unlogged (crash of 2026-07-02 08:22, // WER e0434352 with a silent Ryujinx log). On any error: disable and tell why. try { OnDrawImpl(channel); } catch (Exception e) { _enabled = false; Logger.Warning?.Print(LogClass.Gpu, $"MVPP probe: disabled after unexpected error: {e}"); } } private static void OnDrawImpl(GpuChannel channel) { // The main 3D scene pass is the scaled one; UI/native passes stay at 1x. if (channel.TextureManager.RenderTargetScale == 1f) { return; } long now = Environment.TickCount64; if (now - _lastSampleMs < SampleIntervalMs) { return; } _lastSampleMs = now; if (_dumpSlot >= 0) { DumpCbuf(channel, _dumpSlot); return; } int candidates = 0; int changed = 0; int logged = 0; for (int stage = 0; stage < Constants.ShaderStages; stage++) { uint mask = channel.BufferManager.GetGraphicsUniformBufferUseMask(stage); for (int slot = 0; mask != 0; slot++, mask >>= 1) { if ((mask & 1) == 0) { continue; } // The bound ranges are already TRANSLATED: physical addresses, not GPU VAs // (SetGraphicsUniformBuffer runs TranslateAndCreateBuffer). Read physical. ulong address = channel.BufferManager.GetGraphicsUniformBufferAddress(stage, slot); int size = Math.Min(channel.BufferManager.GetGraphicsUniformBufferSize(stage, slot), MaxBytesPerBuffer); if (address == 0 || address == ulong.MaxValue || size < 64) { continue; } ReadOnlySpan data; try { data = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(address, size)); } catch (Exception e) { if (!_readFailedLogged) { _readFailedLogged = true; Logger.Warning?.Print(LogClass.Gpu, $"MVPP probe: cbuf read failed (stage{stage} cbuf{slot} @0x{address:X}): {e.GetType().Name} (logged once)."); } continue; } // 16-byte aligned windows of 16 floats. for (int i = 0; i + 16 <= data.Length; i += 4) { ReadOnlySpan window = data.Slice(i, 16); if (!LooksLikeMat4(window)) { continue; } candidates++; uint fp = Fingerprint(window); (int, int, int) key = (stage, slot, i * 4); if (_lastFingerprint.TryGetValue(key, out uint prev) && prev != fp) { changed++; if (logged < MaxLoggedPerSample) { logged++; Logger.Info?.Print(LogClass.Gpu, $"MVPP probe CHANGED: stage{stage} cbuf{slot} +0x{i * 4:X3} " + $"r0=[{window[0]:0.###} {window[1]:0.###} {window[2]:0.###} {window[3]:0.###}] fp={fp:X8}"); } } _lastFingerprint[key] = fp; i += 12; // jump past this matrix (loop adds 4 -> next window starts right after it). } } } Logger.Info?.Print(LogClass.Gpu, $"MVPP probe sample: {candidates} mat4 candidates, {changed} changed since last sample."); } private static void DumpCbuf(GpuChannel channel, int slot) { ulong address = channel.BufferManager.GetGraphicsUniformBufferAddress(0, slot); int size = Math.Min(channel.BufferManager.GetGraphicsUniformBufferSize(0, slot), 512); if (address == 0 || address == ulong.MaxValue || size < 64) { return; } ReadOnlySpan data = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(address, size)); for (int i = 0; i + 4 <= data.Length; i += 4) { Logger.Info?.Print(LogClass.Gpu, $"MVPP dump cbuf{slot} +0x{i * 4:X3}: {data[i]:0.####} {data[i + 1]:0.####} {data[i + 2]:0.####} {data[i + 3]:0.####}"); } } /// /// Cheap shape filter: 16 finite floats of sane magnitude with enough non-zero terms. /// Material constants pass it too - the temporal changed-filter does the real sorting. /// private static bool LooksLikeMat4(ReadOnlySpan m) { int nonZero = 0; for (int i = 0; i < 16; i++) { float v = m[i]; if (!float.IsFinite(v) || MathF.Abs(v) > 1e6f) { return false; } if (v != 0f) { nonZero++; } } return nonZero >= 10; } private static uint Fingerprint(ReadOnlySpan m) { uint fp = 2166136261u; for (int i = 0; i < 16; i++) { fp = (fp ^ (uint)BitConverter.SingleToInt32Bits(m[i])) * 16777619u; } return fp; } } }