Python helpers to generate and check a full trace from shader debug

This commit is contained in:
Jake Turner
2025-08-19 12:30:25 +01:00
parent e6f75e1530
commit f12aa91ba2
2 changed files with 192 additions and 19 deletions
+68 -15
View File
@@ -1,5 +1,6 @@
import struct
from typing import List
from typing import Tuple
import renderdoc
from . import util
@@ -337,39 +338,91 @@ def decode_mesh_data(controller: rd.ReplayController, indices: List[int], displa
return ret
def shadervariable_equal(a: rd.ShaderVariable, b : rd.ShaderVariable):
def str_vartype(t: rd.VarType) -> str:
if t == rd.VarType.Bool:
return "Bool"
elif t == rd.VarType.ConstantBlock:
return "ConstantBlock"
elif t == rd.VarType.Double:
return "Double"
elif t == rd.VarType.Enum:
return "Enum"
elif t == rd.VarType.Float:
return "Float"
elif t == rd.VarType.GPUPointer:
return "GPUPointer"
elif t == rd.VarType.Half:
return "Half"
elif t == rd.VarType.ReadOnlyResource:
return "ReadOnlyResource"
elif t == rd.VarType.ReadWriteResource:
return "ReadWriteResource"
elif t == rd.VarType.Sampler:
return "Sampler"
elif t == rd.VarType.SByte:
return "SByte"
elif t == rd.VarType.SInt:
return "SInt"
elif t == rd.VarType.SLong:
return "SLong"
elif t == rd.VarType.SShort:
return "SShort"
elif t == rd.VarType.Struct:
return "Struct"
elif t == rd.VarType.UByte:
return "UByte"
elif t == rd.VarType.UInt:
return "UInt"
elif t == rd.VarType.ULong:
return "ULong"
elif t == rd.VarType.Unknown:
return "Unknown"
elif t == rd.VarType.UShort:
return "UShort"
return "???"
def shadervariable_equal(a: rd.ShaderVariable, b : rd.ShaderVariable) -> Tuple[bool, str]:
difference = ""
if a.rows != b.rows:
return False
difference = f"Rows differ: {a.rows} != {b.rows}"
return (False, difference)
if a.columns != b.columns:
return False
difference = f"Columns differ: {a.columns} != {b.columns}"
return (False, difference)
if a.name != b.name:
return False
difference = f"Names differ: {a.name} != {b.name}"
return (False, difference)
if a.type != b.type:
return False
difference = f"Types differ: {str_vartype(a.type)} != {str_vartype(b.type)}"
return (False, difference)
if a.flags != b.flags:
return False
difference = f"Flags differ: {a.flags} != {b.flags}"
return (False, difference)
if len(a.members) != len(b.members):
return False
difference = f"Member count differs: {len(a.members)} != {len(b.members)}"
return (False, difference)
for i in range(a.rows * a.columns):
if a.type == rd.VarType.UByte or a.type == rd.VarType.SByte:
if a.value.u8v[i] != b.value.u8v[i]:
return False
return (False, f"Values differ at index {i}: {a.value.u8v[i]} != {b.value.u8v[i]}")
elif a.type == rd.VarType.Half or a.type == rd.VarType.UShort or a.type == rd.VarType.SShort:
if a.value.u16v[i] != b.value.u16v[i]:
return False
return (False, f"Values differ at index {i}: {a.value.u16v[i]} != {b.value.u16v[i]}")
elif a.type == rd.VarType.Float or a.type == rd.VarType.UInt or a.type == rd.VarType.SInt or a.type == rd.VarType.Bool or a.type == rd.VarType.Enum:
if a.value.u32v[i] != b.value.u32v[i]:
return False
return (False, f"Values differ at index {i}: {a.value.u32v[i]} != {b.value.u32v[i]}")
elif a.type == rd.VarType.Double or a.type == rd.VarType.ULong or a.type == rd.VarType.SLong or a.type == rd.VarType.GPUPointer:
if a.value.u64v[i] != b.value.u64v[i]:
return False
return (False, f"Values differ at index {i}: {a.value.u64v[i]} != {b.value.u64v[i]}")
else:
if a.value.u64v[i] != b.value.u64v[i]:
return False
return (False, f"Values differ at index {i}: {a.value.u64v[i]} != {b.value.u64v[i]}")
for m in range(len(a.members)):
if not shadervariable_equal(a.members[m], b.members[m]):
return False
(ret, diff) = shadervariable_equal(a.members[m], b.members[m])
if not ret:
difference = f"Member[{m}] differs {diff}"
return (False, difference)
return True
return (True, "")
+124 -4
View File
@@ -629,6 +629,124 @@ class TestCase:
log.success("Backbuffer is identical to reference")
def log_shader_variable(self, var: rd.ShaderVariable) -> None:
log.print(f"Shader Variable: {var.name} Type:{var.type} Rows:{var.rows} Columns:{var.columns} Flags:{var.flags} CountMembers:{len(var.members)}")
for i in range(var.rows * var.columns):
type = var.type
if type == rd.VarType.UByte or type == rd.VarType.SByte:
log.print(f"Byte {i}: {var.value.u8v[i]}")
elif type == rd.VarType.Half or type == rd.VarType.UShort or type == rd.VarType.SShort:
log.print(f"Half {i}: {var.value.u16v[i]}")
elif type == rd.VarType.Float:
log.print(f"Float {i}: {var.value.f32v[i]}")
elif type == rd.VarType.UInt or type == rd.VarType.SInt or type == rd.VarType.Bool or type == rd.VarType.Enum:
log.print(f"Int {i}: {var.value.u32v[i]}")
elif type == rd.VarType.Double:
log.print(f"Double {i}: {var.value.f64v[i]}")
elif type == rd.VarType.ULong or type == rd.VarType.SLong or type == rd.VarType.GPUPointer:
log.print(f"Long {i}: {var.value.u64v[i]}")
else:
log.print(f"??? {i}: {var.value.u64v[i]}")
for m in range(len(var.members)):
self.log_shader_variable(var.members[m])
def compare_shader_variable_change(self, expectedChange: rd.ShaderVariableChange, change: rd.ShaderVariableChange, showDiffs = True) -> bool:
ret = True
difference = ""
(res, difference) = analyse.shadervariable_equal(expectedChange.before, change.before)
if not res:
if not showDiffs:
return False
log.error(f"ShaderVariableChange different before {expectedChange.before.name} {change.before.name} {difference}")
ret = False
(res, difference) = analyse.shadervariable_equal(expectedChange.after, change.after)
if not res:
if not showDiffs:
return False
log.error(f"ShaderVariableChange different after {expectedChange.after.name} {change.after.name} {difference}")
ret = False
return ret
def compare_shader_variable_changes(self, expectedChanges: List[rd.ShaderVariableChange], changes: List[rd.ShaderVariableChange], showDiffs = True) -> bool:
ret = True
if (len(expectedChanges) != len(changes)):
if not showDiffs:
return False
log.error(f"Different number of changes:{len(expectedChanges)} != {len(changes)}")
return False
for i in range(len(expectedChanges)):
expected = expectedChanges[i]
change = changes[i]
if not self.compare_shader_variable_change(expected, change, showDiffs):
if not showDiffs:
return False
log.error(f"ShaderVariableChange[{i}] does not match")
ret = False
return ret
def compare_single_step(self, expectedState: rd.ShaderDebugState, state: rd.ShaderDebugState, showDiffs = True) -> bool:
ret = True
if expectedState.stepIndex != state.stepIndex:
if not showDiffs:
return False
log.error(f"Different stepIndex: {expectedState.stepIndex} != {state.stepIndex}")
ret = False
if expectedState.flags != state.flags:
if not showDiffs:
return False
log.error(f"Different flags: {expectedState.flags} != {state.flags}")
ret = False
if expectedState.nextInstruction != state.nextInstruction:
if not showDiffs:
return False
log.error(f"Different nextInstruction: {expectedState.nextInstruction} != {state.nextInstruction}")
ret = False
if not self.compare_shader_variable_changes(expectedState.changes, state.changes, showDiffs):
if not showDiffs:
return False
log.error(f"Different changes at nextInstruction:{expectedState.nextInstruction} stepIndex:{expectedState.stepIndex}")
ret = False
if len(expectedState.callstack) != len(state.callstack):
if not showDiffs:
return False
log.error(f"Different callstack length: {len(expectedState.callstack)} != {len(state.callstack)}")
return False
for i in range(len(expectedState.callstack)):
if expectedState.callstack[i] != state.callstack[i]:
if not showDiffs:
return False
log.error(f"Different callstack entry[{i}]: {expectedState.callstack[i]} != {state.callstack[i]}")
ret = False
return ret
def compare_full_traces(self, expectedStates: List[rd.ShaderDebugState], states: List[rd.ShaderDebugState], showDiffs = True) -> bool:
ret = True
if len(expectedStates) != len(states):
if not showDiffs:
return False
log.error(f"Traces have different number of states: {len(expectedStates)} != {len(states)}")
return False
for i in range(len(expectedStates)):
if not self.compare_single_step(expectedStates[i], states[i], showDiffs):
if not showDiffs:
return False
log.error(f"Trace state[{i}] does not match")
ret = False
return ret
def generate_full_trace(self, trace: rd.ShaderDebugTrace) -> List[rd.ShaderDebugState]:
allStates = []
while True:
states = self.controller.ContinueDebug(trace.debugger)
if len(states) == 0:
break
for state in states:
allStates.append(state)
return allStates
def process_trace(self, trace: rd.ShaderDebugTrace, validate: bool = True):
variables = {}
cycles = 0
@@ -980,8 +1098,9 @@ class TestCase:
else:
if c.after.name in variables:
# Step Forwards: not-first appearance of a variable "before" must equal currently known value
if not analyse.shadervariable_equal(c.before, variables[c.after.name]):
raise TestFailureException(f"Step {i} ShaderVariableChange for '{c.after.name}' before does not match existing entry")
(res, difference) = analyse.shadervariable_equal(c.before, variables[c.after.name])
if not res:
raise TestFailureException(f"Step {i} ShaderVariableChange for '{c.after.name}' before does not match existing entry {difference}")
else:
# Step Forwards: first appearance of a variable must have "before" = {}
if c.before != rd.ShaderVariable():
@@ -1006,8 +1125,9 @@ class TestCase:
else:
if c.before.name in variables:
# Step Backwards: not-first appearance of a variable "after" must equal currently known value
if not analyse.shadervariable_equal(c.after, variables[c.before.name]):
raise TestFailureException(f"Step {i} ShaderVariableChange for '{c.before.name}' after does not match existing entry")
(res, difference) = analyse.shadervariable_equal(c.after, variables[c.before.name])
if not res:
raise TestFailureException(f"Step {i} ShaderVariableChange for '{c.before.name}' after does not match existing entry {difference}")
else:
# Step Backwards: first appearance of a variable must have "after" = {}
if c.after != rd.ShaderVariable():