Skip to content

Commit 34682f2

Browse files
committed
graphics: per-message GBV detection, suppress tree dump for GBV-only frames
1 parent 1ba3f27 commit 34682f2

4 files changed

Lines changed: 77 additions & 29 deletions

File tree

sources/engine/Stride.Graphics/Direct3D11/GraphicsDevice.Direct3D11.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ public unsafe partial class GraphicsDevice
4040

4141
private ID3D11InfoQueue* nativeInfoQueue;
4242

43+
// GBV message IDs from D3D11_MESSAGE_ID, populated lazily via reflection so the scan
44+
// only runs in debug mode (InfoQueue draining never happens in release → Value is
45+
// never queried). Robust against SDK additions to the GBV enum range.
46+
private static readonly Lazy<System.Collections.Generic.HashSet<MessageID>> gbvMessageIds = new(BuildGbvMessageIdSet);
47+
48+
private static System.Collections.Generic.HashSet<MessageID> BuildGbvMessageIdSet()
49+
{
50+
var set = new System.Collections.Generic.HashSet<MessageID>();
51+
foreach (var name in Enum.GetNames<MessageID>())
52+
{
53+
if (name.Contains("Gpubased", StringComparison.OrdinalIgnoreCase)
54+
&& Enum.TryParse<MessageID>(name, out var id))
55+
set.Add(id);
56+
}
57+
return set;
58+
}
59+
4360
/// <summary>
4461
/// Gets the internal Direct3D 11 Device.
4562
/// </summary>
@@ -481,14 +498,14 @@ private void OnDeviceInfoQueueMessage(ref readonly Message message)
481498
var description = descriptionSpan.GetString();
482499

483500
// D3D11 has no synchronous-callback API. CPU-side validation is synchronous with the
484-
// API call, so as long as we drain at every scope transition (BeginProfile/EndProfile)
485-
// the leaf at drain time matches the leaf at message time. If GPU-based validation is
486-
// turned on (DebugGpuValidationEnabled = true), GBV messages arrive asynchronously
487-
// via the InfoQueue at fence completion — skip leaf attribution in that mode.
501+
// API call, so draining at every BeginProfile/EndProfile gives accurate leaf
502+
// attribution. GBV messages arrive asynchronously and we detect them by ID via the
503+
// reflection-built set. DebugGpuValidationEnabled is the kill-switch override.
488504
bool isDrawCategory = message.Category is MessageCategory.StateSetting
489505
or MessageCategory.Execution
490506
or MessageCategory.ResourceManipulation;
491-
bool attributable = isDrawCategory && !DebugGpuValidationEnabled;
507+
bool isGbv = DebugGpuValidationEnabled || gbvMessageIds.Value.Contains(message.ID);
508+
bool attributable = isDrawCategory && !isGbv;
492509
string scopePrefix = "";
493510
DebugScopeFrame leaf = null;
494511
if (attributable)
@@ -504,12 +521,12 @@ or MessageCategory.Execution
504521
case MessageSeverity.Error:
505522
DebugLog.Error($"[D3D11] {scopePrefix}{description}");
506523
if (leaf is not null) leaf.Errors++;
507-
if (isDrawCategory) debugSawDrawIssue = true;
524+
if (attributable) debugSawDrawIssue = true;
508525
break;
509526
case MessageSeverity.Warning:
510527
DebugLog.Warning($"[D3D11] {scopePrefix}{description}");
511528
if (leaf is not null) leaf.Warnings++;
512-
if (isDrawCategory) debugSawDrawIssue = true;
529+
if (attributable) debugSawDrawIssue = true;
513530
break;
514531
}
515532

sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ public unsafe partial class GraphicsDevice
4040
// Set by the callback when a draw-relevant validation message arrives within a scope.
4141
// Consumed by DrainDebugMessages at the next scope-empty transition.
4242

