import renderdoc as rd from typing import List import rdtest import struct class VK_Shader_Debug_Zoo(rdtest.TestCase): demos_test_name = 'VK_Shader_Debug_Zoo' def check_capture(self): if not self.controller.GetAPIProperties().shaderDebugging: rdtest.log.success("Shader debugging not enabled, skipping test") return failed = False for test_name in ["GLSL1 tests", "GLSL2 tests", "ASM tests"]: rdtest.log.begin_section(test_name) action = self.find_action(test_name) for child in range(len(action.children)): section = action.children[child] self.controller.SetFrameEvent(section.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() if not pipe.GetShaderReflection(rd.ShaderStage.Pixel).debugInfo.debuggable: rdtest.log.print("Skipping undebuggable shader at {} in {}.".format(child, test_name)) return for test in range(section.numInstances): x = 4 * test + 1 y = 4 * child + 1 # Debug the shader trace: rd.ShaderDebugTrace = self.controller.DebugPixel(x, y, rd.DebugPixelInputs()) if trace.debugger is None: failed = True rdtest.log.error("Test {} in sub-section {} did not debug at all".format(test, child)) self.controller.FreeTrace(trace) continue _, variables = self.process_trace(trace) output: rd.SourceVariableMapping = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) debugged = self.evaluate_source_var(output, variables) try: self.check_pixel_value(pipe.GetOutputTargets()[0].resource, x, y, debugged.value.f32v[0:4]) except rdtest.TestFailureException as ex: failed = True rdtest.log.error("Test {} in sub-section {} did not match. {}".format(test, child, str(ex))) continue finally: self.controller.FreeTrace(trace) rdtest.log.success("Test {} in sub-section {} matched as expected".format(test, child)) rdtest.log.end_section(test_name) test_name = "Disassembly Tests" rdtest.log.begin_section(test_name) action = self.find_action("ASM tests") self.controller.SetFrameEvent(action.children[0].eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() refl: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Pixel) disasm = self.controller.DisassembleShader(pipe.GetGraphicsPipelineObject(), refl, "") # Test for some expected strings in the disassembly expectedStrings = [] # OpSwitch disassembly of 32-bit and 64-bit literals expectedStrings.append("case 305419896:") expectedStrings.append("case 4063516280:") expectedStrings.append("case 1311768465173141112:") expectedStrings.append("case 17452669529668998776:") for exp in expectedStrings: if exp not in disasm: failed = True rdtest.log.error("Failed to find `{}` in disassembly".format(exp)) rdtest.log.end_section(test_name) # CS is 8x4x1 thread_checks = [ (0,0,0), (1,1,0), (2,2,0), (3,3,0), (4,2,0), (5,1,0), (6,0,0), (7,1,0), ] compute_dims = [a for a in self.find_action( "Compute Tests").children if 'x' in a.customName] for comp_dim in compute_dims: section = f"Compute tests with {comp_dim.customName}" rdtest.log.begin_section(section) compute_tests = [ a for a in comp_dim.children if a.flags & rd.ActionFlags.Dispatch] for test, action in enumerate(compute_tests): self.controller.SetFrameEvent(action.eventId, False) pipe = self.controller.GetPipelineState() csrefl = pipe.GetShaderReflection(rd.ShaderStage.Compute) if not csrefl.debugInfo.debuggable: rdtest.log.print(f"Compute shader is undebuggable at {action.eventId} for {test}.") failed = True continue rw = pipe.GetReadWriteResources(rd.ShaderStage.Compute) if len(rw) != 1: rdtest.log.error("Unexpected number of RW resources") self.controller.FreeTrace(trace) failed = True continue groupid = (1, 0, 0) dim = csrefl.dispatchThreadsDimension # Debug the shader for threadid in thread_checks: # each test writes up to 16k data, one vec4 per thread * up to 1024 threads bufdata = self.controller.GetBufferData( rw[0].descriptor.resource, test*16*1024, 16*1024) try: expectedValue = struct.unpack_from( "4f", bufdata, 16*threadid[1]*dim[0] + 16*threadid[0]) except Exception as ex: rdtest.log.error(f"Exception Test {test} failed {ex}") failed = True continue # Debug the shader trace: rd.ShaderDebugTrace = self.controller.DebugThread(groupid, threadid) cycles, variables = self.process_trace(trace) # Check for non-zero cycles if cycles == 0: rdtest.log.success(f"Test {test} Group:{groupid} Thread:{threadid} : Shader debug cycle count was zero") self.controller.FreeTrace(trace) failed = True continue # Find the source variable 'testResult' at the highest instruction index name = 'testResult' debugged = None countInst = len(trace.instInfo) for inst in range(countInst): sourceVars = trace.instInfo[countInst-1-inst].sourceVars try: dataVars = [v for v in sourceVars if v.name == name] if len(dataVars) == 0: continue debugged = self.evaluate_source_var(dataVars[0], variables) except KeyError as ex: continue except rdtest.TestFailureException as ex: continue break if debugged is None: raise rdtest.TestFailureException(f"Couldn't find source variable {name} at {test}") debuggedValue = list(debugged.value.f32v[0:4]) if not rdtest.value_compare(expectedValue, debuggedValue, eps=5.0E-06): rdtest.log.error(f"Test {test} Group:{groupid} Thread:{threadid} EID:{action.eventId} failed {name} debugger {debuggedValue} doesn't match expected {expectedValue}") self.controller.FreeTrace(trace) failed = True continue rdtest.log.success(f"Test {test} Group:{groupid} Thread:{threadid} as expected") rdtest.log.end_section(section) if failed: raise rdtest.TestFailureException("Some tests were not as expected") rdtest.log.success("All tests matched")