Add new python examples, also available embedded into UI

This commit is contained in:
baldurk
2026-08-13 21:05:11 +01:00
parent 11b83f2272
commit ec5cd1f049
14 changed files with 1125 additions and 1 deletions
+62
View File
@@ -0,0 +1,62 @@
# 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)
# prime numbers have exactly 2 integer factors.
# note the // operator in python does integer-division
def isprime(n):
return len([x for x in range(1, n + 1) if (n / x) == (n // x)]) == 2
# our main filter function. Could use the params or other things passed in
# to do more complex filtering
def filter_func(
ctx: qrenderdoc.CaptureContext,
filter: str,
params: str,
eventId: int,
chunk: renderdoc.SDChunk,
action: renderdoc.ActionDescription,
eventName: str,
):
return isprime(eventId)
# the parser gives us a chance to parse the params and cache the data,
# this is only called when the filter is modified. It also lets us
# error-check any params we want
def parser_func(ctx: qrenderdoc.CaptureContext, filter: str, params: str):
if "error" in params:
return f"You shouldn't put 'error' in ${filter}()"
return ""
# the completer can take the current params string and dynamically
# provide auto-complete suggestions for what to add.
def completer_func(ctx: qrenderdoc.CaptureContext, filter: str, params: str):
return ["foo", "bar", "error"]
# In a UI extension we'd register and unregister over the lifetime of the extension.
# But we can also just unregister unconditionally, and then re-register. Note if you
# do not unregister then this will fail and will refuse to overwrite an existing filter.
pyrenderdoc.GetEventBrowser().UnregisterEventFilterFunction("prime")
pyrenderdoc.GetEventBrowser().RegisterEventFilterFunction(
"prime",
"Show only events with prime EIDs.",
filter_func,
parser_func,
completer_func,
)
+54
View File
@@ -0,0 +1,54 @@
# 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()
pyrenderdoc.ShowCaptureDialog()
dialog = pyrenderdoc.GetCaptureDialog()
exe = pyrenderdoc.Extensions().OpenFileName("Find an executable", "", "*.exe")
dialog.SetExecutableFilename(exe)
dialog.SetCommandLine("--cool-level very")
settings = dialog.Settings()
# we could also set the command line here, this is identical to SetCommandLine() above
print(settings.commandLine)
# reset anything the user has changed to default
settings.options = renderdoc.CaptureOptions()
# enable callstack capture
settings.options.captureCallstacks = True
dialog.SetSettings(settings)
opts = [qrenderdoc.DialogButton.Yes, qrenderdoc.DialogButton.No]
go = pyrenderdoc.Extensions().QuestionDialog("Ready to Launch?", opts, "Final Check")
if go == qrenderdoc.DialogButton.Yes:
conn = dialog.Launch()
# don't allow the connection to close itself so we can expect that it will be valid
# when the delayed callback below is called
conn.PreventAutoClose()
def connected_cb():
print(f"Connected to {conn.Target()} running APIs: {', '.join(conn.GetAPIs())}")
numcaps = len(conn.GetCaptures())
if numcaps == 0:
print("No captures have been made!")
else:
print(f"{numcaps} captures have been made!")
# wait a little bit, then call our callback to print the connection status
pyrenderdoc.DelayedCallback(5000, connected_cb)
+211
View File
@@ -0,0 +1,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()
+66
View File
@@ -0,0 +1,66 @@
# 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)
# annotate the function parameter so that autocomplete
# understands the type
from typing import List
# recursively walk the actions and their children,
# looking at marker regions. Returns a list of lines
# so we can more easily indent when recursing
def format_tree(actions: List[renderdoc.ActionDescription]):
draws, dispatches, copies = 0, 0, 0
ret = []
for a in actions:
# take the flags type for brevity
ActionFlags = renderdoc.ActionFlags
if a.flags & (ActionFlags.PushMarker | ActionFlags.MultiAction):
# markers store their name in the action's customName
# field so include that and then indent all the lines
# from recursing into the action's children
ret.append(f"{a.customName}:")
ret += [" " + l for l in format_tree(a.children)]
# for non marker-regions, count them
elif a.flags & ActionFlags.Drawcall:
draws += 1
elif a.flags & ActionFlags.Dispatch:
dispatches += 1
elif a.flags & (ActionFlags.Copy | ActionFlags.Clear):
copies += 1
# make a final line if we found anything else in this region
line = ""
if draws > 0:
line += f", {draws} draws"
if dispatches > 0:
line += f", {dispatches} dispatches"
if copies > 0:
line += f", {copies} clears/copies"
# trim the starting ", "
if line != "":
ret.insert(0, line[2:])
return ret
# the root of the recursion starts with actions at the
# root level of the capture
for line in format_tree(pyrenderdoc.CurRootActions()):
print(line)
+57
View File
@@ -0,0 +1,57 @@
# 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)
mems = {}
for t in pyrenderdoc.GetTextures():
if t.memory == renderdoc.ResourceId():
continue
entry = (
t.memoryOffset,
t.memoryOffset + t.byteSize,
f"[Texture] {pyrenderdoc.GetResourceName(t.resourceId)}",
)
if t.memory not in mems:
mems[t.memory] = []
mems[t.memory].append(entry)
for b in pyrenderdoc.GetBuffers():
if b.memory == renderdoc.ResourceId():
continue
entry = (
b.memoryOffset,
b.memoryOffset + b.length,
f"[Buffer] {pyrenderdoc.GetResourceName(b.resourceId)}",
)
if b.memory not in mems:
mems[b.memory] = []
mems[b.memory].append(entry)
for m in mems:
binds = mems[m]
for idx, bind1 in enumerate(binds):
for bind2 in binds[idx + 1 :]:
if bind1[0] < bind2[0] and bind1[1] > bind2[0]:
print(f"In memory {pyrenderdoc.GetResourceName(m)} overlap:")
print(f" {bind1[0]:08x} - {bind1[1]:08x}: {bind1[2]} ")
print(f" {bind2[0]:08x} - {bind2[1]:08x}: {bind2[2]}")
+153
View File
@@ -0,0 +1,153 @@
# 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():
raise RuntimeError("Run example with capture open and a vertex draw selected")
import struct
from typing import List, cast
# verify the current drawcall
pipe = pyrenderdoc.CurPipelineState()
avoid_stages = [
renderdoc.ShaderStage.Mesh,
renderdoc.ShaderStage.Geometry,
renderdoc.ShaderStage.Hull,
]
if any([pipe.GetShader(x) != renderdoc.ResourceId() for x in avoid_stages]):
raise RuntimeError("Can't run example on this draw")
refl = pipe.GetShaderReflection(renderdoc.ShaderStage.Vertex)
if refl is None:
raise RuntimeError("Can't run example on this draw")
controller = pyrenderdoc.GetBlockingController()
meshdata = controller.GetPostVSData(0, 0, renderdoc.MeshDataStage.VSOut)
print(f"Mesh data contains {meshdata.numIndices} indices in {str(meshdata.topology)}")
if meshdata.indexResourceId != renderdoc.ResourceId():
print(" (indexed)")
else:
print(" (non-indexed)")
if meshdata.unproject:
print(f" Rasterized data: {meshdata.nearPlane:.2f}-{meshdata.farPlane:.2f}")
# default to just indices, but if this does use an index buffer then fetch that data
idxs = [i for i in range(meshdata.numIndices)]
if meshdata.indexResourceId != renderdoc.ResourceId():
bufdata = controller.GetBufferData(
meshdata.indexResourceId, meshdata.indexByteOffset, meshdata.indexByteSize
)
# pick the appropriate format character for 1-byte, 2-byte, or 4-byte indices
# 01234
struct_type = " BH I"[meshdata.indexByteStride]
# use struct.unpack to interpret the bytes as a series of integers
idxs = cast(
List[int], struct.unpack(f"{struct_type}{meshdata.numIndices}", bufdata)
)
def fmt_vec(vec):
return ", ".join([f"{x:.3f}" for x in vec])
# print the first 4 triangles
for tri in range(min(4, meshdata.numIndices // 3)):
tri_idxs = idxs[tri * 3 : tri * 3 + 3]
print()
print(f"Triangle {tri}:")
for idx in tri_idxs:
print(f" [{idx}]:")
# we expect baseVertex to be 0 - this does NOT come from the
# original draw, but we still include it
idx += meshdata.baseVertex
offset = meshdata.vertexByteOffset + meshdata.vertexByteStride * idx
# it would definitely be better to cache this data and look it up locally,
# but we do this to demonstrate how GetBufferData can be used
vert_data = controller.GetBufferData(
meshdata.vertexResourceId, offset, meshdata.vertexByteStride
)
# this could again be cached outside the per-vertex loop
offset = 0
# RenderDoc always outputs the position at the beginning of the vertex
# data, so that the mesh data can be re-used for rendering without needing
# any offsets.
posidx = [
o.systemValue == renderdoc.ShaderBuiltin.Position
for o in refl.outputSignature
].index(True)
if posidx >= 0:
pos = refl.outputSignature[posidx]
# simple case, we assume float output and don't have to worry about alignment
posdata = fmt_vec(struct.unpack_from(f"={pos.compCount}f", vert_data))
print(f" <pos>: {posdata}")
offset += pos.compCount * 4
for output in refl.outputSignature:
# position was handled above, so skip it here
if output.systemValue == renderdoc.ShaderBuiltin.Position:
continue
# some APIs align postvs data, to traditional 'wide' alignment:
# elements rounded up to 4 bytes and 3-wide vectors aligned to 4-wide
# in all other cases data is tightly packed
if pipe.HasAlignedPostVSData(renderdoc.MeshDataStage.VSOut):
align = max(4, renderdoc.VarTypeByteSize(output.varType))
if output.compCount == 3:
align *= 4
else:
align *= output.compCount
if offset % align != 0:
offset = align - (offset % align)
data_offs = offset
offset += output.compCount * renderdoc.VarTypeByteSize(output.varType)
# for simplicity we don't handle many different variable types here, only
# simple ones. You can use the varType and more complex struct formats to
# decode other types of data
fmtchar = ""
if output.varType == renderdoc.VarType.Float:
fmtchar = "f"
elif output.varType == renderdoc.VarType.UInt:
fmtchar = "I"
elif output.varType == renderdoc.VarType.SInt:
fmtchar = "i"
if fmtchar != "":
fmt = f"={output.compCount}{fmtchar}"
data = fmt_vec(struct.unpack_from(fmt, vert_data, data_offs))
else:
data = "<non-decoded data>"
name = output.varName
if name == "":
name = output.semanticIdxName
print(f" {name}: {data}")
+65
View File
@@ -0,0 +1,65 @@
# 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()
mqt = pyrenderdoc.Extensions().GetMiniQtHelper()
# helper to make for shorter example code
def add_widget(parent, child, text=""):
mqt.AddWidget(parent, child)
if text != "":
mqt.SetWidgetText(child, text)
return child
# create a new floating window for our example
top = mqt.CreateToplevelWidget("Example!")
pyrenderdoc.AddDockWindow(top, qrenderdoc.DockReference.NewFloatingArea, None)
# add a group of interactive widgets
group = add_widget(top, mqt.CreateGroupBox(True), "Interactive Widgets")
layout = add_widget(group, mqt.CreateHorizontalContainer())
# callback for when the button is pressed that randomises the
# progress bar and counts its presses
count = 0
def update_button(ctx=None, wid=None, text=None):
global count
mqt.SetWidgetText(butt, f"{count} button presses")
count += 1
import random
mqt.SetProgressBarValue(prog, random.randint(0, 100))
# when the checkbox is toggled, update the label
def update_checkbox(ctx=None, wid=None, text=None):
checked = "checked" if mqt.IsWidgetChecked(check) else "unchecked"
mqt.SetWidgetText(output_label, f"checkbox is {checked}")
# create three widgets in this group
butt = add_widget(layout, mqt.CreateButton(update_button))
check = add_widget(layout, mqt.CreateCheckbox(update_checkbox))
output_label = add_widget(layout, mqt.CreateLabel(), "checkbox is ????")
# create a second group of read only widgets
group = add_widget(top, mqt.CreateGroupBox(True), "Display Widgets")
lab = add_widget(group, mqt.CreateLabel(), "A label with a funky font")
mqt.SetWidgetFont(lab, "Comic Sans MS", 15, False, True)
prog = add_widget(group, mqt.CreateProgressBar(True))
mqt.SetProgressBarRange(prog, 0, 100)
readonly_text = add_widget(
group, mqt.CreateTextBox(True), "This text box is read only!"
)
mqt.SetWidgetEnabled(readonly_text, False)
# initialise the button text to start with
update_button()
+133
View File
@@ -0,0 +1,133 @@
# 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)
pipe = pyrenderdoc.CurPipelineState()
get_name = lambda id: pyrenderdoc.GetResourceName(id)
print("-------------------------")
print(" Outputs ")
print("-------------------------")
# list all the output targets
outs = pipe.GetOutputTargets()
for i, out in enumerate(outs):
id = out.resource
# ignore any targets that are unbound
if id != renderdoc.ResourceId():
print(f"Out {i}: {get_name(id)}")
id = pipe.GetDepthTarget().resource
print(f"Depth: {get_name(id)}")
print()
print("-------------------------")
print(" Pipeline/Shaders ")
print("-------------------------")
id = pipe.GetGraphicsPipelineObject()
print(f"Pipeline: {get_name(id)}")
id = pipe.GetShader(renderdoc.ShaderStage.Vertex)
print(f"VS: {get_name(id)}")
id = pipe.GetShader(renderdoc.ShaderStage.Pixel)
print(f"PS: {get_name(id)}")
print()
print("-------------------------")
print(" Constant Blocks (VS) ")
print("-------------------------")
cbs = pipe.GetConstantBlocks(renderdoc.ShaderStage.Vertex)
# print out the stage even though we've asked for vertex CBs.
# the index corresponds to which constant block in the vertex
# reflection was used (omitted here is the array index which
# doesn't affect that lookup)
for cb in cbs:
print(
f"{str(cb.access.stage)} CB[{cb.access.index}]: {get_name(cb.descriptor.resource)}"
)
print()
print("-------------------------")
print(" Descriptors by Stage ")
print("-------------------------")
# ask for all descriptors but only those that are used
descs = pipe.GetAllUsedDescriptors(True)
# first we'll iterate over all possible shader stages, and get all
# the descriptors for that stage
for stage in renderdoc.ShaderStage:
# silently skip stages with no used bindings
stage_descs = [d for d in descs if d.access.stage == stage]
if stage_descs == []:
continue
# now iterate over the descriptors and print its type and the resources
# in the descriptor
print(f"** {str(stage)} descriptors:")
for d in stage_descs:
desc_str = f"{str(d.access.type)} - "
if (
d.sampler.object != renderdoc.ResourceId()
and d.descriptor.resource != renderdoc.ResourceId()
):
desc_str += (
f"{get_name(d.descriptor.resource)} + {get_name(d.sampler.object)}"
)
elif d.sampler.object != renderdoc.ResourceId():
desc_str += f"{get_name(d.sampler.object)}"
else:
desc_str += f"{get_name(d.descriptor.resource)}"
print(desc_str)
# we also print the descriptor store this is stored in
print(
f" in {get_name(d.access.descriptorStore)} at offset {d.access.byteOffset}"
)
print()
print("-------------------------")
print(" Descriptors by Type ")
print("-------------------------")
# Iterate in a similar way, but this time grouping by descriptor type
for desctype in renderdoc.DescriptorType:
type_descs = [d for d in descs if d.access.type == desctype]
if type_descs == []:
continue
print(f"** {str(desctype)} descriptors:")
for d in type_descs:
desc_str = f"{str(d.access.stage)} - "
if (
d.sampler.object != renderdoc.ResourceId()
and d.descriptor.resource != renderdoc.ResourceId()
):
desc_str += (
f"{get_name(d.descriptor.resource)} + {get_name(d.sampler.object)}"
)
elif d.sampler.object != renderdoc.ResourceId():
desc_str += f"{get_name(d.sampler.object)}"
else:
desc_str += f"{get_name(d.descriptor.resource)}"
print(desc_str)
print(
f" in {get_name(d.access.descriptorStore)} at offset {d.access.byteOffset}"
)
@@ -0,0 +1,54 @@
# 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)
pipe = pyrenderdoc.CurPipelineState()
depth = pipe.GetDepthTarget().resource
ib = pipe.GetIBuffer().resourceId
if depth == renderdoc.ResourceId() or ib == renderdoc.ResourceId():
raise RuntimeError(
"Can't run example!\n"
"Current event doesn't use both index buffer and depth target"
)
eid = pyrenderdoc.CurEvent()
controller = pyrenderdoc.GetBlockingController()
for name, id in [("Depth Target", depth), ("Index Buffer", ib)]:
usagelist = controller.GetUsage(id)
cur_usage = next(u for u in usagelist if u.eventId == eid).usage
prev_usages = [u for u in usagelist if u.eventId < eid and u.usage != cur_usage]
later_usages = [u for u in usagelist if u.eventId > eid and u.usage != cur_usage]
if len(prev_usages) == 0:
print(f"{name} {pyrenderdoc.GetResourceName(id)} was never used before {eid}!")
else:
print(
f"{name} {pyrenderdoc.GetResourceName(id)} was used as "
f"{str(prev_usages[-1].usage)} at {str(prev_usages[-1].eventId)}."
)
if len(later_usages) == 0:
print(f"{name} {pyrenderdoc.GetResourceName(id)} is never used after {eid}!")
else:
print(
f"{name} {pyrenderdoc.GetResourceName(id)} will be used as "
f"{str(later_usages[0].usage)} at {str(later_usages[0].eventId)}."
)
+111
View File
@@ -0,0 +1,111 @@
# 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)
pyrenderdoc.CurPipelineState().GetVertexInputs()
vs = pyrenderdoc.CurPipelineState().GetShaderReflection(renderdoc.ShaderStage.Vertex)
ps = pyrenderdoc.CurPipelineState().GetShaderReflection(renderdoc.ShaderStage.Pixel)
if vs is None or ps is None:
raise ValueError("Expected a draw with a VS and PS to be selected")
for vin in vs.inputSignature:
name = vin.varName
if name == "":
name = vin.semanticIdxName
print(
f"Vertex input {name} is {str(vin.varType)} x {vin.compCount} "
f"at register {vin.regIndex}"
)
print()
for vout in vs.outputSignature:
name = vout.varName
if name == "":
name = vout.semanticIdxName
print(
f"Vertex input {name} is {str(vout.varType)} x {vout.compCount} "
f"at register {vout.regIndex}"
)
print()
print(f"VS has {len(vs.constantBlocks)} constant blocks declared")
if len(vs.constantBlocks) > 0:
cb = vs.constantBlocks[0]
print(
f" First is named {cb.name} "
f"at {cb.fixedBindSetOrSpace}:{cb.fixedBindNumber}"
)
if cb.compileConstants:
print(" (compile-time constants)")
elif not cb.bufferBacked:
print(" (runtime non-buffer temp data)")
else:
print(f" (from a buffer, expected {cb.byteSize} bytes)")
print(f" containing {len(cb.variables)} variables")
if len(cb.variables) > 0:
var = cb.variables[0]
print(f" the first is named {var.name} at offset {var.byteOffset}")
print(
f" type {str(var.type.baseType)} "
f"dimension {var.type.rows}x{var.type.columns}"
)
print()
print(f"PS has {len(ps.readOnlyResources)} R/O resources")
if len(ps.readOnlyResources) > 0:
res = ps.readOnlyResources[0]
print(
f" First is named {res.name} "
f"at {res.fixedBindSetOrSpace}:{res.fixedBindNumber}"
)
print(f" declared as {str(res.textureType)} of {res.variableType.baseType}")
if res.hasSampler:
print(f" ++ has attached sampler")
print(f"PS has {len(ps.samplers)} samplers")
if len(ps.samplers) > 0:
samp = ps.samplers[0]
print(
f" First is named {samp.name} "
f"at {samp.fixedBindSetOrSpace}:{samp.fixedBindNumber}"
)
print()
print(f"PS was compiled by {renderdoc.ToolExecutable(ps.debugInfo.compiler)}")
print(f"{str(ps.debugInfo.encoding)} was compiled to {str(ps.encoding)}")
if ps.debugInfo.debuggable:
print("PS is debuggable!")
else:
print(f"PS can't be debugged: {ps.debugInfo.debugStatus}")
+57
View File
@@ -0,0 +1,57 @@
import struct
# 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)
mybuf = renderdoc.ResourceId.Null()
for buf in pyrenderdoc.GetBuffers():
print(f"buf {buf.resourceId} is {pyrenderdoc.GetResourceName(buf.resourceId)}")
mybuf = buf.resourceId
# here put your actual selection criteria - i.e. look for a particular name
if "Vertex" in pyrenderdoc.GetResourceName(buf.resourceId):
break
print(f"selected {pyrenderdoc.GetResourceName(mybuf)}")
formatter = """
float3 pos;
half norms[6];
uint flags;
"""
if mybuf != renderdoc.ResourceId.Null():
# Open a new buffer viewer for this buffer, with the given format
bufview = pyrenderdoc.ViewBuffer(0, 0, mybuf, formatter)
# Show the buffer viewer on the main tool area
pyrenderdoc.AddDockWindow(
bufview.Widget(), qrenderdoc.DockReference.MainToolArea, None
)
# Get access to a controller to get the buffer data.
# We use the blocking controller for simplicity, but a better option
# might be to invoke onto the replay thread with
# pyrenderdoc.Replay().AsyncInvoke()
controller = pyrenderdoc.GetBlockingController()
data_bytes = controller.GetBufferData(mybuf, 0, 8)
data_decoded = struct.unpack_from("8B", data_bytes)
print(f"The first 8 bytes of the buffer are: {data_decoded}")
+76
View File
@@ -0,0 +1,76 @@
# 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)
highestArea = 0
largest = None
for tex in pyrenderdoc.GetTextures():
name = pyrenderdoc.GetResourceName(tex.resourceId)
print(f"{name} is {tex.width} x {tex.height}")
area = tex.width * tex.height
if area > highestArea:
highestArea = area
largest = tex
if largest is not None:
name = pyrenderdoc.GetResourceName(largest.resourceId)
print(f"\n+++ Largest texture is {name}")
# open largest texture (by area) in texture viewer, and focus
pyrenderdoc.ShowTextureViewer()
pyrenderdoc.GetTextureViewer().ViewTexture(
largest.resourceId, renderdoc.CompType.Typeless, True
)
# Get access to a controller to get the texture saving API access.
# We use the blocking controller for simplicity, but a better option
# might be to invoke onto the replay thread with
# pyrenderdoc.Replay().AsyncInvoke()
controller = pyrenderdoc.GetBlockingController()
filename = pyrenderdoc.Extensions().SaveFileName(
"Choose where to save JPG/PNG/DDS texture files", "", "*.jpg"
)
filename = filename.replace(".jpg", "")
texsave = renderdoc.TextureSave()
texsave.resourceId = largest.resourceId
# Blend alpha to a checkerboard pattern for formats without alpha support
texsave.alpha = renderdoc.AlphaMapping.BlendToCheckerboard
# Most formats can only display a single image per file, so we select the
# first mip and first slice
texsave.mip = 0
texsave.slice.sliceIndex = 0
texsave.destType = renderdoc.FileType.JPG
controller.SaveTexture(texsave, filename + ".jpg")
# For formats with an alpha channel, preserve it
texsave.alpha = renderdoc.AlphaMapping.Preserve
texsave.destType = renderdoc.FileType.PNG
controller.SaveTexture(texsave, filename + ".png")
# DDS textures can save multiple mips and array slices, so instead
# of the default behaviour of saving mip 0 and slice 0, we set -1
# which saves *all* mips and slices
texsave.mip = -1
texsave.slice.sliceIndex = -1
texsave.destType = renderdoc.FileType.DDS
controller.SaveTexture(texsave, filename + ".dds")
+12
View File
@@ -1,5 +1,17 @@
<RCC>
<qresource prefix="/py">
<file alias="examples/show_buffer.py">../../docs/python_api/examples/show_buffer.py</file>
<file alias="examples/show_texture.py">../../docs/python_api/examples/show_texture.py</file>
<file alias="examples/iter_actions.py">../../docs/python_api/examples/iter_actions.py</file>
<file alias="examples/pipe_state.py">../../docs/python_api/examples/pipe_state.py</file>
<file alias="examples/shader_refl.py">../../docs/python_api/examples/shader_refl.py</file>
<file alias="examples/resource_usage.py">../../docs/python_api/examples/resource_usage.py</file>
<file alias="examples/mem_binds.py">../../docs/python_api/examples/mem_binds.py</file>
<file alias="examples/history_debug.py">../../docs/python_api/examples/history_debug.py</file>
<file alias="examples/mesh_output.py">../../docs/python_api/examples/mesh_output.py</file>
<file alias="examples/miniqt_ui.py">../../docs/python_api/examples/miniqt_ui.py</file>
<file alias="examples/exe_launching.py">../../docs/python_api/examples/exe_launching.py</file>
<file alias="examples/event_filter.py">../../docs/python_api/examples/event_filter.py</file>
<file alias="stubgen.py">../../docs/stubgen.py</file>
<file alias="parse_reflection.py">../Code/pyrenderdoc/parse_reflection.py</file>
</qresource>
+14 -1
View File
@@ -325,7 +325,20 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
m_Examples->setIcon(0, Icons::help());
const QPair<QString, QString> examples[] = {
{tr("Show a Buffer"), lit("Example will go here")},
{tr("Tutorial: First Steps with Python"), lit(":/py/tutorial/first_steps.py")},
{tr("Tutorial: UI extensions"), lit(":/py/tutorial/ui_extensions.py")},
{tr("Show buffer with format"), lit(":/py/examples/show_buffer.py")},
{tr("Show and save a texture"), lit(":/py/examples/show_texture.py")},
{tr("Iterating over Actions"), lit(":/py/examples/iter_actions.py")},
{tr("Pipeline State"), lit(":/py/examples/pipe_state.py")},
{tr("Shader Reflection"), lit(":/py/examples/shader_refl.py")},
{tr("Resource Usage"), lit(":/py/examples/resource_usage.py")},
{tr("Memory bindings"), lit(":/py/examples/mem_binds.py")},
{tr("Pixel History & Shader Debug"), lit(":/py/examples/history_debug.py")},
{tr("Mesh Output"), lit(":/py/examples/mesh_output.py")},
{tr("Launching an application"), lit(":/py/examples/exe_launching.py")},
{tr("Custom event filter"), lit(":/py/examples/event_filter.py")},
{tr("Mini-Qt UI"), lit(":/py/examples/miniqt_ui.py")},
};
for(const QPair<QString, QString> &example : examples)