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 contains the index of the parent drawcall.

The second is to use previous and next, which contain the indices of 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 renderdoc as rd

# Set up a hash for storing draw information by event
draws = {}

# Define a recursive function for iterating over draws
def iterDraw(d, indent = ''):
	global draws

	# Print this drawcall
	print('%s%d: %s' % (indent, d.eventId, d.name))

	# Save the draw by eventId for use later
	draws[d.eventId] = d

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

def sampleCode(controller):
	global draws

	# 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
		if not draw.next in draws:
			break
		draw = draws[draw.next]

	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 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