Iterate drawcall tree

In this example we will show how to iterate over drawcalls.

The drawcalls returned from GetDrawcalls() are draws or marker regions at the root level - with no parent marker region. There are multiple ways to iterate through the list of draws.

The first way illustrated in this sample is to walk the tree using children, which contains the list of child draws at any point in the tree. There is also parent which points to the parent drawcall.

The second is to use previous and next, which point to the previous and next draw respectively in a linear fashion, regardless of nesting depth.

In the example we use this iteration to determine the number of passes, using the drawcall flags to denote the start of each pass by a starting clear call.

Example Source

Download the example script.

import sys

# Import renderdoc if not already imported (e.g. in the UI)
if 'renderdoc' not in sys.modules and '_renderdoc' not in sys.modules:
	import renderdoc

# Alias renderdoc for legibility
rd = renderdoc

# Define a recursive function for iterating over draws
def iterDraw(d, indent = ''):
	# Print this drawcall
	print('%s%d: %s' % (indent, d.eventId, d.name))

	# Iterate over the draw's children
	for d in d.children:
		iterDraw(d, indent + '    ')

def sampleCode(controller):
	# Iterate over all of the root drawcalls
	for d in controller.GetDrawcalls():
		iterDraw(d)

	# Start iterating from the first real draw as a child of markers
	draw = controller.GetDrawcalls()[0]

	while len(draw.children) > 0:
		draw = draw.children[0]

	# Counter for which pass we're in
	passnum = 0
	# Counter for how many draws are in the pass
	passcontents = 0
	# Whether we've started seeing draws in the pass - i.e. we're past any
	# starting clear calls that may be batched together
	inpass = False

	print("Pass #0 starts with %d: %s" % (draw.eventId, draw.name))

	while draw != None:
		# When we encounter a clear
		if draw.flags & rd.DrawFlags.Clear:
			if inpass:
				print("Pass #%d contained %d draws" % (passnum, passcontents))
				passnum += 1
				print("Pass #%d starts with %d: %s" % (passnum, draw.eventId, draw.name))
				passcontents = 0
				inpass = False
		else:
			passcontents += 1
			inpass = True

		# Advance to the next drawcall
		draw = draw.next
		if draw is None:
			break

	if inpass:
		print("Pass #%d contained %d draws" % (passnum, passcontents))

def loadCapture(filename):
	# Open a capture file handle
	cap = rd.OpenCaptureFile()

	# Open a particular file - see also OpenBuffer to load from memory
	status = cap.OpenFile(filename, '', None)

	# Make sure the file opened successfully
	if status != rd.ReplayStatus.Succeeded:
		raise RuntimeError("Couldn't open file: " + str(status))

	# Make sure we can replay
	if not cap.LocalReplaySupport():
		raise RuntimeError("Capture cannot be replayed")

	# Initialise the replay
	status,controller = cap.OpenCapture(None)

	if status != rd.ReplayStatus.Succeeded:
		raise RuntimeError("Couldn't initialise replay: " + str(status))

	return cap,controller

if 'pyrenderdoc' in globals():
	pyrenderdoc.Replay().BlockInvoke(sampleCode)
else:
	cap,controller = loadCapture('test.rdc')

	sampleCode(controller)

	controller.Shutdown()
	cap.Shutdown()

Sample output:

1: Scene
    2: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
    3: ClearDepthStencilView(D=1.000000, S=00)
    9: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
    10: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
    11: ClearDepthStencilView(D=1.000000, S=00)
    13: GBuffer
        28: DrawIndexed(4800)
        43: DrawIndexed(36)
        56: DrawIndexed(5220)
        69: DrawIndexed(5580)
        82: DrawIndexed(5580)
        95: DrawIndexed(5580)
        108: DrawIndexed(5580)
        121: DrawIndexed(5580)
        134: DrawIndexed(5580)
        147: DrawIndexed(5580)
        160: DrawIndexed(5580)
        173: DrawIndexed(5580)
        186: DrawIndexed(5580)
        199: DrawIndexed(5220)
        212: DrawIndexed(5220)
        225: DrawIndexed(5220)
        238: DrawIndexed(5220)
        251: DrawIndexed(5220)
        264: DrawIndexed(5220)
        277: DrawIndexed(5220)
        290: DrawIndexed(5220)
        303: DrawIndexed(5220)
        316: DrawIndexed(5220)
    319: ClearDepthStencilView(D=1.000000, S=00)
    321: Shadowmap
        333: DrawIndexed(4800)
        345: DrawIndexed(36)
        355: DrawIndexed(5220)
        365: DrawIndexed(5580)
        375: DrawIndexed(5580)
        385: DrawIndexed(5580)
        395: DrawIndexed(5580)
        405: DrawIndexed(5580)
        415: DrawIndexed(5580)
        425: DrawIndexed(5580)
        435: DrawIndexed(5580)
        445: DrawIndexed(5580)
        455: DrawIndexed(5580)
        465: DrawIndexed(5220)
        475: DrawIndexed(5220)
        485: DrawIndexed(5220)
        495: DrawIndexed(5220)
        505: DrawIndexed(5220)
        515: DrawIndexed(5220)
        525: DrawIndexed(5220)
        535: DrawIndexed(5220)
        545: DrawIndexed(5220)
        555: DrawIndexed(5220)
    558: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
    559: ClearDepthStencilView(D=1.000000, S=00)
    561: Lighting
        563: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
        564: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
        580: DrawIndexed(36)
        597: DrawIndexed(36)
        614: DrawIndexed(36)
    617: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
    618: ClearDepthStencilView(D=1.000000, S=00)
    620: Shading
        630: Draw(6)
        645: DrawIndexed(960)
        652: API Calls
655: End of Frame
Pass #0 starts with 2: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
Pass #0 contained 24 draws
Pass #1 starts with 319: ClearDepthStencilView(D=1.000000, S=00)
Pass #1 contained 24 draws
Pass #2 starts with 558: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
Pass #2 contained 3 draws
Pass #3 starts with 617: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
Pass #3 contained 4 draws