mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-24 07:26:34 +00:00
Add new python examples, also available embedded into UI
This commit is contained in:
@@ -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,
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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]}")
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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)}."
|
||||
)
|
||||
@@ -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}")
|
||||
@@ -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}")
|
||||
@@ -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")
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user