From 42182ebcb84f09512c50e1b908f2dd99daca0340 Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Fri, 2 Oct 2026 18:48:02 +0300 Subject: [PATCH] [Diagnostics] Trace the GPU writers of DCC metadata and of a chosen address SHARPEMU_TRACE_DCC_WRITERS=1 writes the shader program and a call stack for GPU buffer writes that overlap DCC metadata; SHARPEMU_TRACE_WRITER_ADDRESS does the same for writes that cover one guest address. --- .../Diagnostics/DccWriterTrace.cs | 70 +++++++++++++++++++ .../Gpu/Buffers/GuestBufferCache.cs | 5 ++ .../Gpu/Rendering/RenderExecutor.Compute.cs | 5 ++ 3 files changed, 80 insertions(+) create mode 100644 src/SharpEmu.Libs/Diagnostics/DccWriterTrace.cs diff --git a/src/SharpEmu.Libs/Diagnostics/DccWriterTrace.cs b/src/SharpEmu.Libs/Diagnostics/DccWriterTrace.cs new file mode 100644 index 0000000..88efe4f --- /dev/null +++ b/src/SharpEmu.Libs/Diagnostics/DccWriterTrace.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Diagnostics; +using System.Globalization; +using SharpEmu.Libs.Gpu.Buffers; + +namespace SharpEmu.Libs.Diagnostics; + +internal static class DccWriterTrace +{ + private const int MaxReports = 64; + private const int MaxFrames = 12; + + private static readonly bool DccEnabled = + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_DCC_WRITERS") == "1"; + + private static readonly ulong TargetAddress = ParseAddress(Environment.GetEnvironmentVariable("SHARPEMU_TRACE_WRITER_ADDRESS")); + + public static readonly bool Enabled = DccEnabled || TargetAddress != 0; + + [ThreadStatic] + public static ulong CurrentProgram; + + private static readonly HashSet<(ulong Address, ulong Size, ulong Program)> _reported = new(); + private static readonly HashSet<(ulong Hash, string Reason)> _refusals = new(); + + private static ulong ParseAddress(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return 0; + } + + var text = value.Trim(); + if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + text = text[2..]; + } + + return ulong.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed) ? parsed : 0; + } + + public static void Record(IGuestImageCache images, ulong address, ulong size) + { + var targeted = TargetAddress != 0 && TargetAddress >= address && TargetAddress - address < size; + if (_reported.Count >= MaxReports || (!targeted && !(DccEnabled && images.OverlapsDccMetadata(address, size))) || + !_reported.Add((address, size, CurrentProgram))) + { + return; + } + + var frames = new StackTrace(1, false).GetFrames(); + var names = frames.Take(MaxFrames).Select(frame => frame.GetMethod() is { } method + ? $"{method.DeclaringType?.Name}.{method.Name}" + : "?"); + Console.Error.WriteLine( + $"[GPU][{(targeted ? "TARGET_WRITER" : "DCC_WRITER")}] address=0x{address:X16} size=0x{size:X} program=0x{CurrentProgram:X16} stack={string.Join(" < ", names)}"); + } + + public static void Refuse(ulong hash, string reason) + { + if (!Enabled || _refusals.Count >= MaxReports || !_refusals.Add((hash, reason))) + { + return; + } + + Console.Error.WriteLine($"[GPU][BOUNDED_FILL_REFUSED] program=0x{hash:X16} reason={reason}"); + } +} diff --git a/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs b/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs index ba94a99..31766df 100644 --- a/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs +++ b/src/SharpEmu.Libs/Gpu/Buffers/GuestBufferCache.cs @@ -241,6 +241,11 @@ public sealed unsafe class GuestBufferCache : IGuestBufferStore, IDisposable if (isWritten) { buffer.NoteGpuWrite(); + if (Diagnostics.DccWriterTrace.Enabled) + { + Diagnostics.DccWriterTrace.Record(RequireImageCache(), guestAddress, size); + } + if (!_gpuModifiedRanges.Contains(guestAddress, size)) { _gpuModifiedRanges.Add(guestAddress, size); diff --git a/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Compute.cs b/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Compute.cs index cdb4686..8ad2c25 100644 --- a/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Compute.cs +++ b/src/SharpEmu.Libs/Gpu/Rendering/RenderExecutor.Compute.cs @@ -69,6 +69,11 @@ public sealed partial class RenderExecutor var input = computeProgram.Input; var program = input.Stage.Program ?? throw _host.Fatal($"The compute program is missing: shader=0x{compute.Address:X16}."); + if (Diagnostics.DccWriterTrace.Enabled) + { + Diagnostics.DccWriterTrace.CurrentProgram = program.Hash; + } + if (RenderTrace.Enabled) { RenderTrace.Write(