From 11b83f227250c80de86c17b45554155eacad938a Mon Sep 17 00:00:00 2001 From: baldurk Date: Tue, 23 Jun 2026 15:54:35 +0100 Subject: [PATCH] Remove old python documentation, ready for replacement --- docs/imgs/python_ext/Step1.png | Bin 29329 -> 0 bytes docs/imgs/python_ext/Step2.png | Bin 14043 -> 0 bytes docs/python_api/descriptors_bindings.rst | 150 --------- docs/python_api/dev_environment.rst | 63 ---- docs/python_api/examples/basics.rst | 54 ---- docs/python_api/examples/qrenderdoc/index.rst | 7 - .../examples/qrenderdoc/show_buffer.py | 23 -- .../examples/qrenderdoc/show_buffer.rst | 50 --- docs/python_api/examples/qrenderdoc_intro.rst | 52 --- .../examples/renderdoc/decode_mesh.py | 291 ----------------- .../examples/renderdoc/decode_mesh.rst | 304 ------------------ .../examples/renderdoc/display_window.py | 146 --------- .../examples/renderdoc/display_window.rst | 150 --------- .../examples/renderdoc/fetch_counters.py | 107 ------ .../examples/renderdoc/fetch_counters.rst | 119 ------- .../examples/renderdoc/fetch_shader.py | 98 ------ .../examples/renderdoc/fetch_shader.rst | 207 ------------ docs/python_api/examples/renderdoc/index.rst | 35 -- .../examples/renderdoc/iter_actions.py | 101 ------ .../examples/renderdoc/iter_actions.rst | 161 ---------- .../examples/renderdoc/remote_capture.py | 200 ------------ .../examples/renderdoc/remote_capture.rst | 118 ------- .../examples/renderdoc/save_texture.py | 116 ------- .../examples/renderdoc/save_texture.rst | 25 -- docs/python_api/examples/renderdoc_intro.py | 32 -- docs/python_api/examples/renderdoc_intro.rst | 133 -------- docs/python_api/index.rst | 41 --- .../ui_extension_tutorial/__init__.py | 151 --------- .../ui_extension_tutorial/extension.json | 9 - docs/python_api/ui_extensions.rst | 244 +------------- 30 files changed, 2 insertions(+), 3185 deletions(-) delete mode 100644 docs/imgs/python_ext/Step1.png delete mode 100644 docs/imgs/python_ext/Step2.png delete mode 100644 docs/python_api/descriptors_bindings.rst delete mode 100644 docs/python_api/dev_environment.rst delete mode 100644 docs/python_api/examples/basics.rst delete mode 100644 docs/python_api/examples/qrenderdoc/index.rst delete mode 100644 docs/python_api/examples/qrenderdoc/show_buffer.py delete mode 100644 docs/python_api/examples/qrenderdoc/show_buffer.rst delete mode 100644 docs/python_api/examples/qrenderdoc_intro.rst delete mode 100644 docs/python_api/examples/renderdoc/decode_mesh.py delete mode 100644 docs/python_api/examples/renderdoc/decode_mesh.rst delete mode 100644 docs/python_api/examples/renderdoc/display_window.py delete mode 100644 docs/python_api/examples/renderdoc/display_window.rst delete mode 100644 docs/python_api/examples/renderdoc/fetch_counters.py delete mode 100644 docs/python_api/examples/renderdoc/fetch_counters.rst delete mode 100644 docs/python_api/examples/renderdoc/fetch_shader.py delete mode 100644 docs/python_api/examples/renderdoc/fetch_shader.rst delete mode 100644 docs/python_api/examples/renderdoc/index.rst delete mode 100644 docs/python_api/examples/renderdoc/iter_actions.py delete mode 100644 docs/python_api/examples/renderdoc/iter_actions.rst delete mode 100644 docs/python_api/examples/renderdoc/remote_capture.py delete mode 100644 docs/python_api/examples/renderdoc/remote_capture.rst delete mode 100644 docs/python_api/examples/renderdoc/save_texture.py delete mode 100644 docs/python_api/examples/renderdoc/save_texture.rst delete mode 100644 docs/python_api/examples/renderdoc_intro.py delete mode 100644 docs/python_api/examples/renderdoc_intro.rst delete mode 100644 docs/python_api/ui_extension_tutorial/__init__.py delete mode 100644 docs/python_api/ui_extension_tutorial/extension.json diff --git a/docs/imgs/python_ext/Step1.png b/docs/imgs/python_ext/Step1.png deleted file mode 100644 index 3c263a240cdded92899283cd515b8eba83cb7a7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29329 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 diff --git a/docs/imgs/python_ext/Step2.png b/docs/imgs/python_ext/Step2.png deleted file mode 100644 index 4d69e98d29e3d8435f03466e386d226574cb42ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14043 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. Take note of the python version that you build against, since this will need to be used later. - -.. note:: - On windows by default RenderDoc builds against python 3.6 which is what it's distributed with. - - This can be overridden by setting an overridden path under the ``Python Configuration`` section in the properties of the ``qrenderdoc`` project and ``pyrenderdoc_module``/``qrenderdoc_module`` projects. It must point to a python installation. - - RenderDoc requires ``pythonXY.lib``, include files such as include/Python.h, as well as a .zip of the standard library. If you installed python with an installer you have the first two, and can generate the standard library zip by zipping the contents of the Lib folder. If you downloaded the embeddable zip distribution you will only have the standard library zip, you need to obtain the include files and ``.lib`` file separately. - -Python Setup for VS Code ------------------------- -Using the same python version as the RenderDoc build used (Python 3.6 by default). -From inside the `docs` folder run the following command: -`python3 regenerate_stubs.py `` -After running the command the output folder `` should contain `renderdoc` and `qrenderdoc` folders. - -In `VS Code` change the setting `python.analysis.extraPaths`` and add the output path used in the previous command (``). - -Now when viewing a python script in `VS Code`, you should see the `RenderDoc` types being correctly resolved and autocompleted. - -If you get a warning about failure to import module `renderdoc` then something is not correct in the setup, double check the `VS Code` setting `python.analysis.extraPaths` includes the `RenderDoc` python API parsed output folder (which should contain two folders `renderdoc` and `qrenderdoc`). - -Configuring python module for PyCharm -------------------------------------- - -Python Setup for PyCharm ------------------------- - -Now install PyCharm, for this document we will install 2020.3.2. Any version is fine, though newer versions may require modification to work properly. - -Before you run PyCharm, we will replace one file in it to generate better type information for RenderDoc. In the RenderDoc repository there's a `pycharm_helpers folder `_. Copying the content of the plugins folder over the folder in your PyCharm installation will update the file that is customised. You can back it up beforehand at this path: ``plugins/python-ce/helpers/generator3/module_redeclarator.py``. - -If you're using a different version of PyCharm you can try to apply the patch also available in that folder. - -Configuring python module for PyCharm -------------------------------------- - -You can now launch PyCharm and open or create the python project where you'll be writing code. Now we'll configure the python interpreter. This must match the python version that you built against above - the same major and minor version, and the same bitness (32-bit to 32-bit or 64-bit to 64-bit). - -Go into :guilabel:`File` enter :guilabel:`Settings`. On the left you can go into the project and to :guilabel:`Python Interpreter`. - -In the first entry click the gear next to :guilabel:`Python Interpreter` and choose :guilabel:`Add` if the interpreter you want isn't available. You can now configure a System Interpreter with the correct version as above. - -Once you've chosen the correct interpreter we'll also tell it where to find the RenderDoc libraries since they won't be in the default python path. Go back to the gear wheel but this time select :guilabel:`Show All`. With your chosen interpreter selected click on the tree icon at the bottom labeled ``Show paths for the selected interpreter`` and add the directory where you have the ``renderdoc`` and ``qrenderdoc`` modules, as well as the ``renderdoc`` library. - -If everything went well, PyCharm should load that interpreter for the project and discover the renderdoc python modules. It will then generate stubs for them with correct typing information so you can benefit from proper autocomplete while writing python code. - -Troubleshooting PyCharm ------------------------ - -If you get an error about "No module named 'renderdoc'" then something has gone wrong with how the interpreter finds and loads the python module. Ensure you have the right path specified and that the interpreter is the correctly matching version for the python module you compiled. - -To regenerate the generated python stubs delete your ``python_stubs`` folder in the JetBrains local cache. On windows this is in ``%LOCALAPPDATA%/JetBrains``. diff --git a/docs/python_api/examples/basics.rst b/docs/python_api/examples/basics.rst deleted file mode 100644 index 35b2ff124..000000000 --- a/docs/python_api/examples/basics.rst +++ /dev/null @@ -1,54 +0,0 @@ -Basic Interfaces -================ - -This document explains some common interfaces and their relationship, which can be useful as a primer to understand where to get started, as well as for reference to look back on from later examples. - -Replay Basics -------------- - -ReplayController -^^^^^^^^^^^^^^^^ - -The primary interface for accessing the low level of RenderDoc's replay analysis is :py:class:`~renderdoc.ReplayController`. - -From this interface, information about the capture can be gathered, using e.g. :py:meth:`~renderdoc.ReplayController.GetRootActions` to return the list of root-level actions in the frame, or :py:meth:`~renderdoc.ReplayController.GetResources` to return a list of all resources in the capture. - -Some methods like the two above return information which is global and does not vary across the frame. Most functions however return information relative to the current point in the frame. - -During RenderDoc's replay, you can imagine a cursor that moves back and forth between the start and end of the frame. All requests for information that varies - such as texture and buffer contents, pipeline state, and other information will be relative to the current event. - -Every function call within a frame is assigned an ascending ``eventId``, from ``1`` up to as many events as are in the frame. Within the action list returned by :py:meth:`~renderdoc.ReplayController.GetRootActions`, each action contains a list of events in :py:attr:`~renderdoc.ActionDescription.events`. These contain all of the ``eventId`` that immediately preceded the action. The details of the function call can be found by using :py:attr:`~renderdoc.APIEvent.chunkIndex` as an index into the structured data returned from :py:meth:`~qrenderdoc.CaptureContext.GetStructuredFile`. The structured data contains the function name and the complete set of parameters passed to it, with their values. - -To change the current active event and move the cursor, you can call :py:meth:`~renderdoc.ReplayController.SetFrameEvent`. This will move the replay to represent the current state immediately after the given event has executed. - -At this point you can use :py:meth:`~renderdoc.ReplayController.GetBufferData` and :py:meth:`~renderdoc.ReplayController.GetTextureData` to obtain the contents of a buffer or texture respectively. The pipeline state can be accessed via ``Get*PipelineState`` for each API - to determine the current capture's pipeline type you can fetch the API properties from :py:meth:`~renderdoc.ReplayController.GetAPIProperties`. - -There is also an API-agnostic pipeline abstraction to return information that is the same across APIs. Using :py:meth:`~qrenderdoc.CaptureContext.CurPipelineState` returns a :py:class:`~renderdoc.PipeState` which has accessors for fetching the current vertex buffers, shaders, and colour outputs. This allows you to write generic code that will work on any API that RenderDoc supports. The API-specific pipelines are still available through ``Get*PipelineState``. - -For more examples of how to fetch data, see the concrete examples below. - -ReplayOutput -^^^^^^^^^^^^ - -While :py:class:`~renderdoc.ReplayController` provides methods for obtaining data directly, it doesn't provide any functionality for displaying to a window. For this the :py:class:`~renderdoc.ReplayOutput` class allows you to bind to a window and configure the output. - -First you need to gather the platform-specific windowing information in a :py:class:`~renderdoc.WindowingData`. This class is opaque to python, but you can create it using helper functions such as :py:func:`~renderdoc.CreateWin32WindowingData` and :py:func:`~renderdoc.CreateXlibWindowingData`. The parameters to these are platform specific, and are typically accepted as integers where they refer to a windowing handle. - -To then create an output for a window, :py:meth:`~renderdoc.ReplayController.CreateOutput` can create different types of outputs. - -Once created, you can configure the output with :py:meth:`~renderdoc.ReplayOutput.SetMeshDisplay` and :py:meth:`~renderdoc.ReplayOutput.SetTextureDisplay` to update the configuration, and then call :py:meth:`~renderdoc.ReplayOutput.Display` to display on screen. - -.. _qrenderdoc-python-basics: - -RenderDoc UI Basics -------------------- - -The RenderDoc UI provides a number of useful abstractions over the lower level API, which can be convenient when developing scripts. In addition it gives access to the different panels to allow limited control over them. The ``pyrenderdoc`` global is available to all scripts running within the RenderDoc UI, and it provides access to all of these things. - -Each single-instance panel such as the :py:class:`~qrenderdoc.TextureViewer` or :py:class:`~qrenderdoc.PipelineStateViewer` has accessors within the :py:class:`~qrenderdoc.CaptureContext`. - -Functions such as :py:meth:`~qrenderdoc.CaptureContext.GetTextureViewer` will return a valid handle to the texture viewer, but if the texture viewer was closed then although it will be created it will *not* be immediately visible. You need to call :py:meth:`~qrenderdoc.CaptureContext.ShowTextureViewer` first which will bring the texture viewer to the front and make sure it is visible and docked if it wasn't already. - -You can also create new instances of windows such as buffer or shader viewers using :py:meth:`~qrenderdoc.CaptureContext.ViewBuffer` or :py:meth:`~qrenderdoc.CaptureContext.ViewShader`. - -The :py:class:`~qrenderdoc.CaptureContext` interface also provides useful utility functions such as :py:meth:`~qrenderdoc.CaptureContext.GetTexture` or :py:meth:`~qrenderdoc.CaptureContext.GetAction` to look up objects by id instead of needing your own caching and lookup from the lists returned by the lower level interface. diff --git a/docs/python_api/examples/qrenderdoc/index.rst b/docs/python_api/examples/qrenderdoc/index.rst deleted file mode 100644 index 5be78bceb..000000000 --- a/docs/python_api/examples/qrenderdoc/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -qrenderdoc examples -=================== - -These examples are only relevant to the scripting available in the UI. - -.. toctree:: - show_buffer \ No newline at end of file diff --git a/docs/python_api/examples/qrenderdoc/show_buffer.py b/docs/python_api/examples/qrenderdoc/show_buffer.py deleted file mode 100644 index 0b0cfb4ee..000000000 --- a/docs/python_api/examples/qrenderdoc/show_buffer.py +++ /dev/null @@ -1,23 +0,0 @@ -filename = "test.rdc" -formatter = "float3 pos; half norms[16]; uint flags;" - -pyrenderdoc.LoadCapture(filename, renderdoc.ReplayOptions(), filename, False, True) - -mybuf = renderdoc.ResourceId.Null() - -for buf in pyrenderdoc.GetBuffers(): - print("buf %s is %s" % (buf.resourceId, pyrenderdoc.GetResourceName(buf.resourceId))) - - # here put your actual selection criteria - i.e. look for a particular name - if pyrenderdoc.GetResourceName(buf.resourceId) == "dataBuffer": - mybuf = buf.resourceId - break - -print("selected %s" % pyrenderdoc.GetResourceName(mybuf)) - -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) diff --git a/docs/python_api/examples/qrenderdoc/show_buffer.rst b/docs/python_api/examples/qrenderdoc/show_buffer.rst deleted file mode 100644 index af397bd60..000000000 --- a/docs/python_api/examples/qrenderdoc/show_buffer.rst +++ /dev/null @@ -1,50 +0,0 @@ -Display buffer with format -========================== - -This example shows an easy way to automate a repro case. It could be run as a command line argument when starting the UI, to avoid repetitive steps. - -First the code opens a specified file, although this step could be omitted if the desired capture is already open. - -.. highlight:: python -.. code:: python - - filename = "test.rdc" - - pyrenderdoc.LoadCapture(filename, renderdoc.ReplayOptions(), filename, False, True) - -Next we iterate through the list of buffers to find the one we want. The selection criteria are up to you, in this case we look at the name provided and identify the buffer by that, however it could also be a particular size, or the buffer bound at a given event. - -.. highlight:: python -.. code:: python - - mybuf = renderdoc.ResourceId.Null() - - for buf in pyrenderdoc.GetBuffers(): - print("buf %s is %s" % (buf.resourceId, pyrenderdoc.GetResourceName(buf.resourceId))) - - # here put your actual selection criteria - i.e. look for a particular name - if pyrenderdoc.GetResourceName(buf.resourceId) == "dataBuffer": - mybuf = buf.resourceId - break - - print("selected %s" % pyrenderdoc.GetResourceName(mybuf)) - -Once we've identified the buffer we want to view, we create a buffer viewer and display it on the main tool area. - -.. highlight:: python -.. code:: python - - # 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) - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: show_buffer.py diff --git a/docs/python_api/examples/qrenderdoc_intro.rst b/docs/python_api/examples/qrenderdoc_intro.rst deleted file mode 100644 index 4f81f7dc3..000000000 --- a/docs/python_api/examples/qrenderdoc_intro.rst +++ /dev/null @@ -1,52 +0,0 @@ -Getting Started (RenderDoc UI) -============================== - -.. note:: - - This document is aimed at users getting started with scripting within the RenderDoc UI. - - The module used here (``qrenderdoc``) is not available as a stand-alone module. - -When working within the RenderDoc UI, the ``renderdoc`` and ``qrenderdoc`` modules are implicitly imported when any script runs. In addition to this, an additional global ``pyrenderdoc`` is always available. It is an instance of :py:class:`~qrenderdoc.CaptureContext` and it represents the interface into the UI interface. - -Loading a Capture ------------------ - -Unlike :doc:`when using the base module directly `, within the UI it is strongly recommended that you load captures using the UI interfaces itself rather than doing it entirely within python code, otherwise there is a risk of conflict between the two loaded captures in the same program. - -To load a capture programmatically, we can use the ``pyrenderdoc`` global variable and call :py:meth:`~qrenderdoc.CaptureContext.LoadCapture`. When loading a local capture some of the parameters are redundant - we don't need to specify a different "actual" file vs the loaded file, and it is not a temporary handle. These parameters are primarily used by the UI itself when loading captures from remote hosts or immediately after they are generated while they are still stored temporarily on disk and the user needs to be prompted to save or delete them on close. - -.. highlight:: python -.. code:: python - - filename = 'test.rdc' - - # Load a file, with the same 'original' name, that's not temporary, and is local - pyrenderdoc.LoadCapture(filename, renderdoc.ReplayOptions(), filename, False, True) - -This will close any capture that is already loaded, but to just close an open capture you can use :py:meth:`~qrenderdoc.CaptureContext.CloseCapture`. - -As part of opening the capture, RenderDoc will begin replay automatically and populate the various panels and internal data structures for easy access. It will also handle prompting the user if any errors happen or if replay isn't supported. - -Once the capture is opened, all of the RenderDoc UI accessible data is immediately available and can be accessed directly, see :ref:`qrenderdoc-python-basics`. - -Accessing Capture Analysis --------------------------- - -To access the :py:class:`~renderdoc.ReplayController` and any related core interfaces, a little bit of extra work is required. Within the UI, the replay work happens on a separate thread to prevent long-running tasks from causing the UI to become unresponsive. That means the :py:class:`~renderdoc.ReplayController` is not immediately available, but is provided to a callback on the right thread. - -To invoke onto the right thread, you can use :py:meth:`~qrenderdoc.ReplayManager.BlockInvoke` and pass it a callback that will be called with a single parameter - the :py:class:`~renderdoc.ReplayController` instance for the currently open capture. - -.. warning:: - - There is another invoke function :py:meth:`~qrenderdoc.ReplayManager.AsyncInvoke`, but due to Python's limited threading capability the callback can't be called while the script is executing, meaning this has limited use. For Python it is recommended to use :py:meth:`~qrenderdoc.ReplayManager.BlockInvoke`. - -.. highlight:: python -.. code:: python - - def myCallback(controller): - print("%d top-level actions" % len(controller.GetRootActions())) - - pyrenderdoc.Replay().BlockInvoke(myCallback) - -If there is no replay active, the callback will be silently dropped. diff --git a/docs/python_api/examples/renderdoc/decode_mesh.py b/docs/python_api/examples/renderdoc/decode_mesh.py deleted file mode 100644 index 7df2498a2..000000000 --- a/docs/python_api/examples/renderdoc/decode_mesh.py +++ /dev/null @@ -1,291 +0,0 @@ -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 - -# We'll need the struct data to read out of bytes objects -import struct - -# We base our data on a MeshFormat, but we add some properties -class MeshData(rd.MeshFormat): - indexOffset = 0 - name = '' - -# Recursively search for the drawcall with the most vertices -def biggestDraw(prevBiggest, d): - ret = prevBiggest - if ret == None or d.numIndices > ret.numIndices: - ret = d - - for c in d.children: - biggest = biggestDraw(ret, c) - - if biggest.numIndices > ret.numIndices: - ret = biggest - - return ret - -# Unpack a tuple of the given format, from the data -def unpackData(fmt, data): - # We don't handle 'special' formats - typically bit-packed such as 10:10:10:2 - if fmt.Special(): - raise RuntimeError("Packed formats are not supported!") - - formatChars = {} - # 012345678 - formatChars[rd.CompType.UInt] = "xBHxIxxxL" - formatChars[rd.CompType.SInt] = "xbhxixxxl" - formatChars[rd.CompType.Float] = "xxexfxxxd" # only 2, 4 and 8 are valid - - # These types have identical decodes, but we might post-process them - formatChars[rd.CompType.UNorm] = formatChars[rd.CompType.UInt] - formatChars[rd.CompType.UScaled] = formatChars[rd.CompType.UInt] - formatChars[rd.CompType.SNorm] = formatChars[rd.CompType.SInt] - formatChars[rd.CompType.SScaled] = formatChars[rd.CompType.SInt] - - # We need to fetch compCount components - vertexFormat = str(fmt.compCount) + formatChars[fmt.compType][fmt.compByteWidth] - - # Unpack the data - value = struct.unpack_from(vertexFormat, data, 0) - - # If the format needs post-processing such as normalisation, do that now - if fmt.compType == rd.CompType.UNorm: - divisor = float((2 ** (fmt.compByteWidth * 8)) - 1) - value = tuple(float(i) / divisor for i in value) - elif fmt.compType == rd.CompType.SNorm: - maxNeg = -float(2 ** (fmt.compByteWidth * 8)) / 2 - divisor = float(-(maxNeg-1)) - value = tuple((float(i) if (i == maxNeg) else (float(i) / divisor)) for i in value) - - # If the format is BGRA, swap the two components - if fmt.BGRAOrder(): - value = tuple(value[i] for i in [2, 1, 0, 3]) - - return value - -# Get a list of MeshData objects describing the vertex inputs at this draw -def getMeshInputs(controller, draw): - state = controller.GetPipelineState() - - # Get the index & vertex buffers, and fixed vertex inputs - ib = state.GetIBuffer() - vbs = state.GetVBuffers() - attrs = state.GetVertexInputs() - - meshInputs = [] - - for attr in attrs: - - # We don't handle instance attributes - if attr.perInstance: - raise RuntimeError("Instanced properties are not supported!") - - meshInput = MeshData() - meshInput.indexResourceId = ib.resourceId - meshInput.indexByteOffset = ib.byteOffset - meshInput.indexByteStride = ib.byteStride - meshInput.baseVertex = draw.baseVertex - meshInput.indexOffset = draw.indexOffset - meshInput.numIndices = draw.numIndices - - # If the draw doesn't use an index buffer, don't use it even if bound - if not (draw.flags & rd.ActionFlags.Indexed): - meshInput.indexResourceId = rd.ResourceId.Null() - - # The total offset is the attribute offset from the base of the vertex - meshInput.vertexByteOffset = attr.byteOffset + vbs[attr.vertexBuffer].byteOffset + draw.vertexOffset * vbs[attr.vertexBuffer].byteStride - meshInput.format = attr.format - meshInput.vertexResourceId = vbs[attr.vertexBuffer].resourceId - meshInput.vertexByteStride = vbs[attr.vertexBuffer].byteStride - meshInput.name = attr.name - - meshInputs.append(meshInput) - - return meshInputs - -# Get a list of MeshData objects describing the vertex outputs at this draw -def getMeshOutputs(controller, postvs): - meshOutputs = [] - posidx = 0 - - vs = controller.GetPipelineState().GetShaderReflection(rd.ShaderStage.Vertex) - - # Repeat the process, but this time sourcing the data from postvs. - # Since these are outputs, we iterate over the list of outputs from the - # vertex shader's reflection data - for attr in vs.outputSignature: - # Copy most properties from the postvs struct - meshOutput = MeshData() - meshOutput.indexResourceId = postvs.indexResourceId - meshOutput.indexByteOffset = postvs.indexByteOffset - meshOutput.indexByteStride = postvs.indexByteStride - meshOutput.baseVertex = postvs.baseVertex - meshOutput.indexOffset = 0 - meshOutput.numIndices = postvs.numIndices - - # The total offset is the attribute offset from the base of the vertex, - # as calculated by the stride per index - meshOutput.vertexByteOffset = postvs.vertexByteOffset - meshOutput.vertexResourceId = postvs.vertexResourceId - meshOutput.vertexByteStride = postvs.vertexByteStride - - # Construct a resource format for this element - meshOutput.format = rd.ResourceFormat() - meshOutput.format.compByteWidth = rd.VarTypeByteSize(attr.varType) - meshOutput.format.compCount = attr.compCount - meshOutput.format.compType = rd.VarTypeCompType(attr.varType) - meshOutput.format.type = rd.ResourceFormatType.Regular - - meshOutput.name = attr.semanticIdxName if attr.varName == '' else attr.varName - - if attr.systemValue == rd.ShaderBuiltin.Position: - posidx = len(meshOutputs) - - meshOutputs.append(meshOutput) - - # Shuffle the position element to the front - if posidx > 0: - pos = meshOutputs[posidx] - del meshOutputs[posidx] - meshOutputs.insert(0, pos) - - accumOffset = 0 - - for i in range(0, len(meshOutputs)): - meshOutputs[i].vertexByteOffset = accumOffset - - # Note that some APIs such as Vulkan will pad the size of the attribute here - # while others will tightly pack - fmt = meshOutputs[i].format - - accumOffset += (8 if fmt.compByteWidth > 4 else 4) * fmt.compCount - - return meshOutputs - -def getIndices(controller, mesh): - # Get the character for the width of index - indexFormat = 'B' - if mesh.indexByteStride == 2: - indexFormat = 'H' - elif mesh.indexByteStride == 4: - indexFormat = 'I' - - # Duplicate the format by the number of indices - indexFormat = str(mesh.numIndices) + indexFormat - - # If we have an index buffer - if mesh.indexResourceId != rd.ResourceId.Null(): - # Fetch the data - ibdata = controller.GetBufferData(mesh.indexResourceId, mesh.indexByteOffset, 0) - - # Unpack all the indices, starting from the first index to fetch - offset = mesh.indexOffset * mesh.indexByteStride - indices = struct.unpack_from(indexFormat, ibdata, offset) - - # Apply the baseVertex offset - return [i + mesh.baseVertex for i in indices] - else: - # With no index buffer, just generate a range - return tuple(range(mesh.numIndices)) - -def printMeshData(controller, meshData): - indices = getIndices(controller, meshData[0]) - - print("Mesh configuration:") - for attr in meshData: - print("\t%s:" % attr.name) - print("\t\t- vertex: %s / %d stride" % (attr.vertexResourceId, attr.vertexByteStride)) - print("\t\t- format: %s x %s @ %d" % (attr.format.compType, attr.format.compCount, attr.vertexByteOffset)) - - # We'll decode the first three indices making up a triangle - for i in range(0, 3): - idx = indices[i] - - print("Vertex %d is index %d:" % (i, idx)) - - for attr in meshData: - # This is the data we're reading from. This would be good to cache instead of - # re-fetching for every attribute for every index - offset = attr.vertexByteOffset + attr.vertexByteStride * idx - data = controller.GetBufferData(attr.vertexResourceId, offset, 0) - - # Get the value from the data - value = unpackData(attr.format, data) - - # We don't go into the details of semantic matching here, just print both - print("\tAttribute '%s': %s" % (attr.name, value)) - -def sampleCode(controller): - # Find the biggest drawcall in the whole capture - draw = None - for d in controller.GetRootActions(): - draw = biggestDraw(draw, d) - - # Move to that draw - controller.SetFrameEvent(draw.eventId, True) - - print("Decoding mesh inputs at %d: %s\n\n" % (draw.eventId, draw.GetName(controller.GetStructuredFile()))) - - # Calculate the mesh input configuration - meshInputs = getMeshInputs(controller, draw) - - # Fetch and print the data from the mesh inputs - printMeshData(controller, meshInputs) - - print("Decoding mesh outputs\n\n") - - # Fetch the postvs data - postvs = controller.GetPostVSData(0, 0, rd.MeshDataStage.VSOut) - - # Calcualte the mesh configuration from that - meshOutputs = getMeshOutputs(controller, postvs) - - # Print it - printMeshData(controller, meshOutputs) - -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(): - pyrenderdoc.Replay().BlockInvoke(sampleCode) -else: - rd.InitialiseReplay(rd.GlobalEnvironment(), []) - - if len(sys.argv) <= 1: - print('Usage: python3 {} filename.rdc'.format(sys.argv[0])) - sys.exit(0) - - cap,controller = loadCapture(sys.argv[1]) - - sampleCode(controller) - - controller.Shutdown() - cap.Shutdown() - - rd.ShutdownReplay() - diff --git a/docs/python_api/examples/renderdoc/decode_mesh.rst b/docs/python_api/examples/renderdoc/decode_mesh.rst deleted file mode 100644 index 95d1bfee7..000000000 --- a/docs/python_api/examples/renderdoc/decode_mesh.rst +++ /dev/null @@ -1,304 +0,0 @@ -Decoding Mesh Data -================== - -In this example we will fetch the geometry inputs to and outputs from a vertex shader. While this sample does not handle all possible edge cases, it is more complex than most others. - -First we gather the API state that describes the vertex input data. In this example we will use the API abstraction :py:class:`~renderdoc.PipeState` so that this code works on a capture from any API: - -.. highlight:: python -.. code:: python - - state = controller.GetPipelineState() - - # Get the index & vertex buffers, and fixed vertex inputs - ib = state.GetIBuffer() - vbs = state.GetVBuffers() - attrs = state.GetVertexInputs() - -We iterate over every attribute defined, and create an object that describes where to source it from, based on :py:class:`~renderdoc.MeshFormat` - since that is the format returned by :py:meth:`~renderdoc.ReplayController.GetPostVSData` this allows us to re-use code. - -In the object we pass both the indices (which does not vary per attribute in our case) as well as the data for the vertex buffer the attribute comes from. - -.. highlight:: python -.. code:: python - - for attr in attrs: - # We don't handle instance attributes - if attr.perInstance: - raise RuntimeError("Instanced properties are not supported!") - - meshInput = MeshData() - meshInput.indexResourceId = ib.resourceId - meshInput.indexByteOffset = ib.byteOffset - meshInput.indexByteStride = ib.byteStride - meshInput.baseVertex = draw.baseVertex - meshInput.indexOffset = draw.indexOffset - meshInput.numIndices = draw.numIndices - - # If the draw doesn't use an index buffer, don't use it even if bound - if not (draw.flags & rd.ActionFlags.Indexed): - meshInput.indexResourceId = rd.ResourceId.Null() - - # The total offset is the attribute offset from the base of the vertex - meshInput.vertexByteOffset = attr.byteOffset + vbs[attr.vertexBuffer].byteOffset + draw.vertexOffset * vbs[attr.vertexBuffer].byteStride - meshInput.format = attr.format - meshInput.vertexResourceId = vbs[attr.vertexBuffer].resourceId - meshInput.vertexByteStride = vbs[attr.vertexBuffer].byteStride - meshInput.name = attr.name - - meshInputs.append(meshInput) - -Next we fetch the index data using :py:meth:`~renderdoc.ReplayController.GetBufferData`, applying any offsets that might be present, and decode it using python's ``struct`` module. If we're not using index buffers, then we just generate a range of indices from the first vertex up to the number of indices. - -.. highlight:: python -.. code:: python - - def getIndices(controller, mesh): - # Get the character for the width of index - indexFormat = 'B' - if mesh.indexByteStride == 2: - indexFormat = 'H' - elif mesh.indexByteStride == 4: - indexFormat = 'I' - - # Duplicate the format by the number of indices - indexFormat = str(mesh.numIndices) + indexFormat - - # If we have an index buffer - if mesh.indexResourceId != rd.ResourceId.Null(): - # Fetch the data - ibdata = controller.GetBufferData(mesh.indexResourceId, mesh.indexByteOffset, 0) - - # Unpack all the indices, starting from the first index to fetch - offset = mesh.indexOffset * mesh.indexByteStride - indices = struct.unpack_from(indexFormat, ibdata, offset) - - # Apply the baseVertex offset - return [i + mesh.baseVertex for i in indices] - else: - # With no index buffer, just generate a range - return tuple(range(mesh.numIndices)) - -To begin with, we define a helper that will read a given variable out of a ``bytes`` object, using a :py:class:`~renderdoc.ResourceFormat` do define the size and format of the data. - -We only handle simple regular formatted types, rather than bit-packed types, to simplify the code. As a shortcut, we use a hash of strings, where the hash key is the component type, and then the character index in the string is the byte width. This gives us the ``struct.unpack`` character to decode one component of the variable, then we prepend the number of components to fetch. - -For normalised formats - :py:attr:`~renderdoc.CompType.UNorm` and :py:attr:`~renderdoc.CompType.SNorm` - we also divide the resulting integer value to get the final floating point value used. - -.. highlight:: python -.. code:: python - - # Unpack a tuple of the given format, from the data - def unpackData(fmt, data): - # We don't handle 'special' formats - typically bit-packed such as 10:10:10:2 - if fmt.Special(): - raise RuntimeError("Packed formats are not supported!") - - formatChars = {} - # 012345678 - formatChars[rd.CompType.UInt] = "xBHxIxxxL" - formatChars[rd.CompType.SInt] = "xbhxixxxl" - formatChars[rd.CompType.Float] = "xxexfxxxd" # only 2, 4 and 8 are valid - - # These types have identical decodes, but we might post-process them - formatChars[rd.CompType.UNorm] = formatChars[rd.CompType.UInt] - formatChars[rd.CompType.UScaled] = formatChars[rd.CompType.UInt] - formatChars[rd.CompType.SNorm] = formatChars[rd.CompType.SInt] - formatChars[rd.CompType.SScaled] = formatChars[rd.CompType.SInt] - - # We need to fetch compCount components - vertexFormat = str(fmt.compCount) + formatChars[fmt.compType][fmt.compByteWidth] - - # Unpack the data - value = struct.unpack_from(vertexFormat, data, 0) - - # If the format needs post-processing such as normalisation, do that now - if fmt.compType == rd.CompType.UNorm: - divisor = float((2 ** (fmt.compByteWidth * 8)) - 1) - value = tuple(float(i) / divisor for i in value) - elif fmt.compType == rd.CompType.SNorm: - maxNeg = -float(2 ** (fmt.compByteWidth * 8)) / 2 - divisor = float(-(maxNeg-1)) - value = tuple((float(i) if (i == maxNeg) else (float(i) / divisor)) for i in value) - - # If the format is BGRA, swap the two components - if fmt.BGRAOrder(): - value = tuple(value[i] for i in [2, 1, 0, 3]) - - return value - -Finally with that helper defined we can iterate over each attribute for the first three indices: - -.. highlight:: python -.. code:: python - - indices = getIndices(controller, meshData[0]) - - # We'll decode the first three indices making up a triangle - for i in range(0, 3): - idx = indices[i] - - print("Vertex %d is index %d:" % (i, idx)) - - for attr in meshData: - -Using the index, we can fetch the right vertex data for each vertex's attribute using :py:meth:`~renderdoc.ReplayController.GetBufferData` again. This simplified approach is very wasteful since we re-fetch the same vertex data for each vertex buffer over and over. A more realistic sample would cache the vertex data: - -.. highlight:: python -.. code:: python - - # This is the data we're reading from. This would be good to cache instead of - # re-fetching for every attribute for every index - offset = attr.vertexByteOffset + attr.vertexByteStride * idx - data = controller.GetBufferData(attr.vertexResourceId, offset, 0) - - # Get the value from the data - value = unpackData(attr.format, data) - - # We don't go into the details of semantic matching here, just print both - print("\tAttribute '%s': %s" % (attr.name, str(value))) - -For the vertex outputs, we do something very similar but instead of fetching the attributes from state bindings, we look at the shader reflection data of the vertex. Similarly instead of fetching the vertex byte data from bound vertex buffers, we call :py:meth:`~renderdoc.ReplayController.GetPostVSData` to fetch it from the analysis. - -In the case of vertex outputs there is no explicit offset available, so we calculate our own offsets. Note that for some APIs like Vulkan the outputs are not necessarily tightly packed, so padding calculations may be necessary. - -The position output is also treated specially - it always appears first, regardless of the actual order of the outputs. We solve this by noting which output is the builtin position output, and shuffling it to the start of the array. - -.. highlight:: python -.. code:: python - - posidx = 0 - - vs = controller.GetPipelineState().GetShaderReflection(rd.ShaderStage.Vertex) - - # Repeat the process, but this time sourcing the data from postvs. - # Since these are outputs, we iterate over the list of outputs from the - # vertex shader's reflection data - for attr in vs.outputSignature: - # Copy most properties from the postvs struct - meshOutput = MeshData() - meshOutput.indexResourceId = postvs.indexResourceId - meshOutput.indexByteOffset = postvs.indexByteOffset - meshOutput.indexByteStride = postvs.indexByteStride - meshOutput.baseVertex = postvs.baseVertex - meshOutput.indexOffset = 0 - meshOutput.numIndices = postvs.numIndices - - # The total offset is the attribute offset from the base of the vertex, - # as calculated by the stride per index - meshOutput.vertexByteOffset = postvs.vertexByteOffset - meshOutput.vertexResourceId = postvs.vertexResourceId - meshOutput.vertexByteStride = postvs.vertexByteStride - - # Construct a resource format for this element - meshOutput.format = rd.ResourceFormat() - meshOutput.format.compByteWidth = rd.VarTypeByteSize(attr.varType) - meshOutput.format.compCount = attr.compCount - meshOutput.format.compType = rd.VarTypeCompType(attr.varType) - meshOutput.format.type = rd.ResourceFormatType.Regular - - meshOutput.name = attr.semanticIdxName if attr.varName == '' else attr.varName - - if attr.systemValue == rd.ShaderBuiltin.Position: - posidx = len(meshOutputs) - - meshOutputs.append(meshOutput) - - # Shuffle the position element to the front - if posidx > 0: - pos = meshOutputs[posidx] - del meshOutputs[posidx] - meshOutputs.insert(0, pos) - - accumOffset = 0 - - for i in range(0, len(meshOutputs)): - meshOutputs[i].vertexByteOffset = accumOffset - - # Note that some APIs such as Vulkan will pad the size of the attribute here - # while others will tightly pack - fmt = meshOutputs[i].format - - accumOffset += (8 if fmt.compByteWidth > 4 else 4) * fmt.compCount - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: decode_mesh.py - -Sample output: - -.. sourcecode:: text - - Decoding mesh inputs at 69: DrawIndexed(5580) - - - Mesh configuration: - POSITION0: - - vertex: / 44 stride - - format: CompType.Float x 3 @ 0 - TANGENT0: - - vertex: / 44 stride - - format: CompType.Float x 3 @ 12 - NORMAL0: - - vertex: / 44 stride - - format: CompType.Float x 3 @ 24 - TEXCOORD0: - - vertex: / 44 stride - - format: CompType.Float x 2 @ 36 - Vertex 0 is index 0: - Attribute 'POSITION0': (1.0, -1.5, 0.0) - Attribute 'TANGENT0': (-0.0, 0.0, 1.0) - Attribute 'NORMAL0': (0.9701425433158875, 0.24253533780574799, 0.0) - Attribute 'TEXCOORD0': (0.0, 1.0) - Vertex 1 is index 31: - Attribute 'POSITION0': (0.9750000238418579, -1.399999976158142, 0.0) - Attribute 'TANGENT0': (-0.0, 0.0, 1.0) - Attribute 'NORMAL0': (0.9701424241065979, 0.24253588914871216, 0.0) - Attribute 'TEXCOORD0': (0.0, 0.9666666388511658) - Vertex 2 is index 32: - Attribute 'POSITION0': (0.9536939859390259, -1.399999976158142, 0.20271390676498413) - Attribute 'TANGENT0': (-0.20791170001029968, 0.0, 0.9781476259231567) - Attribute 'NORMAL0': (0.9489423036575317, 0.24253617227077484, 0.20170393586158752) - Attribute 'TEXCOORD0': (0.03333333507180214, 0.9666666388511658) - Decoding mesh outputs - - - Mesh configuration: - SV_POSITION: - - vertex: / 68 stride - - format: CompType.Float x 4 @ 0 - POSITION: - - vertex: / 68 stride - - format: CompType.Float x 4 @ 16 - TEXCOORD: - - vertex: / 68 stride - - format: CompType.Float x 2 @ 32 - TANGENT: - - vertex: / 68 stride - - format: CompType.Float x 3 @ 40 - NORMAL: - - vertex: / 68 stride - - format: CompType.Float x 4 @ 52 - Vertex 0 is index 0: - Attribute 'SV_POSITION': (-6.269223690032959, -4.345583915710449, -2.4250497817993164, -2.0) - Attribute 'POSITION': (-4.0, 0.0, -12.0, 1.0) - Attribute 'TEXCOORD': (0.0, 1.0) - Attribute 'TANGENT': (-5.0, 1.5, -11.0) - Attribute 'NORMAL': (0.9701425433158875, 0.24253533780574799, 0.0, 0.0) - Vertex 1 is index 31: - Attribute 'SV_POSITION': (-6.308406352996826, -4.104162216186523, -2.4250497817993164, -2.0) - Attribute 'POSITION': (-4.025000095367432, 0.10000002384185791, -12.0, 1.0) - Attribute 'TEXCOORD': (0.0, 0.9666666388511658) - Attribute 'TANGENT': (-5.0, 1.5, -11.0) - Attribute 'NORMAL': (0.9701424241065979, 0.24253588914871216, 0.0, 0.0) - Vertex 2 is index 32: - Attribute 'SV_POSITION': (-6.341799736022949, -4.104162216186523, -2.221970558166504, -1.797286033630371) - Attribute 'POSITION': (-4.046306133270264, 0.10000002384185791, -11.797286033630371, 1.0) - Attribute 'TEXCOORD': (0.03333333507180214, 0.9666666388511658) - Attribute 'TANGENT': (-5.207911491394043, 1.5, -11.021852493286133) - Attribute 'NORMAL': (0.9489423036575317, 0.24253617227077484, 0.20170393586158752, 0.0) diff --git a/docs/python_api/examples/renderdoc/display_window.py b/docs/python_api/examples/renderdoc/display_window.py deleted file mode 100644 index 0993851fd..000000000 --- a/docs/python_api/examples/renderdoc/display_window.py +++ /dev/null @@ -1,146 +0,0 @@ -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() diff --git a/docs/python_api/examples/renderdoc/display_window.rst b/docs/python_api/examples/renderdoc/display_window.rst deleted file mode 100644 index b79da0cac..000000000 --- a/docs/python_api/examples/renderdoc/display_window.rst +++ /dev/null @@ -1,150 +0,0 @@ -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. - -.. highlight:: python -.. code:: python - - # 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 :py:class:`~renderdoc.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 :py:class:`~renderdoc.WindowingData`, we can create a :py:class:`~renderdoc.ReplayOutput` using :py:meth:`~renderdoc.ReplayController.CreateOutput`. - -.. highlight:: python -.. code:: python - - # 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 :py:meth:`~renderdoc.ReplayController.GetTextures` and :py:meth:`~renderdoc.ReplayController.GetRootActions`, and we'll also define a helper function to fetch a particular texture by :class:`~renderdoc.ResourceId`, so that we can easily look up the details for a texture. - -.. highlight:: python -.. code:: python - - # 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 :py:meth:`~renderdoc.ReplayOutput.Display`. ``advance`` changes the current state to reflect a new action. - -.. highlight:: python -.. code:: python - - # 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 :py:meth:`~renderdoc.ReplayController.SetFrameEvent`. Then we set up the texture display configuration with :py:meth:`~renderdoc.ReplayOutput.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. - -.. highlight:: python -.. code:: python - - # 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. - -.. highlight:: python -.. code:: python - - # Start the callbacks - advance() - paint() - - # Start the main window loop - window.mainloop() - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: display_window.py \ No newline at end of file diff --git a/docs/python_api/examples/renderdoc/fetch_counters.py b/docs/python_api/examples/renderdoc/fetch_counters.py deleted file mode 100644 index 1899ef7bc..000000000 --- a/docs/python_api/examples/renderdoc/fetch_counters.py +++ /dev/null @@ -1,107 +0,0 @@ -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 - -actions = {} - -# Define a recursive function for iterating over actions -def iterDraw(d, indent = ''): - global actions - - # save the action by eventId - actions[d.eventId] = d - - # Iterate over the draw's children - for d in d.children: - iterDraw(d, indent + ' ') - -def sampleCode(controller): - # Iterate over all of the root actions, so we have names for each - # eventId - for d in controller.GetRootActions(): - iterDraw(d) - - # Enumerate the available counters - counters = controller.EnumerateCounters() - - if not (rd.GPUCounter.SamplesPassed in counters): - raise RuntimeError("Implementation doesn't support Samples Passed counter") - - # Now we fetch the counter data, this is a good time to batch requests of as many - # counters as possible, the implementation handles any book keeping. - results = controller.FetchCounters([rd.GPUCounter.SamplesPassed]) - - # Get the description for the counter we want - samplesPassedDesc = controller.DescribeCounter(rd.GPUCounter.SamplesPassed) - - # Describe each counter - for c in counters: - desc = controller.DescribeCounter(c) - - print("Counter %d (%s):" % (c, desc.name)) - print(" %s" % desc.description) - print(" Returns %d byte %s, representing %s" % (desc.resultByteWidth, desc.resultType, desc.unit)) - - # Look in the results for any draws with 0 samples written - this is an indication - # that if a lot of draws appear then culling could be better. - for r in results: - draw = actions[r.eventId] - - # Only care about draws, not about clears and other misc events - if not (draw.flags & rd.ActionFlags.Drawcall): - continue - - if samplesPassedDesc.resultByteWidth == 4: - val = r.value.u32 - else: - val = r.value.u64 - - if val == 0: - print("EID %d '%s' had no samples pass depth/stencil test!" % (r.eventId, draw.GetName(controller.GetStructuredFile()))) - -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(): - pyrenderdoc.Replay().BlockInvoke(sampleCode) -else: - rd.InitialiseReplay(rd.GlobalEnvironment(), []) - - if len(sys.argv) <= 1: - print('Usage: python3 {} filename.rdc'.format(sys.argv[0])) - sys.exit(0) - - cap,controller = loadCapture(sys.argv[1]) - - sampleCode(controller) - - controller.Shutdown() - cap.Shutdown() - - rd.ShutdownReplay() - diff --git a/docs/python_api/examples/renderdoc/fetch_counters.rst b/docs/python_api/examples/renderdoc/fetch_counters.rst deleted file mode 100644 index 7c97d8439..000000000 --- a/docs/python_api/examples/renderdoc/fetch_counters.rst +++ /dev/null @@ -1,119 +0,0 @@ -Fetch GPU Counter Data -====================== - -In this example we will gather GPU counter data over a capture and find any actions that completely failed the depth/stencil test. - -The first thing we do is enumerate a list of counters that the implementation supports using :py:meth:`~renderdoc.ReplayController.EnumerateCounters`. A few of these counters values are statically known - see :py:class:`~renderdoc.GPUCounter`. If you know which counter you want ahead of time you can continue straight away by calling :py:meth:`~renderdoc.ReplayController.FetchCounters` with a list of counters to sample from, or :py:meth:`~renderdoc.ReplayController.DescribeCounter` to obtain a :py:class:`~renderdoc.CounterDescription` of the counter itself: - -.. highlight:: python -.. code:: python - - # Enumerate the available counters - counters = controller.EnumerateCounters() - - if not (rd.GPUCounter.SamplesPassed in counters): - raise RuntimeError("Implementation doesn't support Samples Passed counter") - - # Now we fetch the counter data, this is a good time to batch requests of as many - # counters as possible, the implementation handles any book keeping. - results = controller.FetchCounters([rd.GPUCounter.SamplesPassed]) - - # Get the description for the counter we want - samplesPassedDesc = controller.DescribeCounter(rd.GPUCounter.SamplesPassed) - -However we will also print all available counters. If the implementation supports vendor-specific counters they will be enumerated as well and we can print their descriptions. - -.. highlight:: python -.. code:: python - - # Describe each counter - for c in counters: - desc = controller.DescribeCounter(c) - - print("Counter %d (%s):" % (c, desc.name)) - print(" %s" % desc.description) - print(" Returns %d byte %s, representing %s" % (desc.resultByteWidth, desc.resultType, desc.unit)) - -Once we have the list of :py:class:`~renderdoc.CounterResult` from sampling the specified counters, each result returned is for one counter on one event. Since we only fetched one counter we can simply iterate over the results looking up the action for each. For actual draws (excluding clears and markers etc) we use the counter description to determine the data payload size, and get the value out. Interpreting this can either happen based on the description, or in our case we know that this counter returns a simple value we can check: - -.. highlight:: python -.. code:: python - - # Look in the results for any draws with 0 samples written - this is an indication - # that if a lot of draws appear then culling could be better. - for r in results: - draw = actions[r.eventId] - - # Only care about draws, not about clears and other misc events - if not (draw.flags & rd.ActionFlags.Drawcall): - continue - - if samplesPassedDesc.resultByteWidth == 4: - val = r.value.u32 - else: - val = r.value.u64 - - if val == 0: - print("EID %d '%s' had no samples pass depth/stencil test!" % (r.eventId, draw.GetName(controller.GetStructuredFile()))) - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: fetch_counters.py - -Sample output: - -.. sourcecode:: text - - Counter 1 (GPU Duration): - Time taken for this event on the GPU, as measured by delta between two GPU timestamps. - Returns 8 byte CompType.Float, representing CounterUnit.Seconds - Counter 2 (Input Vertices Read): - Number of vertices read by input assembler. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 3 (Input Primitives): - Number of primitives read by the input assembler. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 4 (GS Primitives): - Number of primitives output by a geometry shader. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 5 (Rasterizer Invocations): - Number of primitives that were sent to the rasterizer. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 6 (Rasterized Primitives): - Number of primitives that were rendered. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 7 (Samples Passed): - Number of samples that passed depth/stencil test. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - ... which we will sample - Counter 8 (VS Invocations): - Number of times a vertex shader was invoked. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 9 (HS Invocations): - Number of times a hull shader was invoked. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 10 (DS Invocations): - Number of times a domain shader (or tesselation evaluation shader in OpenGL) was invoked. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 11 (GS Invocations): - Number of times a geometry shader was invoked. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 12 (PS Invocations): - Number of times a pixel shader was invoked. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - Counter 13 (CS Invocations): - Number of times a compute shader was invoked. - Returns 8 byte CompType.UInt, representing CounterUnit.Absolute - EID 69 'DrawIndexed()' had no samples pass depth/stencil test! - EID 82 'DrawIndexed()' had no samples pass depth/stencil test! - EID 95 'DrawIndexed()' had no samples pass depth/stencil test! - EID 108 'DrawIndexed()' had no samples pass depth/stencil test! - EID 199 'DrawIndexed()' had no samples pass depth/stencil test! - EID 212 'DrawIndexed()' had no samples pass depth/stencil test! - EID 225 'DrawIndexed()' had no samples pass depth/stencil test! - EID 238 'DrawIndexed()' had no samples pass depth/stencil test! \ No newline at end of file diff --git a/docs/python_api/examples/renderdoc/fetch_shader.py b/docs/python_api/examples/renderdoc/fetch_shader.py deleted file mode 100644 index d24553be9..000000000 --- a/docs/python_api/examples/renderdoc/fetch_shader.py +++ /dev/null @@ -1,98 +0,0 @@ -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 printVar(v, indent = ''): - print(indent + v.name + ":") - - if len(v.members) == 0: - valstr = "" - for r in range(0, v.rows): - valstr += indent + ' ' - - for c in range(0, v.columns): - valstr += '%.3f ' % v.value.f32v[r*v.columns + c] - - if r < v.rows-1: - valstr += "\n" - - print(valstr) - - for v in v.members: - printVar(v, indent + ' ') - -def sampleCode(controller): - print("Available disassembly formats:") - - targets = controller.GetDisassemblyTargets(True) - - for disasm in targets: - print(" - " + disasm) - - target = targets[0] - - state = controller.GetPipelineState() - - # For some APIs, it might be relevant to set the PSO id or entry point name - pipe = state.GetGraphicsPipelineObject() - entry = state.GetShaderEntryPoint(rd.ShaderStage.Pixel) - - # Get the pixel shader's reflection object - ps = state.GetShaderReflection(rd.ShaderStage.Pixel) - - cb = state.GetConstantBlock(rd.ShaderStage.Pixel, 0, 0) - - print("Pixel shader:") - print(controller.DisassembleShader(pipe, ps, target)) - - cbufferVars = controller.GetCBufferVariableContents(pipe, ps.resourceId, rd.ShaderStage.Pixel, entry, 0, cb.descriptor.resource, 0, 0) - - for v in cbufferVars: - printVar(v) - -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(): - pyrenderdoc.Replay().BlockInvoke(sampleCode) -else: - rd.InitialiseReplay(rd.GlobalEnvironment(), []) - - if len(sys.argv) <= 1: - print('Usage: python3 {} filename.rdc'.format(sys.argv[0])) - sys.exit(0) - - cap,controller = loadCapture(sys.argv[1]) - - sampleCode(controller) - - controller.Shutdown() - cap.Shutdown() - - rd.ShutdownReplay() - diff --git a/docs/python_api/examples/renderdoc/fetch_shader.rst b/docs/python_api/examples/renderdoc/fetch_shader.rst deleted file mode 100644 index cad13da6c..000000000 --- a/docs/python_api/examples/renderdoc/fetch_shader.rst +++ /dev/null @@ -1,207 +0,0 @@ -Fetch Shader details -==================== - -In this example we will fetch the disassembly for a shader and a set of constant values. - -When disassembling a shader there may be more than one possible representation available, so we first enumerate the formats that are available using :py:meth:`~renderdoc.ReplayController.GetDisassemblyTargets` before selecting a target to disassemble to. The first target is always a reasonable default, and there will be at least one. :py:meth:`~renderdoc.ReplayController.GetDisassemblyTargets` takes a parameter indicating whether the pipeline state object will be available - some disassembly targets require the full pipeline and are not available when disassembling only a shader in isolation: - -.. highlight:: python -.. code:: python - - print("Available disassembly formats:") - - targets = controller.GetDisassemblyTargets(True) - - for disasm in targets: - print(" - " + disasm) - - target = targets[0] - -Next we fetch any ancillary data that might be needed to disassemble - this varies by API depending on whether it supports multiple entry points per shader, or has a concept of pipeline state objects that are used together with a shader to disassemble. - -For the purposes of this example we use the API abstraction :py:class:`~renderdoc.PipeState` so that this code works on a capture from any API, so we fetch the state bindings that we need. Finally we fetch the disassembled shader string with :py:meth:`~renderdoc.ReplayController.DisassembleShader` and print it: - -.. highlight:: python -.. code:: python - - state = controller.GetPipelineState() - - # For some APIs, it might be relevant to set the PSO id or entry point name - pipe = state.GetGraphicsPipelineObject() - entry = state.GetShaderEntryPoint(rd.ShaderStage.Pixel) - - # Get the pixel shader's reflection object - ps = state.GetShaderReflection(rd.ShaderStage.Pixel) - - cb = state.GetConstantBlock(rd.ShaderStage.Pixel, 0, 0) - - print("Pixel shader:") - print(controller.DisassembleShader(pipe, ps, target)) - -Now we want to display the constants bound to this shader. Shader bindings is an area that diverges quite a lot between the APIs, and RenderDoc's abstraction over this is detailed in :ref:`more detail `. For now, we'll simply select the first constant buffer in this shader and fetch the constants for it with :py:meth:`~renderdoc.ReplayController.GetCBufferVariableContents`. - -.. highlight:: python -.. code:: python - - cbufferVars = controller.GetCBufferVariableContents(pipe, ps.resourceId, rd.ShaderStage.Pixel, entry, 0, cb.descriptor.resource, 0, 0) - -Since constants can contain structs of other constants, we want to define a recursive function that will iterate over a constant and print it along with its value. We want to handle both vectors and matrices so we need to iterate over both rows and columns for each variable. - -.. highlight:: python -.. code:: python - - def printVar(v, indent = ''): - print(indent + v.name + ":") - - if len(v.members) == 0: - valstr = "" - for r in range(0, v.rows): - valstr += indent + ' ' - - for c in range(0, v.columns): - valstr += '%.3f ' % v.value.f32v[r*v.columns + c] - - if r < v.rows-1: - valstr += "\n" - - print(valstr) - - for v in v.members: - printVar(v, indent + ' ') - -Finally, we iterate over the constants that we fetched earlier calling the function for each. - -.. highlight:: python -.. code:: python - - for v in cbufferVars: - printVar(v) - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: fetch_shader.py - -Sample output: - -.. sourcecode:: text - - Available disassembly formats: - - DXBC - - AMD GCN ISA - Pixel shader: - Shader hash 9dd8337a-c75dd787-1fa0f07e-5f39f955 - - ps_5_0 - dcl_globalFlags refactoringAllowed - dcl_constantbuffer cb0[8], immediateIndexed - dcl_sampler gDepthSam (s0), mode_default - dcl_resource_texture2d (float,float,float,float) gDepthMap (t0) - dcl_resource_texture2d (float,float,float,float) gGBufferMap (t1) - dcl_input_ps linear v1.xyz - dcl_input_ps linear v2.xyw - dcl_output o0.xyzw - dcl_output o1.xyzw - dcl_temps 6 - - 0: nop - 1: mov r0.xyz, v2.xywx - 2: div r0.xy, r0.xyxx, r0.zzzz - 3: mul r0.xy, r0.xyxx, l(0.500000, -0.500000, 0.000000, 0.000000) - 4: add r0.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) - 5: mov r0.xy, r0.xyxx - 6: sample_indexable(texture2d)(float,float,float,float) r0.z, r0.xyxx, gDepthMap.yzxw, gDepthSam - 7: mov r0.z, r0.z - 8: nop - 9: mov r0.z, r0.z - 10: mov r0.z, -r0.z - 11: add r0.z, r0.z, l(1.001801) - 12: div r0.z, l(0.421448), r0.z - 13: mov r0.z, r0.z - 14: dp3 r0.w, v1.xyzx, v1.xyzx - 15: rsq r0.w, r0.w - 16: mul r1.xyz, r0.wwww, v1.xyzx - 17: div r0.z, r0.z, r1.z - 18: mul r2.xyz, r0.zzzz, r1.xyzx - 19: sample_indexable(texture2d)(float,float,float,float) r0.xyzw, r0.xyxx, gGBufferMap.xyzw, gDepthSam - 20: dp3 r1.w, r0.xyzx, r0.xyzx - 21: rsq r1.w, r1.w - 22: mul r0.xyz, r0.xyzx, r1.wwww - 23: mov r3.xyz, gLightPosV.xyzx - 24: mov r4.xyz, -r2.xyzx - 25: add r4.xyz, r3.xyzx, r4.xyzx - 26: dp3 r1.w, r4.xyzx, r4.xyzx - 27: rsq r1.w, r1.w - 28: mul r4.xyz, r1.wwww, r4.xyzx - 29: dp3 r1.w, r0.xyzx, r4.xyzx - 30: max r5.x, r1.w, l(0) - 31: nop - 32: mov r1.xyz, -r1.xyzx - 33: add r1.xyz, r1.xyzx, r4.xyzx - 34: dp3 r1.w, r1.xyzx, r1.xyzx - 35: rsq r1.w, r1.w - 36: mul r1.xyz, r1.wwww, r1.xyzx - 37: mov r0.xyz, r0.xyzx - 38: mul r0.w, r0.w, l(64.000000) - 39: dp3 r0.x, r1.xyzx, r0.xyzx - 40: max r0.x, r0.x, l(0) - 41: log r0.x, r0.x - 42: mul r0.x, r0.x, r0.w - 43: exp r5.y, r0.x - 44: mov r5.y, r5.y - 45: nop - 46: mov r3.xyz, r3.xyzx - 47: mov r2.xyz, r2.xyzx - 48: mov r0.xyz, gLight.att.xyzx - 49: mov r1.xyz, -r2.xyzx - 50: add r1.xyz, r1.xyzx, r3.xyzx - 51: dp3 r0.w, r1.xyzx, r1.xyzx - 52: sqrt r0.w, r0.w - 53: mul r0.y, r0.y, r0.w - 54: add r0.x, r0.y, r0.x - 55: mul r0.y, r0.w, r0.w - 56: mul r0.y, r0.z, r0.y - 57: add r0.x, r0.y, r0.x - 58: div r0.x, l(1.000000), r0.x - 59: mov r0.x, r0.x - 60: mul r0.xy, r5.xyxx, r0.xxxx - 61: max r0.xy, r0.xyxx, l(0, 0, 0, 0) - 62: mul o0.xyzw, r0.xxxx, gLight.diffuse.xyzw - 63: mul o1.xyzw, r0.yyyy, gLight.diffuse.xyzw - 64: ret - - gLight: - pos: - -2.022 2.000 -3.694 - dir: - 0.000 0.000 0.000 - ambient: - 0.300 0.300 0.300 1.000 - diffuse: - 0.300 1.000 0.600 1.000 - spec: - 0.500 0.500 0.500 1.000 - att: - 0.000 0.200 0.100 - spotPower: - 0.000 - range: - 3.000 - gLightPosV: - -2.022 0.200 6.306 -107374176.000 - gLigthDirES: - -0.298 -0.596 -0.745 - gWorldViewProj: - 1.567 0.000 0.000 0.000 - 0.000 2.414 0.000 0.000 - 0.000 0.000 1.002 1.000 - -3.169 0.483 5.896 6.306 - gWorldView: - 1.000 0.000 0.000 0.000 - 0.000 1.000 0.000 0.000 - 0.000 0.000 1.000 0.000 - -2.022 0.200 6.306 1.000 diff --git a/docs/python_api/examples/renderdoc/index.rst b/docs/python_api/examples/renderdoc/index.rst deleted file mode 100644 index 5415a5495..000000000 --- a/docs/python_api/examples/renderdoc/index.rst +++ /dev/null @@ -1,35 +0,0 @@ -renderdoc Examples -================== - -Here we have some examples of the lower level renderdoc API. Some may be only relevant when using the module directly, but most are general and can be followed either when running in python or in the UI. - -In order to help with this, the examples are organised such that most code is written within a function that accepts the replay controller, and the code from :doc:`../renderdoc_intro` runs only when standalone and not within the UI: - -.. note:: - - While some of the samples will work within the UI, because they directly access the lower level API and skip the UI's API, it may cause some state inconsistencies. See :doc:`../qrenderdoc/index` for examples using the UI API directly. - -.. highlight:: python -.. code:: python - - def sampleCode(controller): - print("Here we can use the replay controller") - - if 'pyrenderdoc' in globals(): - pyrenderdoc.Replay().BlockInvoke(sampleCode) - else: - cap,controller = loadCapture('test.rdc') - - sampleCode(controller) - - controller.Shutdown() - cap.Shutdown() - -.. toctree:: - iter_actions - fetch_shader - fetch_counters - save_texture - decode_mesh - display_window - remote_capture diff --git a/docs/python_api/examples/renderdoc/iter_actions.py b/docs/python_api/examples/renderdoc/iter_actions.py deleted file mode 100644 index b0b8276cb..000000000 --- a/docs/python_api/examples/renderdoc/iter_actions.py +++ /dev/null @@ -1,101 +0,0 @@ -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 actions -def iterAction(d, indent = ''): - # Print this action - print('%s%d: %s' % (indent, d.eventId, d.GetName(controller.GetStructuredFile()))) - - # Iterate over the action's children - for d in d.children: - iterAction(d, indent + ' ') - -def sampleCode(controller): - # Iterate over all of the root actions - for d in controller.GetRootActions(): - iterAction(d) - - # Start iterating from the first real action as a child of markers - action = controller.GetRootActions()[0] - - while len(action.children) > 0: - action = action.children[0] - - # Counter for which pass we're in - passnum = 0 - # Counter for how many actions are in the pass - passcontents = 0 - # Whether we've started seeing actions 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" % (action.eventId, action.GetName(controller.GetStructuredFile()))) - - while action != None: - # When we encounter a clear - if action.flags & rd.ActionFlags.Clear: - if inpass: - print("Pass #%d contained %d actions" % (passnum, passcontents)) - passnum += 1 - print("Pass #%d starts with %d: %s" % (passnum, action.eventId, action.GetName(controller.GetStructuredFile()))) - passcontents = 0 - inpass = False - else: - passcontents += 1 - inpass = True - - # Advance to the next action - action = action.next - if action is None: - break - - if inpass: - print("Pass #%d contained %d actions" % (passnum, passcontents)) - -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(): - pyrenderdoc.Replay().BlockInvoke(sampleCode) -else: - rd.InitialiseReplay(rd.GlobalEnvironment(), []) - - if len(sys.argv) <= 1: - print('Usage: python3 {} filename.rdc'.format(sys.argv[0])) - sys.exit(0) - - cap,controller = loadCapture(sys.argv[1]) - - sampleCode(controller) - - controller.Shutdown() - cap.Shutdown() - - rd.ShutdownReplay() - diff --git a/docs/python_api/examples/renderdoc/iter_actions.rst b/docs/python_api/examples/renderdoc/iter_actions.rst deleted file mode 100644 index 8efba6bde..000000000 --- a/docs/python_api/examples/renderdoc/iter_actions.rst +++ /dev/null @@ -1,161 +0,0 @@ -Iterate Action tree -=================== - -In this example we will show how to iterate over actions. - -The actions returned from :py:meth:`~renderdoc.ReplayController.GetRootActions` are actions or marker regions at the root level - with no parent marker region. There are multiple ways to iterate through the list of actions. - -The first way illustrated in this sample is to walk the tree using :py:attr:`~renderdoc.ActionDescription.children`, which contains the list of child actions at any point in the tree. There is also :py:attr:`~renderdoc.ActionDescription.parent` which points to the parent action. - -The second is to use :py:attr:`~renderdoc.ActionDescription.previousAction` and :py:attr:`~renderdoc.ActionDescription.nextAction`, which point to the previous and next action respectively in a linear fashion, regardless of nesting depth. - -In the example we use this iteration to determine the number of passes, using the action flags to denote the start of each pass by a starting clear call. - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: iter_actions.py - -Sample output: - -.. sourcecode:: text - - 1: Scene - 2: ID3D11DeviceContext::ClearRenderTargetView() - 3: ID3D11DeviceContext::ClearDepthStencilView() - 9: ID3D11DeviceContext::ClearRenderTargetView() - 10: ID3D11DeviceContext::ClearRenderTargetView() - 11: ID3D11DeviceContext::ClearDepthStencilView() - 13: GBuffer - 25: Floor - 28: ID3D11DeviceContext::DrawIndexed() - 29: empty label - 30: ID3DUserDefinedAnnotation::EndEvent() - 40: Base - 43: ID3D11DeviceContext::DrawIndexed() - 53: Center sphere - 56: ID3D11DeviceContext::DrawIndexed() - 66: Cone - 69: ID3D11DeviceContext::DrawIndexed() - 79: Cone - 82: ID3D11DeviceContext::DrawIndexed() - 92: Cone - 95: ID3D11DeviceContext::DrawIndexed() - 105: Cone - 108: ID3D11DeviceContext::DrawIndexed() - 118: Cone - 121: ID3D11DeviceContext::DrawIndexed() - 131: Cone - 134: ID3D11DeviceContext::DrawIndexed() - 144: Cone - 147: ID3D11DeviceContext::DrawIndexed() - 157: Cone - 160: ID3D11DeviceContext::DrawIndexed() - 170: Cone - 173: ID3D11DeviceContext::DrawIndexed() - 183: Cone - 186: ID3D11DeviceContext::DrawIndexed() - 196: Sphere - 199: ID3D11DeviceContext::DrawIndexed() - 209: Sphere - 212: ID3D11DeviceContext::DrawIndexed() - 222: Sphere - 225: ID3D11DeviceContext::DrawIndexed() - 235: Sphere - 238: ID3D11DeviceContext::DrawIndexed() - 248: Sphere - 251: ID3D11DeviceContext::DrawIndexed() - 261: Sphere - 264: ID3D11DeviceContext::DrawIndexed() - 274: Sphere - 277: ID3D11DeviceContext::DrawIndexed() - 287: Sphere - 290: ID3D11DeviceContext::DrawIndexed() - 300: Sphere - 303: ID3D11DeviceContext::DrawIndexed() - 313: Sphere - 316: ID3D11DeviceContext::DrawIndexed() - 317: ID3DUserDefinedAnnotation::EndEvent() - 319: ID3D11DeviceContext::ClearDepthStencilView() - 321: Shadowmap - 330: Floor - 333: ID3D11DeviceContext::DrawIndexed() - 334: empty label - 335: ID3DUserDefinedAnnotation::EndEvent() - 342: Base - 345: ID3D11DeviceContext::DrawIndexed() - 352: Center sphere - 355: ID3D11DeviceContext::DrawIndexed() - 362: Cone - 365: ID3D11DeviceContext::DrawIndexed() - 372: Cone - 375: ID3D11DeviceContext::DrawIndexed() - 382: Cone - 385: ID3D11DeviceContext::DrawIndexed() - 392: Cone - 395: ID3D11DeviceContext::DrawIndexed() - 402: Cone - 405: ID3D11DeviceContext::DrawIndexed() - 412: Cone - 415: ID3D11DeviceContext::DrawIndexed() - 422: Cone - 425: ID3D11DeviceContext::DrawIndexed() - 432: Cone - 435: ID3D11DeviceContext::DrawIndexed() - 442: Cone - 445: ID3D11DeviceContext::DrawIndexed() - 452: Cone - 455: ID3D11DeviceContext::DrawIndexed() - 462: Sphere - 465: ID3D11DeviceContext::DrawIndexed() - 472: Sphere - 475: ID3D11DeviceContext::DrawIndexed() - 482: Sphere - 485: ID3D11DeviceContext::DrawIndexed() - 492: Sphere - 495: ID3D11DeviceContext::DrawIndexed() - 502: Sphere - 505: ID3D11DeviceContext::DrawIndexed() - 512: Sphere - 515: ID3D11DeviceContext::DrawIndexed() - 522: Sphere - 525: ID3D11DeviceContext::DrawIndexed() - 532: Sphere - 535: ID3D11DeviceContext::DrawIndexed() - 542: Sphere - 545: ID3D11DeviceContext::DrawIndexed() - 552: Sphere - 555: ID3D11DeviceContext::DrawIndexed() - 556: ID3DUserDefinedAnnotation::EndEvent() - 558: ID3D11DeviceContext::ClearRenderTargetView() - 559: ID3D11DeviceContext::ClearDepthStencilView() - 561: Lighting - 563: ID3D11DeviceContext::ClearRenderTargetView() - 564: ID3D11DeviceContext::ClearRenderTargetView() - 566: Point light - 580: ID3D11DeviceContext::DrawIndexed() - 581: Sun light - 597: ID3D11DeviceContext::DrawIndexed() - 600: Cube light - 614: ID3D11DeviceContext::DrawIndexed() - 615: ID3DUserDefinedAnnotation::EndEvent() - 617: ID3D11DeviceContext::ClearRenderTargetView() - 618: ID3D11DeviceContext::ClearDepthStencilView() - 620: Shading - 630: ID3D11DeviceContext::Draw() - 645: ID3D11DeviceContext::DrawIndexed() - 652: ID3DUserDefinedAnnotation::EndEvent() - 653: ID3DUserDefinedAnnotation::EndEvent() - 654: Present(ResourceId::47) - Pass #0 starts with 2: ID3D11DeviceContext::ClearRenderTargetView() - Pass #0 contained 23 actions - Pass #1 starts with 319: ID3D11DeviceContext::ClearDepthStencilView() - Pass #1 contained 23 actions - Pass #2 starts with 558: ID3D11DeviceContext::ClearRenderTargetView() - Pass #2 contained 3 actions - Pass #3 starts with 617: ID3D11DeviceContext::ClearRenderTargetView() - Pass #3 contained 3 actions diff --git a/docs/python_api/examples/renderdoc/remote_capture.py b/docs/python_api/examples/renderdoc/remote_capture.py deleted file mode 100644 index c3cf50592..000000000 --- a/docs/python_api/examples/renderdoc/remote_capture.py +++ /dev/null @@ -1,200 +0,0 @@ -import renderdoc as rd -import threading -import time - -# This sample is intended as an example of how to do remote capture and replay -# as well as using device protocols to automatically enumerate remote targets. -# -# It is not complete since it requires filling in with custom logic to select -# the executable and trigger the capture at the desired time -raise RuntimeError("This sample should not be run directly, read the source") - -rd.InitialiseReplay(rd.GlobalEnvironment(), []) - -protocols = rd.GetSupportedDeviceProtocols() - -print(f"Supported device protocols: {protocols}") - -# Protocols are optional - they allow automatic detection and management of -# devices. -if protocol_to_use is not None: - # the protocol must be supported - if protocol_to_use not in protocols: - raise RuntimeError(f"{protocol_to_use} protocol not supported") - - protocol = rd.GetDeviceProtocolController(protocol_to_use) - - devices = protocol.GetDevices() - - if len(devices) == 0: - raise RuntimeError(f"no {protocol_to_use} devices connected") - - # Choose the first device - dev = devices[0] - name = protocol.GetFriendlyName(dev) - - print(f"Running test on {dev} - named {name}") - - URL = protocol.GetProtocolName() + "://" + dev - - # Protocols can enumerate devices which are not supported. Capture/replay - # is not guaranteed to work on these devices - if not protocol.IsSupported(URL): - raise RuntimeError(f"{dev} doesn't support capture/replay - too old?") - - # Protocol devices may be single-use and not support multiple captured programs - # If so, trying to execute a program for capture is an error - if not protocol.SupportsMultiplePrograms(URL): - # check to see if anything is running. Just use the URL - ident = rd.EnumerateRemoteTargets(URL, 0) - - if ident != 0: - raise RuntimeError(f"{name} already has a program running on {ident}") -else: - # If you're not using a protocol then the URL can simply be a hostname. - # The remote server must be running already - how that is done is up - # to you. Everything else will work the same over a normal TCP connection - protocol = None - URL = hostname - -# Let's try to connect -result,remote = rd.CreateRemoteServerConnection(URL) - -if result == rd.ResultCode.NetworkIOFailed and protocol is not None: - # If there's just no I/O, most likely the server is not running. If we have - # a protocol, we can try to start the remote server - print("Couldn't connect to remote server, trying to start it") - - result = protocol.StartRemoteServer(URL) - - if result != rd.ResultCode.Succeeded: - raise RuntimeError(f"Couldn't launch remote server, got error {str(result)}") - - # Try to connect again! - result,remote = rd.CreateRemoteServerConnection(URL) - -if result != rd.ResultCode.Succeeded: - raise RuntimeError(f"Couldn't connect to remote server, got error {str(result)}") - -# We now have a remote connection. This works regardless of whether it's a device -# with a protocol or not. In fact we are done with the protocol at this point -protocol = None - -print("Got connection to remote server") - -# GetHomeFolder() gives you a good default path to start with. -# ListFolder() lists the contents of a folder and can recursively -# browse the remote filesystem. -home = remote.GetHomeFolder() -paths = remote.ListFolder(home) - -print(f"Executables in home folder '{home}':") - -for p in paths: - print(" - " + p.filename) - -# Select your executable, perhaps hardcoded or browsing using the above -# functions -exe,workingDir,cmdLine,env,opts = select_executable() - -print(f"Running {exe}") - -result = remote.ExecuteAndInject(exe, workingDir, cmdLine, env, opts) - -if result.result != rd.ResultCode.Succeeded: - remote.ShutdownServerAndConnection() - raise RuntimeError(f"Couldn't launch {exe}, got error {str(result.result)}") - -# Spin up a thread to keep the remote server connection alive while we make a capture, -# as it will time out after 5 seconds of inactivity -def ping_remote(remote, kill): - success = True - while success and not kill.is_set(): - success = remote.Ping() - time.sleep(1) - -kill = threading.Event() -ping_thread = threading.Thread(target=ping_remote, args=(remote,kill)) -ping_thread.start() - -# Create target control connection -target = rd.CreateTargetControl(URL, result.ident, 'remote_capture.py', True) - -if target is None: - kill.set() - ping_thread.join() - remote.ShutdownServerAndConnection() - raise RuntimeError(f"Couldn't connect to target control for {exe}") - -print("Connected - waiting for desired capture") - -# Wait for the capture condition we want -capture_condition() - -print("Triggering capture") - -target.TriggerCapture(1) - -# Pump messages, keep waiting until we get a capture message. Time out after 30 seconds -msg = None -start = time.clock() -while msg is None or msg.type != rd.TargetControlMessageType.NewCapture: - msg = target.ReceiveMessage(None) - - if time.clock() - start > 30: - break - -# Close the target connection, we're done either way -target.Shutdown() -target = None - -# Stop the background ping thread -kill.set() -ping_thread.join() - -# If we didn't get a capture, error now -if msg.type != rd.TargetControlMessageType.NewCapture: - remote.ShutdownServerAndConnection() - raise RuntimeError("Didn't get new capture notification after triggering capture") - -cap_path = msg.newCapture.path -cap_id = msg.newCapture.captureId - -print(f"Got new capture at {cap_path} which is frame {msg.newCapture.frameNumber} with {msg.newCapture.api}") - -# We could save the capture locally -# remote.CopyCaptureFromRemote(cap_path, local_path, None) - - -# Open a replay. It's recommended to set no proxy preference, but you could -# call remote.LocalProxies and choose an index. -# -# The path must be remote - if the capture isn't freshly created then you need -# to copy it with remote.CopyCaptureToRemote() -result,controller = remote.OpenCapture(rd.RemoteServer.NoPreference, cap_path, rd.ReplayOptions(), None) - -if result != rd.ResultCode.Succeeded: - remote.ShutdownServerAndConnection() - raise RuntimeError(f"Couldn't open {cap_path}, got error {str(result)}") - -# We can now use replay as normal. -# -# The replay is tunnelled over the remote connection, so you don't have to keep -# pinging the remote connection while using the controller. Use of the remote -# connection and controller can be interleaved though you should only access -# them from one thread at once. If they are both unused for 5 seconds though, -# the timeout will happen, so if the controller is idle it's advisable to ping -# the remote connection - -sampleCode(controller) - -print("Shutting down") - -controller.Shutdown() - -# We can still use remote here - e.g. capture again, replay something else, -# save the capture, etc - -remote.ShutdownServerAndConnection() - -rd.ShutdownReplay() diff --git a/docs/python_api/examples/renderdoc/remote_capture.rst b/docs/python_api/examples/renderdoc/remote_capture.rst deleted file mode 100644 index b70d148c4..000000000 --- a/docs/python_api/examples/renderdoc/remote_capture.rst +++ /dev/null @@ -1,118 +0,0 @@ -Remote Capture and Replay -========================= - -This example is a bit different since it's not ready-to-run. It provides a template for how you can capture and replay on a remote machine, instead of the local machine. It also shows how to use device protocols to automatically manage devices. - -First we can enumerate which device protocols are currently supported. - -.. highlight:: python -.. code:: python - - protocols = rd.GetSupportedDeviceProtocols() - -Each string in the list corresponds to a protocol that can be used for managing devices. If we're using one we can call :py:func:`~renderdoc.GetDeviceProtocolController` passing the protocol name and retrieve the controller. - -The controller provides a few methods for managing devices. First we can call :py:meth:`~renderdoc.DeviceProtocolController.GetDevices` to return a list of device IDs. The format of these device IDs is protocol-dependent but will be equivalent to a normal hostname. Devices may have human-readable names obtainable via :py:meth:`~renderdoc.DeviceProtocolController.GetFriendlyName`. - -.. highlight:: python -.. code:: python - - protocol = rd.GetDeviceProtocolController(protocol_to_use) - - devices = protocol.GetDevices() - - if len(devices) == 0: - raise RuntimeError(f"no {protocol_to_use} devices connected") - - # Choose the first device - dev = devices[0] - name = protocol.GetFriendlyName(dev) - - print(f"Running test on {dev} - named {name}") - - URL = protocol.GetProtocolName() + "://" + dev - -The URL will be used the same as we would use a hostname, when connecting for target control or remote servers. - -Note that protocols may have additional restrictions - be sure to check :py:meth:`~renderdoc.DeviceProtocolController.IsSupported` to check if the device is expected to function at all, and :py:meth:`~renderdoc.DeviceProtocolController.SupportsMultiplePrograms` to see if it supports launching multiple programs. If multiple programs are not supported, you should ensure all running capturable programs are closed before launching a new one. - -To begin with we create a remote server connection using :py:func:`~renderdoc.CreateRemoteServerConnection`. The URL is as constructed above for protocol-based connections, or a simple hostname/IP if we're connecting directly to remote machine. - -If the connection fails, normally we must fail but if we have a device protocol available we can attempt to launch the remote server automatically using :py:meth:`~renderdoc.DeviceProtocolController.StartRemoteServer`. - -.. highlight:: python -.. code:: python - - if result == rd.ResultCode.NetworkIOFailed and protocol is not None: - # If there's just no I/O, most likely the server is not running. If we have - # a protocol, we can try to start the remote server - print("Couldn't connect to remote server, trying to start it") - - result = protocol.StartRemoteServer(URL) - - if result != rd.ResultCode.Succeeded: - raise RuntimeError(f"Couldn't launch remote server, got error {str(result)}") - - # Try to connect again! - result,remote = rd.CreateRemoteServerConnection(URL) - -.. note:: - - The remote server connection has a default timeout of 5 seconds. If the connection is unused for 5 seconds, the other side will disconnect and subsequent use of the interface will fail. - -Once we have a remote server connection, we can browse the remote filesystem for the executable we want to launch using :py:meth:`~renderdoc.RemoteServer.GetHomeFolder` and :py:meth:`~renderdoc.RemoteServer.ListFolder`. - -Then once we've selected the executable, we can launch the remote program for capturing with :py:meth:`~renderdoc.RemoteServer.ExecuteAndInject`. This function is almost identical to the local :py:func:`~renderdoc.ExecuteAndInject` except that it is not possible to wait for the program to exit. - -In our sample, we now place the remote server connection on a background thread that will ping it each second to keep the connection alive while we use a target control connection to trigger a capture in the application. - -.. highlight:: python -.. code:: python - - def ping_remote(remote, kill): - success = True - while success and not kill.is_set(): - success = remote.Ping() - time.sleep(1) - - kill = threading.Event() - ping_thread = threading.Thread(target=ping_remote, args=(remote,kill)) - ping_thread.start() - -To connect to and control an application we use :py:func:`~renderdoc.CreateTargetControl` with the URL as before and the ident returned from :py:meth:`~renderdoc.RemoteServer.ExecuteAndInject`. - -.. highlight:: python -.. code:: python - - target = rd.CreateTargetControl(URL, result.ident, 'remote_capture.py', True) - - # Here we wait for whichever condition you want - target.TriggerCapture(1) - -There are a couple of ways to trigger a capture, both :py:meth:`~renderdoc.TargetControl.TriggerCapture` and :py:meth:`~renderdoc.TargetControl.QueueCapture` depending on whether you want a time-based or frame-based trigger. The application itself can also use the in-application API to trigger a capture. - -The target control connection can be intermittently polled for messages using :py:meth:`~renderdoc.TargetControl.ReceiveMessage`, which keeps the connection alive and will return any new information such as the data for a new capture that has been created. A message of type :py:data:`~renderdoc.TargetControlMessageType.NewCapture` indicates a new capture has been created, and :py:data:`~renderdoc.TargetControlMessage.newCapture` contains the information including the path. - -.. highlight:: python -.. code:: python - - msg = target.ReceiveMessage(None) - - # Once msg.type == rd.TargetControlMessageType.NewCapture has been retrieved - - cap_path = msg.newCapture.path - cap_id = msg.newCapture.captureId - -Once the capture has been found we are finished with the target control connection so we can shut it down and stop the background thread that was keeping the remote server connection alive. Using the remote server connection we can copy the capture back to the local machine with :py:meth:`~renderdoc.RemoteServer.CopyCaptureFromRemote`. Similarly if we wanted to load a previously made capture that wasn't on the remote machine :py:meth:`~renderdoc.RemoteServer.CopyCaptureToRemote` would be useful to copy it ready to be opened. - -Finally to open the capture we use, and that returns a :py:class:`~renderdoc.ReplayController` which can be used as normal and will tunnel over the remote server connection. It can be useful to intermittently ping the remote server connection to check that it's still valid, and remote server and controller calls can be interleaved as long as they don't overlap on multiple threads. - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: remote_capture.py - diff --git a/docs/python_api/examples/renderdoc/save_texture.py b/docs/python_api/examples/renderdoc/save_texture.py deleted file mode 100644 index 0004ed3a3..000000000 --- a/docs/python_api/examples/renderdoc/save_texture.py +++ /dev/null @@ -1,116 +0,0 @@ -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 - -# Recursively search for the drawcall with the most vertices -def biggestDraw(prevBiggest, d): - ret = prevBiggest - if ret == None or d.numIndices > ret.numIndices: - ret = d - - for c in d.children: - biggest = biggestDraw(ret, c) - - if biggest.numIndices > ret.numIndices: - ret = biggest - - return ret - -def sampleCode(controller): - # Find the biggest drawcall in the whole capture - draw = None - for d in controller.GetRootActions(): - draw = biggestDraw(draw, d) - - # Move to that draw - controller.SetFrameEvent(draw.eventId, True) - - texsave = rd.TextureSave() - - # Select the first color output - texsave.resourceId = draw.outputs[0] - - if texsave.resourceId == rd.ResourceId.Null(): - return - - filename = str(int(texsave.resourceId)) - - print("Saving images of %s at %d: %s" % (filename, draw.eventId, draw.GetName(controller.GetStructuredFile()))) - - # Save different types of texture - - # Blend alpha to a checkerboard pattern for formats without alpha support - texsave.alpha = rd.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 = rd.FileType.JPG - controller.SaveTexture(texsave, filename + ".jpg") - - texsave.destType = rd.FileType.HDR - controller.SaveTexture(texsave, filename + ".hdr") - - # For formats with an alpha channel, preserve it - texsave.alpha = rd.AlphaMapping.Preserve - - texsave.destType = rd.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 = rd.FileType.DDS - controller.SaveTexture(texsave, filename + ".dds") - -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(): - pyrenderdoc.Replay().BlockInvoke(sampleCode) -else: - rd.InitialiseReplay(rd.GlobalEnvironment(), []) - - if len(sys.argv) <= 1: - print('Usage: python3 {} filename.rdc'.format(sys.argv[0])) - sys.exit(0) - - cap,controller = loadCapture(sys.argv[1]) - - sampleCode(controller) - - controller.Shutdown() - cap.Shutdown() - - rd.ShutdownReplay() - diff --git a/docs/python_api/examples/renderdoc/save_texture.rst b/docs/python_api/examples/renderdoc/save_texture.rst deleted file mode 100644 index 4f969384c..000000000 --- a/docs/python_api/examples/renderdoc/save_texture.rst +++ /dev/null @@ -1,25 +0,0 @@ -Save a texture to disk -====================== - -In this example we will find a particular action, and save the color output to disk as an image file. - -To begin with, so that we have an interesting action selected we iterate over the list of actions finding the draw with the highest vertex count. For more on how to iterate through a capture's list of actions, see :doc:`iter_actions`. - -Once we have set the draw we want as the current event, we can configure the texture save operation. To do this we create a :py:class:`~renderdoc.TextureSave` object. The properties of the object determine how the texture will be mapped to an image file to be saved to disk. - -At minimum you need to select a file format, and we'll try a few - :py:attr:`~renderdoc.FileType.JPG`, :py:attr:`~renderdoc.FileType.HDR`, :py:attr:`~renderdoc.FileType.PNG`, and :py:attr:`~renderdoc.FileType.DDS`. - -For :py:attr:`~renderdoc.FileType.JPG` and :py:attr:`~renderdoc.FileType.HDR`, alpha is not supported so we choose to blend to a checkerboard pattern in RGB, so the alpha is 'visible'. You can also choose other alpha operations. For the other formats they support alpha natively so we preserve it. - -:py:attr:`~renderdoc.FileType.DDS` is the only format that supports mip levels and array slices, so we choose to keep all of these in the output file instead of selecting only one. It is also possible to map array slices into a grid to display an array texture in a single-image format. - -:py:attr:`~renderdoc.FileType.DDS` will also support the exact format that the texture is in, rather than encoding it to a different precision. - -Example Source --------------- - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: save_texture.py \ No newline at end of file diff --git a/docs/python_api/examples/renderdoc_intro.py b/docs/python_api/examples/renderdoc_intro.py deleted file mode 100644 index b0358ae58..000000000 --- a/docs/python_api/examples/renderdoc_intro.py +++ /dev/null @@ -1,32 +0,0 @@ -import renderdoc as rd - -rd.InitialiseReplay(rd.GlobalEnvironment(), []) - -# Open a capture file handle -cap = rd.OpenCaptureFile() - -# Open a particular file - see also OpenBuffer to load from memory -result = cap.OpenFile('test.rdc', '', 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)) - -# Now we can use the controller! -print("%d top-level actions" % len(controller.GetRootActions())) - -controller.Shutdown() - -cap.Shutdown() - -rd.ShutdownReplay() diff --git a/docs/python_api/examples/renderdoc_intro.rst b/docs/python_api/examples/renderdoc_intro.rst deleted file mode 100644 index 75d7cc168..000000000 --- a/docs/python_api/examples/renderdoc_intro.rst +++ /dev/null @@ -1,133 +0,0 @@ -Getting Started (python) -======================== - -.. note:: - - This document is aimed at users getting started with loading a capture and getting access from the renderdoc module, and is generally not relevant when running within the RenderDoc UI. - - The same APIs are available in the UI, so you can follow these steps. Be aware that loading captures while purely from script may interfere with a capture that is loaded in the UI itself, so this is not recommended. - -Loading the Module ------------------- - -For this section we assume you have built a copy of RenderDoc and have the module (``renderdoc.pyd`` or ``renderdoc.so`` depending on your platform). For information on how to build see the `GitHub repository `_. - -.. note:: - - You must use exactly the same version of python to load the module as was used to build it. - - On windows by default RenderDoc builds against python 3.6 which is what it's distributed with. - - This can be overridden by setting an overridden path under the ``Python Configuration`` section in the properties of the ``qrenderdoc`` project and ``pyrenderdoc_module``/``qrenderdoc_module`` projects. It must point to a python installation. - - RenderDoc requires ``pythonXY.lib``, include files such as include/Python.h, as well as a .zip of the standard library. If you installed python with an installer you have the first two, and can generate the standard library zip by zipping the contents of the Lib folder. If you downloaded the embeddable zip distribution you will only have the standard library zip, you need to obtain the include files and ``.lib`` file separately. - -Once you have the module, either place the module within your python's default library search path, or else insert the location of the python module into the path in your script. You can either set the ``PYTHONPATH`` environment variable or do it at the start of your script: - -.. highlight:: python -.. code:: python - - import sys - - sys.path.append('/path/to/renderdoc/module') - -Additionally, the renderdoc python module needs to be able to load the main renderdoc library (``renderdoc.dll`` or ``librenderdoc.so`` depending on your platform) - the module library itself just contains stubs and python wrappers for the C++ interfaces. You can either place the renderdoc library in the system library paths, or solve it in a platform specific way. For example on windows you can either place ``renderdoc.dll`` in the same directory as the python module, or append to ``PATH``. On Python 3.8 and above ``PATH`` is no longer searched by default so you need to explicitly add the DLL folder: - -.. highlight:: python -.. code:: python - - import os, sys - - os.environ["PATH"] += os.pathsep + os.path.abspath('/path/to/renderdoc/native/library') - if sys.platform == 'win32' and sys.version_info[1] >= 8: - os.add_dll_directory("/path/to/renderdoc/native/library") - -On linux you'd perform a similar modification to ``LD_LIBRARY_PATH``. - -Assuming all has gone well, you should now be able to import the renderdoc module: - -.. highlight:: python -.. code:: python - - import renderdoc as rd - - # Prints 'CullMode.FrontAndBack' - print(rd.CullMode.FrontAndBack) - -Loading a Capture ------------------ - -Given a capture file ``test.rdc`` we want to load it, begin the replay and get ready to perform analysis on it. - -Before doing anything, we must initialise the replay API. We do that by calling :py:meth:`~renderdoc.InitialiseReplay`. Generally no special configuration is needed so passing a default :py:class:`~renderdoc.GlobalEnvironment` and an empty list of arguments is fine. - - -.. highlight:: python -.. code:: python - - rd.InitialiseReplay(rd.GlobalEnvironment(), []) - -To begin with, we use :py:meth:`~renderdoc.OpenCaptureFile` to obtain a :py:class:`~renderdoc.CaptureFile` instance. This gives us access to control over a capture file at a meta level. For more information see the :py:class:`renderdoc.CaptureFile` reference - the interface can also be used to create. - -To open a file, use :py:meth:`~renderdoc.CaptureFile.OpenFile` on the :py:class:`~renderdoc.CaptureFile` instance. This function allows conversion from other formats via an importer, but here we'll use it just for opening a regular ``rdc`` file. It returns a :py:class:`~renderdoc.ResultDetails` which can be used to determine what went wrong in the event that there was a problem. We then check that the capture uses an API which can be replayed locally - for example not every platform supports ``D3D11``, so on linux this would return no local replay support. - -.. highlight:: python -.. code:: python - - # Open a capture file handle - cap = rd.OpenCaptureFile() - - # Open a particular file - see also OpenBuffer to load from memory - result = cap.OpenFile('test.rdc', '', 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") - -Accessing Capture Analysis --------------------------- - -Once the capture has been loaded, we can now begin the replay analysis. To do that we use :py:meth:`~renderdoc.CaptureFile.OpenCapture` which returns a tuple of :py:class:`~renderdoc.ResultDetails` and :py:class:`~renderdoc.ReplayController`. - -This function call will open the capture and begin to replay it, and initialise the analysis. The :py:class:`~renderdoc.ReplayController` returned is the interface to the majority of RenderDoc's replaying functionality. - -.. highlight:: python -.. code:: python - - # Initialise the replay - result,controller = cap.OpenCapture(rd.ReplayOptions(), None) - - if result != rd.ResultCode.Succeeded: - raise RuntimeError("Couldn't initialise replay: " + str(result)) - - # Now we can use the controller! - print("%d top-level actions" % len(controller.GetRootActions())) - -Once we're done with the interfaces, we should call the ``Shutdown`` function on each, this allows the C++ interface to release the resources allocated. - -Once all work is done we can shutdown the replay API. - -.. highlight:: python -.. code:: python - - # Shutdown the controller first, then the capture file - controller.Shutdown() - - cap.Shutdown() - - rd.ShutdownReplay() - -Example Source --------------- - -The full source for this example is available below: - -.. only:: html and not htmlhelp - - :download:`Download the example script `. - -.. literalinclude:: renderdoc_intro.py diff --git a/docs/python_api/index.rst b/docs/python_api/index.rst index 9860cb3bf..f943ce22c 100644 --- a/docs/python_api/index.rst +++ b/docs/python_api/index.rst @@ -1,43 +1,2 @@ Python API ========== - -RenderDoc exposes APIs to python at two different levels: - -1. The :doc:`base replay API `, the ``renderdoc`` module, which provides low level access to handling capture files, replaying frames and obtaining analysis information. The UI is built entirely on top of this API, so it provides the full power of RenderDoc, however is does not have many convenience abstractions. -2. The :doc:`RenderDoc UI API `, the ``qrenderdoc`` module, which exposes the abstractions and panels within the UI tool. - -Within RenderDoc - when either running scripts on the command line, or via the :doc:`../window/python_shell` - both modules are pre-imported and available automatically. - -It is also possible to build the ``renderdoc`` module standalone which can be loaded into python and used for scripting directly without the UI program. Due to the inherent difficulty of distributing C python modules this isn't included by default in distributed builds at the time of writing, but is generated by default in source builds - ``renderdoc.pyd`` on windows or ``renderdoc.so`` elsewhere. Use of this module is strictly a convenience and is not supported. - -.. note:: - - Due to Android being inherently an unstable and unreliable platform, using the python scripting on Android devices is not recommended or supported. It may work, but you'll be on your own with any problems encountered as they are too likely to be caused by problems on Android. - -You must use exactly the same version of python to load the module as was used to build it. - -On windows by default RenderDoc builds against python 3.6 which is what it's distributed with. - -This can be overridden by setting an overridden path under the ``Python Configuration`` section in the properties of the ``qrenderdoc`` project and ``pyrenderdoc_module``/``qrenderdoc_module`` projects. It must point to a python installation. - -RenderDoc requires ``pythonXY.lib``, include files such as include/Python.h, as well as a .zip of the standard library. If you installed python with an installer you have the first two, and can generate the standard library zip by zipping the contents of the Lib folder. If you downloaded the embeddable zip distribution you will only have the standard library zip, you need to obtain the include files and ``.lib`` file separately. - -.. note:: - - RenderDoc only supports Python 3.4+, Python 2 is not supported. - -This documentation contains information on getting started with the scripting, as well as tutorials and examples outline how to perform simple tasks. - -Each example has a simple motivating goal and shows how to achieve it using the interfaces provided. They will not show every possible use of the interfaces, but instead give a starting point to build on. Further information about exactly what functionality is available can be found in the API reference below as well as using the python built-in ``help()`` function. - -.. toctree:: - examples/renderdoc_intro - examples/qrenderdoc_intro - dev_environment - ui_extensions - descriptors_bindings - examples/basics - examples/renderdoc/index - examples/qrenderdoc/index - renderdoc/index - qrenderdoc/index diff --git a/docs/python_api/ui_extension_tutorial/__init__.py b/docs/python_api/ui_extension_tutorial/__init__.py deleted file mode 100644 index fa842d21e..000000000 --- a/docs/python_api/ui_extension_tutorial/__init__.py +++ /dev/null @@ -1,151 +0,0 @@ -############################################################################### -# The MIT License (MIT) -# -# Copyright (c) 2021-2026 Baldur Karlsson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -############################################################################### - -import qrenderdoc as qrd -import renderdoc as rd -from typing import Optional - - -class Window(qrd.CaptureViewer): - def __init__(self, ctx: qrd.CaptureContext, version: str): - super().__init__() - - self.mqt: qrd.MiniQtHelper = ctx.Extensions().GetMiniQtHelper() - - self.ctx = ctx - self.version = version - self.topWindow = self.mqt.CreateToplevelWidget("Breadcrumbs", lambda c, w, d: window_closed()) - - vert = self.mqt.CreateVerticalContainer() - self.mqt.AddWidget(self.topWindow, vert) - - self.breadcrumbs = self.mqt.CreateLabel() - - self.mqt.AddWidget(vert, self.breadcrumbs) - - ctx.AddCaptureViewer(self) - - def OnCaptureLoaded(self): - self.mqt.SetWidgetText(self.breadcrumbs, "Breadcrumbs:") - - def OnCaptureClosed(self): - self.mqt.SetWidgetText(self.breadcrumbs, "Breadcrumbs:") - - def OnSelectedEventChanged(self, event): - pass - - def OnEventChanged(self, event): - action = self.ctx.GetAction(event) - - breadcrumbs = '' - - if action is not None: - breadcrumbs = '@{}: {}'.format(action.eventId, action.name) - - while action.parent is not None: - action = action.parent - breadcrumbs = '@{}: {}'.format(action.eventId, action.name) + '\n' + breadcrumbs - - self.mqt.SetWidgetText(self.breadcrumbs, "Breadcrumbs:\n{}".format(breadcrumbs)) - - -cur_window: Optional[Window] = None - - -def window_closed(): - global cur_window - - if cur_window is not None: - cur_window.ctx.RemoveCaptureViewer(cur_window) - - cur_window = None - - -def window_callback(ctx: qrd.CaptureContext, data): - global cur_window - - if cur_window is None: - cur_window = Window(ctx, extiface_version) - if ctx.HasEventBrowser(): - ctx.AddDockWindow(cur_window.topWindow, qrd.DockReference.TopOf, ctx.GetEventBrowser().Widget(), 0.1) - else: - ctx.AddDockWindow(cur_window.topWindow, qrd.DockReference.MainToolArea, None) - - ctx.RaiseDockWindow(cur_window.topWindow) - - -def menu_callback(ctx: qrd.CaptureContext, data): - texid = rd.ResourceId.Null() - depth = ctx.CurPipelineState().GetDepthTarget() - - # Prefer depth if possible - if depth.resourceId != rd.ResourceId.Null(): - texid = depth.resourceId - else: - cols = ctx.CurPipelineState().GetOutputTargets() - - # See if we can get the first colour target instead - if len(cols) > 1 and cols[0].resourceId != rd.ResourceId.Null(): - texid = cols[0].resourceId - - if texid == rd.ResourceId.Null(): - ctx.Extensions().MessageDialog("Couldn't find any bound target!", "Extension message") - return - else: - mqt = ctx.Extensions().GetMiniQtHelper() - texname = ctx.GetResourceName(texid) - - def get_minmax(r: rd.ReplayController): - minvals, maxvals = r.GetMinMax(texid, rd.Subresource(), rd.CompType.Typeless) - - msg = '{} has min {:.4} and max {:.4} in red'.format(texname, minvals.floatValue[0], maxvals.floatValue[0]) - - mqt.InvokeOntoUIThread(lambda: ctx.Extensions().MessageDialog(msg, "Extension message")) - - ctx.Replay().AsyncInvoke('', get_minmax) - - - -extiface_version = '' - - -def register(version: str, ctx: qrd.CaptureContext): - global extiface_version - extiface_version = version - - print("Registering my extension for RenderDoc version {}".format(version)) - - ctx.Extensions().RegisterWindowMenu(qrd.WindowMenu.Tools, ["My extension"], menu_callback) - ctx.Extensions().RegisterWindowMenu(qrd.WindowMenu.Window, ["Extension Window"], window_callback) - - -def unregister(): - print("Unregistering my extension") - - global cur_window - - if cur_window is not None: - # The window_closed() callback will unregister the capture viewer - cur_window.ctx.Extensions().GetMiniQtHelper().CloseToplevelWidget(cur_window.topWindow) - cur_window = None diff --git a/docs/python_api/ui_extension_tutorial/extension.json b/docs/python_api/ui_extension_tutorial/extension.json deleted file mode 100644 index 19e56ec0f..000000000 --- a/docs/python_api/ui_extension_tutorial/extension.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extension_api": 1, - "name": "Tutorial extension", - "version": "1.0", - "minimum_renderdoc": "1.12", - "description": "Tutorial extension from the documentation.", - "author": "Baldur Karlsson ", - "url": "https://github.com/baldurk/renderdoc" -} diff --git a/docs/python_api/ui_extensions.rst b/docs/python_api/ui_extensions.rst index ef7eb46da..0e679a09b 100644 --- a/docs/python_api/ui_extensions.rst +++ b/docs/python_api/ui_extensions.rst @@ -1,242 +1,2 @@ -Writing UI extensions -===================== - -This document outlines how to get started writing a UI extension. For information on how to configure, register and install a UI extension see :doc:`../how/how_python_extension`. - -First steps ------------ - -We start off with the basic registration function. Create an ``__init__.py`` in your extension's root and fill it out: - -.. highlight:: python -.. code:: python - - import qrenderdoc as qrd - - extiface_version = '' - - def register(version: str, ctx: qrd.CaptureContext): - global extiface_version - extiface_version = version - - print("Registering my extension for RenderDoc version {}".format(version)) - - def unregister(): - print("Unregistering my extension") - -Here we create the minimum ``register()`` and ``unregister()`` functions required for an extension to load, that just print a message. We store the interface version in a global which we can use in future to do version-checks if we want to be compatible with more than one RenderDoc version, since the python interface is not fully forwards and backwards compatible. - -This doesn't really do much, let's register a tool menu item: - -.. highlight:: python -.. code:: python - - def menu_callback(ctx: qrd.CaptureContext, data): - ctx.Extensions().MessageDialog("Hello from the extension!", "Extension message") - - def register(version: str, ctx: qrd.CaptureContext): - # as above ... - - ctx.Extensions().RegisterWindowMenu(qrd.WindowMenu.Tools, ["My extension"], menu_callback) - -Now we have a new menu item which when clicked produces a popup message dialog! - -.. figure:: ../imgs/python_ext/Step1.png - - Python extension generating a message box - -This is a good proof of concept, but really we want something more directly usable. Instead of showing a message box, let's show a window which reacts to the selected action by showing a series of breadcrumbs for marker labels. - -Adding a window and capture viewer ----------------------------------- - -First we create a class to handle our window and to derive from :py:class:`qrenderdoc.CaptureViewer` to get callbacks for events. - -.. highlight:: python -.. code:: python - - class Window(qrd.CaptureViewer): - def __init__(self, ctx: qrd.CaptureContext, version: str): - super().__init__() - - self.mqt: qrd.MiniQtHelper = ctx.Extensions().GetMiniQtHelper() - - self.ctx = ctx - self.version = version - self.topWindow = self.mqt.CreateToplevelWidget("Breadcrumbs", lambda c, w, d: window_closed()) - - ctx.AddCaptureViewer(self) - - def OnCaptureLoaded(self): - pass - - def OnCaptureClosed(self): - pass - - def OnSelectedEventChanged(self, event): - pass - - def OnEventChanged(self, event): - pass - -Here we implement stubs for the different events. More information on when they are sent can be found in the class documentation. We use the :py:class:`qrenderdoc.MiniQtHelper` to create a top-level window for ourselves with the 'breadcrumbs' title, then register ourselves as a capture viewer. The mini-Qt helper is useful to provide simple access to Qt widgets in a portable way from the RenderDoc UI, without relying on full Qt python bindings that may not be available depending on how RenderDoc was built. - -We will need to unregister ourselves as a capture viewer when the window is closed, which happens in the ``window_closed()`` callback that we'll define later. - -An empty window is not very useful, so let's give ourselves a label. More complex layouts and widgets are of course possible but for the moment we'll keep it simple: - -.. highlight:: python -.. code:: python - - vert = self.mqt.CreateVerticalContainer() - self.mqt.AddWidget(self.topWindow, vert) - - self.breadcrumbs = self.mqt.CreateLabel() - - self.mqt.AddWidget(vert, self.breadcrumbs) - -And finally we can fill in the event functions to set the breadcrumbs. We use ``@1234`` syntax for events which causes them to be clickable links that jump to that event. You can also convert a :py:class:`renderdoc.ResourceId` to a string with ``str()`` and it will similarly provide a link for that resource named with the current debug name. - -.. highlight:: python -.. code:: python - - def OnCaptureLoaded(self): - self.mqt.SetWidgetText(self.breadcrumbs, "Breadcrumbs:") - - def OnCaptureClosed(self): - self.mqt.SetWidgetText(self.breadcrumbs, "Breadcrumbs:") - - def OnSelectedEventChanged(self, event): - pass - - def OnEventChanged(self, event): - action = self.ctx.GetAction(event) - - breadcrumbs = '' - - if action is not None: - breadcrumbs = '@{}: {}'.format(action.eventId, action.customName) - - while action.parent is not None: - action = action.parent - breadcrumbs = '@{}: {}'.format(action.eventId, action.customName) + '\n' + breadcrumbs - - self.mqt.SetWidgetText(self.breadcrumbs, "Breadcrumbs:\n{}".format(breadcrumbs)) - -Finally we'll register a new menu item to display the window. We only allow one window at once, so if it still exists we'll just raise it. Otherwise we create a new one. This is also where we unregister the capture viewer: - -.. highlight:: python -.. code:: python - - from typing import Optional - - - cur_window: Optional[Window] = None - - - def window_closed(): - global cur_window - if cur_window is not None: - cur_window.ctx.RemoveCaptureViewer(cur_window) - cur_window = None - - - def open_window_callback(ctx: qrd.CaptureContext, data): - global cur_window - - mqt = ctx.Extensions().GetMiniQtHelper() - - if cur_window is None: - cur_window = Window(ctx, extiface_version) - if ctx.HasEventBrowser(): - ctx.AddDockWindow(cur_window.topWindow, qrd.DockReference.TopOf, ctx.GetEventBrowser().Widget(), 0.1) - else: - ctx.AddDockWindow(cur_window.topWindow, qrd.DockReference.MainToolArea, None) - - ctx.RaiseDockWindow(cur_window.topWindow) - - - def register(version: str, ctx: qrd.CaptureContext): - # as above ... - - ctx.Extensions().RegisterWindowMenu(qrd.WindowMenu.Window, ["Extension Window"], open_window_callback) - - - def unregister(): - print("Unregistering my extension") - - global cur_window - - if cur_window is not None: - # The window_closed() callback will unregister the capture viewer - cur_window.ctx.Extensions().GetMiniQtHelper().CloseToplevelWidget(cur_window.topWindow) - cur_window = None - -With that we now have a new little breadcrumbs window that docks itself above our event browser to show where we are in the frame: - -.. figure:: ../imgs/python_ext/Step2.png - - Python extension showing the current action's breadcrumbs - -Calling onto replay thread --------------------------- - -So far this has worked well, but we're only using information available on the UI thread. A good amount of useful information is cached on the UI thread including the current pipeline state and actions, but for some work we might want to call into the underlying analysis functions. When we do this we must do it on the replay thread to avoid blocking the UI if the analysis work takes a long time. - -This can get quite complex so we will do something very simple, in the message box callback that we created earlier instead of displaying the message box immediately we will first figure out the minimum and maximum values for the current depth output or first colour output and display that. - -To start with we can identify the resource on the UI thread, so let's do that: - -.. highlight:: python -.. code:: python - - import renderdoc as rd - - def menu_callback(ctx: qrd.CaptureContext, data): - texid = rd.ResourceId.Null() - depth = ctx.CurPipelineState().GetDepthTarget() - - # Prefer depth if possible - if depth.resourceId != rd.ResourceId.Null(): - texid = depth.resourceId - else: - cols = ctx.CurPipelineState().GetOutputTargets() - - # See if we can get the first colour target instead - if len(cols) > 1 and cols[0].resourceId != rd.ResourceId.Null(): - texid = cols[0].resourceId - - if texid == rd.ResourceId.Null(): - ctx.Extensions().MessageDialog("Couldn't find any bound target!", "Extension message") - return - - -This all happens as before on the UI thread using UI-cached pipeline state data. If we can't find a resource we just bail out, but otherwise we have ``texid`` with the texture we want to analyse. - -To do this we invoke onto a different thread twice - first the UI thread invokes onto the replay thread to calculate the minimum and maximum values. Then that callback invokes back onto the UI thread to display a message. - -.. highlight:: python -.. code:: python - - if texid == rd.ResourceId.Null(): - ctx.Extensions().MessageDialog("Couldn't find any bound target!", "Extension message") - return - else: - mqt = ctx.Extensions().GetMiniQtHelper() - texname = ctx.GetResourceName(texid) - - def get_minmax(r: rd.ReplayController): - minvals, maxvals = r.GetMinMax(texid, rd.Subresource(), rd.CompType.Typeless) - - msg = '{} has min {:.4} and max {:.4} in red'.format(texname, minvals.floatValue[0], maxvals.floatValue[0]) - - mqt.InvokeOntoUIThread(lambda: ctx.Extensions().MessageDialog(msg, "Extension message")) - - ctx.Replay().AsyncInvoke('', get_minmax) - -Now that we've done that correctly our extension will be able to run in-depth replay analysis without calling functions from the wrong thread or stalling the UI. - -Conclusion ----------- - -Hopefully now from that worked example you have an idea of the basics of writing UI extensions. More complex examples can be found at the `community contributed repository `_ and the source code for this extension is available in the `github repository `_ +Tutorial: UI extensions +=======================