From f12aa91ba2935a7e7705255802476cab024d5719 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Tue, 19 Aug 2025 12:30:25 +0100 Subject: [PATCH] Python helpers to generate and check a full trace from shader debug --- util/test/rdtest/analyse.py | 83 +++++++++++++++++++---- util/test/rdtest/testcase.py | 128 +++++++++++++++++++++++++++++++++-- 2 files changed, 192 insertions(+), 19 deletions(-) diff --git a/util/test/rdtest/analyse.py b/util/test/rdtest/analyse.py index 3347ef40c..f9c4e5cf4 100644 --- a/util/test/rdtest/analyse.py +++ b/util/test/rdtest/analyse.py @@ -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 \ No newline at end of file + return (True, "") \ No newline at end of file diff --git a/util/test/rdtest/testcase.py b/util/test/rdtest/testcase.py index b41de901a..1c1a05d6c 100644 --- a/util/test/rdtest/testcase.py +++ b/util/test/rdtest/testcase.py @@ -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():