Display texture in window

In this example we will open a window and iterate through the capture on a loop, displaying the first colour output target on it.

Note

This is intended for use with the python module directly, as the UI already has a texture viewer panel to do this with much more control. The principle is the same though and it can be useful reference of how to iterate over a capture.

To create a window we use tkinter, since it is provided with the Python distribution.

# Use tkinter to create windows
import tkinter

# Create a simple window
window = tkinter.Tk()
window.geometry("1280x720")

Next we need to determine which windowing systems the RenderDoc implementation supports, and create a WindowingData object for the window we want to render to. For the purposes of this example we will look for Win32 since it’s the simplest to set up - needing only a window handle that we can get from tkinter easily. XCB/XLib require a display connection, which would be possible to get from another library such as Qt.

Once we have the WindowingData, we can create a ReplayOutput using CreateOutput().

# Create renderdoc windowing data.
winsystems = [rd.WindowingSystem(i) for i in controller.GetSupportedWindowSystems()]

# Pass window system specific data here, See:
# - renderdoc.CreateWin32WindowingData
# - renderdoc.CreateXlibWindowingData
# - renderdoc.CreateXCBWindowingData

# This example code works on windows as that's simple to integrate with tkinter
if not rd.WindowingSystem.Win32 in winsystems:
    raise RuntimeError("Example requires Win32 windowing system: " + str(winsystems))

windata = rd.CreateWin32WindowingData(int(window.frame(), 16))

# Create a texture output on the window
out = controller.CreateOutput(windata, rd.ReplayOutputType.Texture)

In order to iterate over all actions we need some global state first from GetTextures() and GetRootActions(), and we’ll also define a helper function to fetch a particular texture by resourceId, so that we can easily look up the details for a texture.

# Fetch the list of textures
textures = controller.GetTextures()

# Fetch the list of actions
actions = controller.GetRootActions()

# Function to look up the texture descriptor for a given resourceId
def getTexture(texid):
    global textures
    for tex in textures:
        if tex.resourceId == texid:
            return tex
    return None

We now define two callback functions - paint and advance. paint will be called every 33ms, it will display the output with the latest state using Display(). advance changes the current state to reflect a new action.

# Our paint function will be called ever 33ms, to display the output
def paint():
    global out, window
    out.Display()
    window.after(33, paint)

Within advance we do a few things. First we move the current event to the current action’s eventId, using SetFrameEvent(). Then we set up the texture display configuration with SetTextureDisplay(), to point to the first colour output at that action.

When we update to a new texture, we fetch its details using our earlier getTexture and calculate a scale that keeps the texture fully visible on screen.

Finally we move to the next action in the list for the next time advance is called.

# Start on the first action
curact = actions[0]

loopcount = 0

# The advance function will be called every 50ms, to move to the next action
def advance():
    global out, window, curact, actions, loopcount

    # Move to the current action
    controller.SetFrameEvent(curact.eventId, False)

    # Initialise a default TextureDisplay object
    disp = rd.TextureDisplay()

    # Set the first colour output as the texture to display
    disp.resourceId = curact.outputs[0]

    if disp.resourceId != rd.ResourceId.Null():
        # Get the details of this texture
        texDetails = getTexture(disp.resourceId)

        # Calculate the scale required in width and height
        widthScale = window.winfo_width() / texDetails.width
        heightScale = window.winfo_height() / texDetails.height

        # Use the lower scale to fit the texture on the window
        disp.scale = min(widthScale, heightScale)

        # Update the texture display
        out.SetTextureDisplay(disp)

    # Set the next action
    curact = curact.next

    # If we have no next action, start again from the first
    if curact is None:
        loopcount = loopcount + 1
        curact = actions[0]

    # after 3 loops, quit
    if loopcount == 3:
        window.quit()
    else:
        window.after(50, advance)

Once we have the callbacks defined, we call them once to initialise the display and set up the repeated callbacks, and start the tkinter main window loop.

# Start the callbacks
advance()
paint()

# Start the main window loop
window.mainloop()

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

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

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

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

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

	# Initialise the replay
	result,controller = cap.OpenCapture(rd.ReplayOptions(), None)

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

	return cap,controller

if 'pyrenderdoc' in globals():
	raise RuntimeError("This sample should not be run within the RenderDoc UI")
else:
	if len(sys.argv) <= 1:
		print('Usage: python3 {} filename.rdc'.format(sys.argv[0]))
		sys.exit(0)

	rd.InitialiseReplay(rd.GlobalEnvironment(), [])

	cap,controller = loadCapture(sys.argv[1])

# Use tkinter to create windows
import tkinter

# Create a simple window
window = tkinter.Tk()
window.geometry("1280x720")

# Create renderdoc windowing data.
winsystems = [rd.WindowingSystem(i) for i in controller.GetSupportedWindowSystems()]

# Pass window system specific data here, See:
# - renderdoc.CreateWin32WindowingData
# - renderdoc.CreateXlibWindowingData
# - renderdoc.CreateXCBWindowingData

# This example code works on windows as that's simple to integrate with tkinter
if not rd.WindowingSystem.Win32 in winsystems:
    raise RuntimeError("Example requires Win32 windowing system: " + str(winsystems))

windata = rd.CreateWin32WindowingData(int(window.frame(), 16))

# Create a texture output on the window
out = controller.CreateOutput(windata, rd.ReplayOutputType.Texture)

# Fetch the list of textures
textures = controller.GetTextures()

# Fetch the list of actions
actions = controller.GetRootActions()

# Function to look up the texture descriptor for a given resourceId
def getTexture(texid):
	global textures
	for tex in textures:
		if tex.resourceId == texid:
			return tex
	return None

# Our paint function will be called ever 33ms, to display the output
def paint():
	global out, window
	out.Display()
	window.after(33, paint)

# Start on the first action
curact = actions[0]

loopcount = 0

# The advance function will be called every 50ms, to move to the next action
def advance():
	global out, window, curact, actions, loopcount

	# Move to the current action
	controller.SetFrameEvent(curact.eventId, False)

	# Initialise a default TextureDisplay object
	disp = rd.TextureDisplay()

	# Set the first colour output as the texture to display
	disp.resourceId = curact.outputs[0]

	if disp.resourceId != rd.ResourceId.Null():
		# Get the details of this texture
		texDetails = getTexture(disp.resourceId)

		# Calculate the scale required in width and height
		widthScale = window.winfo_width() / texDetails.width
		heightScale = window.winfo_height() / texDetails.height

		# Use the lower scale to fit the texture on the window
		disp.scale = min(widthScale, heightScale)

		# Update the texture display
		out.SetTextureDisplay(disp)

	# Set the next action
	curact = curact.next

	# If we have no next action, start again from the first
	if curact is None:
		loopcount = loopcount + 1
		curact = actions[0]

	# after 3 loops, quit
	if loopcount == 3:
		window.quit()
	else:
		window.after(50, advance)

# Start the callbacks
advance()
paint()

# Start the main window loop
window.mainloop()

controller.Shutdown()

cap.Shutdown()

if 'pyrenderdoc' not in globals():
	rd.ShutdownReplay()