using Ryujinx.Common.Logging; using System; using System.IO; namespace Ryujinx.Graphics.Gpu.Engine.Threed { static class MvppPreSyncProbe { private static bool _enabled = Environment.GetEnvironmentVariable("RYUJINX_PRESYNC") == "1"; private static readonly int _vkey = int.TryParse(Environment.GetEnvironmentVariable("RYUJINX_PRESYNC_VKEY"), out int vk) && vk > 0 ? vk : 0x79; private static readonly int _maxShots = int.TryParse(Environment.GetEnvironmentVariable("RYUJINX_PRESYNC_MAX"), out int mx) && mx > 0 ? mx : 4; [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); private static bool _announced; private static int _shot; private static long _nextMs; private static byte[] _before; private static bool _armedThisFrame; private static int _guestShots; private static long _nextGuestMs; private static bool KeyHeld() { if (!OperatingSystem.IsWindows()) { return false; } try { return (GetAsyncKeyState(_vkey) & 0x8000) != 0; } catch { return false; } } public static void DumpGuest(Image.Texture texture) { if (!_enabled || texture == null) { return; } try { long now = Environment.TickCount64; if (_guestShots >= _maxShots || now < _nextGuestMs || !KeyHeld()) { return; } ReadOnlySpan guest = texture.PhysicalMemory.GetSpan(texture.Range); Image.TextureInfo gi = texture.Info; string dir = Path.Combine("presync", $"guest{_guestShots:D2}"); Directory.CreateDirectory(dir); File.WriteAllBytes( Path.Combine(dir, $"MEMOIRE_{gi.Width}x{gi.Height}_lin{gi.IsLinear}_gobY{gi.GobBlocksInY}_stride{gi.Stride}_{gi.FormatInfo.Format}.bin"), guest.ToArray()); using GAL.PinnedSpan host = texture.HostTexture.GetData(); File.WriteAllBytes( Path.Combine(dir, $"HOTE_{gi.Width}x{gi.Height}_{gi.FormatInfo.Format}.bin"), host.Get().ToArray()); Logger.Info?.Print(LogClass.Gpu, $"MVPP PRESYNC GUEST shot {_guestShots}: memoire invitee {guest.Length} octets + texture hote, " + $"{gi.Width}x{gi.Height} lin={gi.IsLinear} gobY={gi.GobBlocksInY} stride={gi.Stride} {gi.FormatInfo.Format} -> {dir}"); _guestShots++; _nextGuestMs = now + 2000; } catch (Exception e) { _enabled = false; Logger.Warning?.Print(LogClass.Gpu, $"MVPP PRESYNC GUEST: desactive apres erreur: {e.Message}"); } } public static void Before(Image.Texture texture) { if (!_enabled || texture?.HostTexture == null) { return; } try { if (!_announced) { _announced = true; Logger.Info?.Print(LogClass.Gpu, "MVPP PRESYNC: ON -- APPUIE SUR F10 quand tu VOIS l'artefact. Capture l'image presentee " + "AVANT et APRES la resynchronisation, pour voir si c'est ELLE qui la detruit."); } _armedThisFrame = false; if (_shot >= _maxShots) { return; } long now = Environment.TickCount64; if (now < _nextMs || !KeyHeld()) { return; } using GAL.PinnedSpan data = texture.HostTexture.GetData(); _before = data.Get().ToArray(); _armedThisFrame = true; } catch (Exception e) { _enabled = false; Logger.Warning?.Print(LogClass.Gpu, $"MVPP PRESYNC: desactive apres erreur (before): {e.Message}"); } } public static void After(Image.Texture texture) { if (!_enabled || !_armedThisFrame || _before == null || texture?.HostTexture == null) { return; } _armedThisFrame = false; try { using GAL.PinnedSpan data = texture.HostTexture.GetData(); byte[] after = data.Get().ToArray(); bool identical = _before.Length == after.Length && _before.AsSpan().SequenceEqual(after); long diffBytes = 0; if (!identical && _before.Length == after.Length) { for (int i = 0; i < after.Length; i++) { if (_before[i] != after[i]) { diffBytes++; } } } string dir = Path.Combine("presync", $"shot{_shot:D2}"); Directory.CreateDirectory(dir); string tag = $"{texture.Info.Width}x{texture.Info.Height}_{texture.Info.FormatInfo.Format}"; File.WriteAllBytes(Path.Combine(dir, $"AVANT_{tag}.bin"), _before); File.WriteAllBytes(Path.Combine(dir, $"APRES_{tag}.bin"), after); double pct = after.Length > 0 ? 100.0 * diffBytes / after.Length : 0; Logger.Info?.Print(LogClass.Gpu, $"MVPP PRESYNC shot {_shot}: {tag} -- {(identical ? "IDENTIQUES (la resync ne touche rien)" : $"DIFFERENTS, {pct:F1}% des octets changes par la resync")} -> {dir}"); _shot++; _nextMs = Environment.TickCount64 + 2000; _before = null; } catch (Exception e) { _enabled = false; Logger.Warning?.Print(LogClass.Gpu, $"MVPP PRESYNC: desactive apres erreur (after): {e.Message}"); } } } }