// 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;
}
}
}