43+
// GBV message IDs from D3D12_MESSAGE_ID, populated lazily via reflection so the scan
44+
// only runs in debug mode (callback never fires in release → Value is never queried).
45+
// Robust against SDK additions to the GBV enum range.
46+
private static readonly Lazy<System.Collections.Generic.HashSet<MessageID>> gbvMessageIds = new(BuildGbvMessageIdSet);
47+
48+
private static System.Collections.Generic.HashSet<MessageID> BuildGbvMessageIdSet()
49+
{
50+
var set = new System.Collections.Generic.HashSet<MessageID>();
51+
foreach (var name in Enum.GetNames<MessageID>())
52+
{
53+
if (name.Contains("Gpubased", StringComparison.OrdinalIgnoreCase)
54+
&& Enum.TryParse<MessageID>(name, out var id))
55+
set.Add(id);
56+
}
57+
return set;
58+
}
59+
4360
/// <summary>
4461
/// Concurrent pool for lists of Graphics Resources that are used for staging operations.
4562
/// </summary>
@@ -910,10 +927,11 @@ private static void OnDebugMessageCallback(MessageCategory category, MessageSeve
910927
or MessageCategory.Execution
911928
or MessageCategory.ResourceManipulation;
912929

913-
// GPU-based validation messages arrive asynchronously — the active scope at callback
914-
// time isn't the one that issued the offending command. Skip leaf attribution and
915-
// scope prefix in that mode to avoid misleading the user.
916-
bool attributable = !device.DebugGpuValidationEnabled;
930+
// GBV messages arrive asynchronously, so leaf attribution would be wrong. We detect
931+
// them by ID via the reflection-built set. The DebugGpuValidationEnabled override
932+
// is a kill switch for cases the heuristic misses.
933+
bool isGbv = device.DebugGpuValidationEnabled || gbvMessageIds.Value.Contains(id);
934+
bool attributable = !isGbv;
917935
var leafName = attributable ? device.GetDebugLeafScopeName() : null;
918936
var scope = leafName is not null ? $"[{leafName}]: " : "";
919937

@@ -922,18 +940,20 @@ or MessageCategory.Execution
922940
// chain of "Down2" passes).
923941
var leaf = attributable ? device.debugCurrentFrame : null;
924942

943+
// Tree dump only fires for attributable messages — a tree without [!] markers is
944+
// noise, so GBV-only frames stay quiet (just the log lines).
925945
switch (severity)
926946
{
927947
case MessageSeverity.Corruption:
928948
case MessageSeverity.Error:
929949
DebugLog.Error($"{scope}{desc}");
930950
if (leaf is not null) leaf.Errors++;
931-
if (isDrawCategory) device.debugSawDrawIssue = true;
951+
if (isDrawCategory && attributable) device.debugSawDrawIssue = true;
932952
break;
933953
case MessageSeverity.Warning:
934954
DebugLog.Warning($"{scope}{desc}");
935955
if (leaf is not null) leaf.Warnings++;
936-
if (isDrawCategory) device.debugSawDrawIssue = true;
956+
if (isDrawCategory && attributable) device.debugSawDrawIssue = true;
937957
break;
938958
}
939959
}
@@ -982,9 +1002,11 @@ internal void FlushDebugMessages()
9821002
bool isDrawCategory = message->Category is MessageCategory.StateSetting
9831003
or MessageCategory.Execution
9841004
or MessageCategory.ResourceManipulation;
1005+
bool isGbv = DebugGpuValidationEnabled || gbvMessageIds.Value.Contains(message->ID);
1006+
bool attributable = !isGbv;
9851007

9861008
string prefix = null;
987-
if (isDrawCategory)
1009+
if (isDrawCategory && attributable)
9881010
{
9891011
var leafName = GetDebugLeafScopeName();
9901012
prefix = leafName is not null ? $"[{leafName}]: " : null;
@@ -995,11 +1017,11 @@ or MessageCategory.Execution
9951017
case MessageSeverity.Corruption:
9961018
case MessageSeverity.Error:
9971019
DebugLog.Error($"{prefix}{description}");
998-
if (isDrawCategory) sawDrawIssue = true;
1020+
if (isDrawCategory && attributable) sawDrawIssue = true;
9991021
break;
10001022
case MessageSeverity.Warning:
10011023
DebugLog.Warning($"{prefix}{description}");
1002-
if (isDrawCategory) sawDrawIssue = true;
1024+
if (isDrawCategory && attributable) sawDrawIssue = true;
10031025
break;
10041026
}
10051027
}

sources/engine/Stride.Graphics/GraphicsDevice.DebugScope.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,12 @@ public partial class GraphicsDevice
3737
internal bool debugSawDrawIssue;
3838

