# 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 drawcalls we need some global state first from `GetTextures()` and `GetDrawcalls()`, 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 drawcalls
    draws = controller.GetDrawcalls()

    # 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 drawcall.

    # 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 drawcall’s `eventId`, using `SetFrameEvent()`. Then we set up the texture display configuration with `SetTextureDisplay()`, to point to the first colour output at that drawcall.

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 drawcall in the list for the next time `advance` is called.

    # Start on the first drawcall
    curdraw = 0

    # The advance function will be called every 150ms, to move to the next draw
    def advance():
        global out, window, curdraw

        # Move to the current drawcall
        controller.SetFrameEvent(draws[curdraw].eventId, False)

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

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

        # 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 drawcall
        curdraw = (curdraw + 1) % len(draws)

        window.after(150, 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
    	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(rd.ReplayOptions(), None)

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

    	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 drawcalls
    draws = controller.GetDrawcalls()

    # 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 drawcall
    curdraw = draws[0]

    loopcount = 0

    # The advance function will be called every 50ms, to move to the next draw
    def advance():
    	global out, window, curdraw, draws, loopcount

    	# Move to the current drawcall
    	controller.SetFrameEvent(curdraw.eventId, False)

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

    	# Set the first colour output as the texture to display
    	disp.resourceId = curdraw.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 drawcall
    	curdraw = curdraw.next

    	# If we have no next draw, start again from the first
    	if curdraw is None:
    		loopcount = loopcount + 1
    		curdraw = draws[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()