mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-29 01:46:38 +00:00
212 lines
6.7 KiB
Python
212 lines
6.7 KiB
Python
# these imports are not strictly necessary, but are convenient
|
|
import renderdoc
|
|
import qrenderdoc
|
|
|
|
# this is here to give autocomplete when editing the example
|
|
# in VS Code where it doesn't know about this global
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
pyrenderdoc = qrenderdoc.CaptureContext()
|
|
|
|
if not pyrenderdoc.IsCaptureLoaded():
|
|
filename = pyrenderdoc.Extensions().OpenFileName("Choose a capture", "", "*.rdc")
|
|
|
|
pyrenderdoc.LoadCapture(filename, renderdoc.ReplayOptions(), filename, False, True)
|
|
|
|
from typing import List
|
|
|
|
|
|
# a callback to repeatedly ask the user if they're ready, with a 5 second
|
|
# arbitrary wait each time they say no to give them a bit of time
|
|
def check_ready():
|
|
choice = pyrenderdoc.Extensions().QuestionDialog(
|
|
"Are you at an interesting event with pixel selected?",
|
|
[
|
|
qrenderdoc.DialogButton.Yes,
|
|
qrenderdoc.DialogButton.No,
|
|
qrenderdoc.DialogButton.Cancel,
|
|
],
|
|
"Ready?",
|
|
)
|
|
|
|
if choice == qrenderdoc.DialogButton.Cancel:
|
|
return
|
|
|
|
if choice == qrenderdoc.DialogButton.No:
|
|
pyrenderdoc.DelayedCallback(5000, check_ready)
|
|
return
|
|
|
|
prepare_history()
|
|
|
|
|
|
def prepare_history():
|
|
tex_view = pyrenderdoc.GetTextureViewer()
|
|
|
|
# find the selected texture and location
|
|
id = tex_view.GetCurrentResource()
|
|
sub = tex_view.GetSelectedSubresource()
|
|
x, y = tex_view.GetPickedLocation()
|
|
|
|
name = pyrenderdoc.GetResourceName(id)
|
|
|
|
print(f'Analysing on "{name}"@{renderdoc.DumpObject(sub)} at {x},{y}')
|
|
|
|
disp = renderdoc.TextureDisplay()
|
|
disp.subresource = sub
|
|
|
|
# show the window first, as the results will likely take some time to come back
|
|
history_window = pyrenderdoc.ViewPixelHistory(id, x, y, sub.slice, disp)
|
|
pyrenderdoc.AddDockWindow(
|
|
history_window.Widget(),
|
|
qrenderdoc.DockReference.RightOf,
|
|
pyrenderdoc.GetPythonShell().Widget(),
|
|
)
|
|
|
|
# get a blocking controller. This means long-running work like pixel history and
|
|
# shader debugging will block the running thread. In a UI extension this is not
|
|
# good but for python scripts it will block the script thread.
|
|
controller = pyrenderdoc.GetBlockingController()
|
|
|
|
history = controller.PixelHistory(id, x, y, sub, disp.typeCast)
|
|
|
|
history_window.SetHistory(history)
|
|
|
|
print(f"{len(history)} modifications to that pixel:")
|
|
|
|
for h in history:
|
|
col = lambda x: [int(c * 100.0) / 100.0 for c in x.col.floatValue]
|
|
|
|
if h.Passed():
|
|
print(
|
|
f" at EID {h.eventId} changed from {col(h.preMod)} to {col(h.postMod)}"
|
|
)
|
|
else:
|
|
print(f" at EID {h.eventId} modification failed")
|
|
|
|
# get a list of all drawcalls that passed with a pixel shader bound
|
|
eb = pyrenderdoc.GetEventBrowser()
|
|
|
|
passed_draws = list(
|
|
filter(
|
|
lambda x: eb.GetActionForEID(x.eventId).flags
|
|
& renderdoc.ActionFlags.Drawcall,
|
|
[h for h in history if h.Passed() and not h.unboundPS],
|
|
)
|
|
)
|
|
|
|
if len(passed_draws) == 0:
|
|
print("No draws wrote to this pixel with a pixel shader")
|
|
else:
|
|
p = passed_draws[0]
|
|
|
|
pyrenderdoc.SetEventID([], p.eventId, p.eventId, False)
|
|
|
|
pipe = pyrenderdoc.CurPipelineState()
|
|
|
|
refl = pipe.GetShaderReflection(renderdoc.ShaderStage.Pixel)
|
|
|
|
if not refl.debugInfo.debuggable:
|
|
print("Shader can't be debugged:")
|
|
print(refl.debugInfo.debugStatus)
|
|
return
|
|
|
|
inputs = renderdoc.DebugPixelInputs()
|
|
|
|
inputs.primitive = p.primitiveID
|
|
inputs.sample = renderdoc.ReplayController.NoPreference
|
|
inputs.view = renderdoc.ReplayController.NoPreference
|
|
|
|
trace = controller.DebugPixel(x, y, inputs)
|
|
|
|
if trace.debugger is None:
|
|
print("Debug failed :(")
|
|
controller.FreeTrace(trace)
|
|
return
|
|
|
|
# the trace holds static global information about this debugged instance,
|
|
# such as the information of which resources are bound or reflected
|
|
# information per-instruction
|
|
|
|
states: List[renderdoc.ShaderDebugState] = []
|
|
|
|
# continually simulate the shader until it completes
|
|
while True:
|
|
more = controller.ContinueDebug(trace.debugger)
|
|
if more == []:
|
|
break
|
|
states += more
|
|
|
|
# look at the mid-point state
|
|
if states == []:
|
|
print("Shader debug failed!")
|
|
else:
|
|
state = states[len(states) // 2]
|
|
print(
|
|
f"Examining step {state.stepIndex}, before instruction {state.nextInstruction}"
|
|
)
|
|
stack = "\n".join(state.callstack)
|
|
print(f"Callstack:\n{stack}")
|
|
|
|
print()
|
|
|
|
print(f"{len(state.changes)} debug variable changes")
|
|
for ch in state.changes:
|
|
print(f" '{ch.before.name}' -> '{ch.after.name}'")
|
|
|
|
print()
|
|
|
|
infos = [
|
|
i for i in trace.instInfo if i.instruction <= state.nextInstruction
|
|
]
|
|
|
|
if infos == []:
|
|
info = trace.instInfo[0]
|
|
else:
|
|
info = infos[-1]
|
|
|
|
disasm = controller.DisassembleShader(
|
|
pipe.GetGraphicsPipelineObject(),
|
|
refl,
|
|
"",
|
|
)
|
|
|
|
disline = disasm.splitlines()[info.lineInfo.disassemblyLine - 1]
|
|
|
|
srcline = ""
|
|
if info.lineInfo.fileIndex >= 0:
|
|
src = refl.debugInfo.files[info.lineInfo.fileIndex].contents
|
|
srcline = src.splitlines()[info.lineInfo.lineStart - 1]
|
|
|
|
print(f"Examining instruction {info.instruction}.")
|
|
print(f" which has {len(info.sourceVars)} source vars:")
|
|
if srcline != "":
|
|
print(srcline)
|
|
print(disline)
|
|
print()
|
|
for s in info.sourceVars:
|
|
debugVars = ", ".join(
|
|
[v.name + "." + ("xyzw"[v.component % 4]) for v in s.variables]
|
|
)
|
|
print(f" {s.name} is {str(s.type)} stored in: {debugVars}")
|
|
|
|
# if we want to display the shader debugger, we need to pass a new debugger
|
|
# as the UI wants to process the set of states itself
|
|
trace = controller.DebugPixel(x, y, inputs)
|
|
shad = pyrenderdoc.DebugShader(
|
|
refl,
|
|
pipe.GetGraphicsPipelineObject(),
|
|
trace,
|
|
"Debugged From Python",
|
|
)
|
|
|
|
pyrenderdoc.AddDockWindow(
|
|
shad.Widget(),
|
|
qrenderdoc.DockReference.BottomOf,
|
|
history_window.Widget(),
|
|
)
|
|
|
|
|
|
# start by calling our function that checks if the user is ready to debug
|
|
check_ready()
|