3939
/// <summary>
40-
/// When <see langword="true"/>, the backend has GPU-side validation enabled
41-
/// (D3D11/D3D12 GPU-based validation, Vulkan GPU-Assisted Validation). Messages from
42-
/// those modes arrive asynchronously to recording, so per-leaf scope attribution is
43-
/// unreliable. Backends should skip leaf <c>Errors</c>/<c>Warnings</c> increments and the
44-
/// <c>[scope]:</c> log prefix when this is set, but still log the message and dump the
45-
/// tree. Defaults to <see langword="false"/> — Stride doesn't enable any of those modes
46-
/// today.
40+
/// Kill switch that forces every validation message to be treated as GPU-side and skip
41+
/// leaf attribution / tree-dump triggering. Backends already detect GPU-side messages
42+
/// per-message (D3D11/D3D12 via the <c>D3D*_MESSAGE_ID_GPU_BASED_VALIDATION_*</c>
43+
/// enum entries; Vulkan via <c>pMessageIdName</c>), so this is only needed if those
44+
/// heuristics ever miss a backend's GBV namespace and produce noisy misattributed
45+
/// dumps. Defaults to <see langword="false"/>; Stride doesn't enable any GBV mode today.
4746
/// </summary>
4847
public bool DebugGpuValidationEnabled { get; set; }
4948

sources/engine/Stride.Graphics/Vulkan/GraphicsAdapterFactory.Vulkan.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,20 @@ private unsafe static uint DebugReport(VkDebugUtilsMessageSeverityFlagsEXT sever
287287
// Performance categories are draw-relevant; General is mostly init/shutdown noise.
288288
var device = GraphicsDevice.DebugMessengerDevice;
289289
bool isDrawCategory = (types & (VkDebugUtilsMessageTypeFlagsEXT.Validation | VkDebugUtilsMessageTypeFlagsEXT.Performance)) != 0;
290-
// GPU-Assisted Validation messages arrive on a worker thread after the GPU completes
291-
// the offending draw — scope attribution is meaningless. Skip prefix/leaf bumps when
292-
// that mode is on; the tree dump still fires from the draw-issue flag below.
293-
bool attributable = device is not null && !device.DebugGpuValidationEnabled;
290+
291+
// GPU-Assisted Validation (instrumented shaders, results read back on a worker)
292+
// arrives asynchronously to recording. The Khronos validation layer tags those with
293+
// ID names containing "GPU-AV"/"GPUAV"/"GPU-Assisted". Skip attribution and the
294+
// tree-dump trigger so GBV-only frames stay quiet.
295+
bool isGbv = device is not null && device.DebugGpuValidationEnabled;
296+
if (!isGbv && pCallbackData->pMessageIdName != null)
297+
{
298+
var idName = new VkUtf8String(pCallbackData->pMessageIdName).ToString();
299+
isGbv = idName.Contains("GPU-AV", StringComparison.Ordinal)
300+
|| idName.Contains("GPUAV", StringComparison.Ordinal)
301+
|| idName.Contains("GPU-Assisted", StringComparison.Ordinal);
302+
}
303+
bool attributable = device is not null && !isGbv;
294304
string scopePrefix = "";
295305
DebugScopeFrame leaf = null;
296306
if (attributable)
@@ -305,13 +315,13 @@ private unsafe static uint DebugReport(VkDebugUtilsMessageSeverityFlagsEXT sever
305315
{
306316
GraphicsDevice.DebugLog.Error($"[Vulkan] {scopePrefix}{message}");
307317
if (leaf is not null) leaf.Errors++;
308-
if (device is not null && isDrawCategory) device.debugSawDrawIssue = true;
318+
if (device is not null && isDrawCategory && attributable) device.debugSawDrawIssue = true;
309319
}
310320
else if (severity >= VkDebugUtilsMessageSeverityFlagsEXT.Warning)
311321
{
312322
GraphicsDevice.DebugLog.Warning($"[Vulkan] {scopePrefix}{message}");
313323
if (leaf is not null) leaf.Warnings++;
314-
if (device is not null && isDrawCategory) device.debugSawDrawIssue = true;
324+
if (device is not null && isDrawCategory && attributable) device.debugSawDrawIssue = true;
315325
}
316326
else if (severity >= VkDebugUtilsMessageSeverityFlagsEXT.Info)
317327
{

0 commit comments

Comments
 (0)