Repository navigation
Expand file tree
/
Copy pathhistory_debug.py
More file actions
211 lines (160 loc) · 6.66 KB
/
Copy pathhistory_debug.py
File metadata and controls
211 lines (160 loc) · 6.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# 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()