[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.
This commit is contained in:
ParantezTech 2026-10-02 18:48:02 +03:00
parent 792953c90b
commit 42182ebcb8
3 changed files with 80 additions and 0 deletions

View File

@ -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}");
}
}

View File

@ -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);

View File

@ -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(