Add detailed per-topic writeups of relevant areas for python scripts

This commit is contained in:
baldurk
2026-08-13 21:05:11 +01:00
parent ed38da72ab
commit 1292134b47
18 changed files with 975 additions and 0 deletions
+2
View File
@@ -1,3 +1,5 @@
.. _how_shader_debug_info:
How do I use shader debug information?
======================================
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,97 @@
Capture File Access
===================
RenderDoc captures are stored in ``.rdc`` files, created by RenderDoc when active in an application and a capture is triggered. This file contains all of the necessary data to replay the captured frame, as well as metadata and additional information.
``.rdc`` files are a fairly simple container and can also be managed by scripts to attach additional custom information that is not normally used by the RenderDoc UI.
This is also how replay and analysis is started when not using the RenderDoc UI but driving the replay API from script :doc:`entirely standalone <../python_module>`.
Accessing capture files
-----------------------
Capture files are managed through two main interfaces, :class:`~renderdoc.CaptureAccess` as a limited subset but is available over a network connection without access to the file on local disk, and :class:`~renderdoc.CaptureFile` which offers more functionality but must be initialised for a locally-accessible file.
.. note::
For this document we will assume you are accessing a :class:`~renderdoc.CaptureFile` but will note which functionality is remotely available through :class:`~renderdoc.CaptureAccess`. For more information about RenderDoc's network replay functionality see :doc:`remote_replay`.
To begin with, you can create a :class:`~renderdoc.CaptureFile` with :func:`~renderdoc.OpenCaptureFile`. This handle is owned by python and must be destroyed when you are finished with :meth:`~renderdoc.CaptureFile.Shutdown`.
From there you can open a capture with :meth:`~renderdoc.CaptureFile.OpenFile` with a filename. This function accepts an optional progress callback which will be called intermittently during opening.
.. tip::
A script running in the UI can access the :class:`~renderdoc.CaptureAccess` for the currently loaded capture with :meth:`~qrenderdoc.ReplayManager.GetCaptureAccess`, and if the file is open locally you can access the :class:`~renderdoc.CaptureFile` with :meth:`~qrenderdoc.ReplayManager.GetCaptureFile`, however note that a capture open remotely will return ``None`` in this latter case.
Both of these interfaces **must** be only accessed on the :ref:`replay thread <pythreading>`.
Opening capture for replay
--------------------------
With a :class:`~renderdoc.CaptureFile` you can begin replaying using :meth:`~renderdoc.CaptureFile.OpenCapture`, which will attempt to open and replay the capture and return the :class:`~renderdoc.ReplayController` for it if successful.
Note that the :class:`~renderdoc.CaptureFile` must still stay open until you are finished with the :class:`~renderdoc.ReplayController` so you should ensure that you shut down the controller first when done with analysis before closing the capture file.
Capture file formats
--------------------
In the vast majority of cases, files opened this way will be normal ``.rdc`` files and so when :meth:`~renderdoc.CaptureFile.OpenFile` is called the filetype should be ``rdc`` or an empty string.
RenderDoc does have support for other formats, although the support for importing is very limited as this requires a format that contains all of the information needed for a RenderDoc capture.
Support for other formats is more useful for exporting with :meth:`~renderdoc.CaptureFile.Convert`, which may contain a limited subset of the data. The list of supported formats can be queried via :meth:`~renderdoc.CaptureFile.GetCaptureFileFormats` which gives details about which formats are supported, whether they can be imported or exported, etc.
Capture data
------------
Opening a file is a fairly lightweight operation, as it only decodes the container and loads capture metadata. This will not perform any graphics API calls or begin to replay the capture, and it will not load large amounts of data into memory.
From here it is possible to query metadata about which graphics API - referred to as a driver here - is used in the capture, whether it is supported locally for replay, and the thumbnail (:meth:`~renderdoc.CaptureFile.GetThumbnail`) for the capture. You can also ask for a machine ident string which will give you information about the platform where the capture was recorded - e.g. x86 or Android, 32-bit or 64-bit. This can be useful in displaying messages to the user in case there is incompatibility.
You can request the :doc:`structured_data` (:class:`~renderdoc.SDFile`) for the capture - this is distinct from replaying as RenderDoc will decode the serialised data within the capture, but will not make any graphics API calls. This can be done on any build of RenderDoc that supports the target API. For example on linux it would not be possible to load the structured data for a D3D capture as D3D support is not available at all, but a windows machine could load the structured data for an Android vulkan capture even if it could not replay it.
Requesting structured data will be a more heavyweight operation as this requires reading and decoding the entirety of the capture. The requested :class:`~renderdoc.SDFile` will contain buffers as well, and so is compatible with any export format that has :data:`~renderdoc.CaptureFileFormat.requiresBuffers` for :meth:`~renderdoc.CaptureFile.Convert`.
Capture sections
----------------
The ``rdc`` container file contains a small header and then an arbitrary number of sections. By default this will contain at minimum the section for the frame capture itself, and commonly will also include an extended lossless thumbnail. If callstacks have been captured, there will be a platform-specific section with information about the loaded modules for later resolving.
The known official sections are detailed in :class:`~renderdoc.SectionType` with both an enum value and a string path e.g. ``renderdoc/internal/framecapture`` for the frame capture as well. Sections can be enumerated and accessed with both :class:`~renderdoc.CaptureAccess` or :class:`~renderdoc.CaptureFile`, and sections can also be added or written to via these APIs.
Through these APIs you can read and write your own custom sections to add extra data into a RenderDoc capture for your own processing or tracking. Sections can be added or overwritten by calling :meth:`~renderdoc.CaptureAccess.WriteSection`, and retrieved with :meth:`~renderdoc.CaptureAccess.GetSectionContents`. Sections are described with :class:`~renderdoc.SectionProperties` both when being written and when being enumerated for reading.
.. note::
You should avoid the ``renderdoc/`` prefix for any of your custom section names - these can be arbitrary strings so you should use your own namespacing.
ASCII sections
--------------
To aid in better access for raw scripts, it is possible to add sections to RenderDoc captures by concatenating a text file to the end of the ``.rdc``. This then means it is possible to add section data in a very limited fashion without needing to use the RenderDoc python API directly.
.. warning::
This is an *advanced* feature and care should be taken when doing this. Any errors in the formatting can render the capture file impossible to open! Unless you definitely need to do this you should investigate safer options.
The format of an ASCII section in a RenderDoc capture file is as follows. These lines are commented in this example, but the real format *must not* include extra white space or comments.
.. sourcecode:: text
A # A literal 'A' character, denoting an ASCII section
119 # The length in bytes, as a decimal number, of the contents
4 # The numeric value of the `SectionType` - usually 0 for custom sections
1 # The version of this section, used for backwards compatibility
renderdoc/ui/notes # The name of the section
# There must be a newline after the name of the section
This header can be crafted in a text editor or via any script as it is plain text. As mentioned above, the comments are only for explanation and the actual header *must* only have the data with no trailing white space. Note that the second line with the length in bytes should usually be generated by the script instead of updated manually.
After this header you can put the contents of the section, whatever you would like them to be. In this case we are setting the UI notes section so that we can demonstrate this and show how you could add comments to a capture that can be viewed in the UI. The notes section is formatted as JSON and we want the ``"comments"`` key to be a string to display:
.. sourcecode:: text
{
"comments": "These are some notes!
there isn't really much to put here, except to demonstrate an ASCII section."
}
If you take the header and remove the comments and white space, and concatenate the header then body onto the end of an existing ``.rdc`` file you will find that the UI can display the comments from the body we have provided here.
+16
View File
@@ -0,0 +1,16 @@
.. _currentevent:
Current Frame Event
===================
Aside from information that is immutable or provided across the whole capture, all things in RenderDoc reflect the state snapshotted at a single virtual point - immediately following the execution on the GPU of a single event.
The current event is the :ref:`event ID <eventids>` where this point sits. All things that follow including resource contents like buffers and textures, as well as pipeline state and anything else will be frozen exactly at that point.
Selected vs Current event
-------------------------
When selecting a marker region that contains many events, there are two distinct concepts:
* The 'selected' event ID is the actual marker region itself, the root event which contains other events and will typically have a lower event ID than its children.
* The current event ID, or often referred to just as the event ID, is the effective event where the snapshotted state is taken from. When selecting a marker region this effective event is immediately after all child events have happened - so selecting a region surrounding a pass of many draws will show the results of all rendering within that region as if you had selected the very final child event.
@@ -0,0 +1,151 @@
Descriptors and Bindings
========================
When querying bindings either for a given shader stage or binding type, generally you can use the high-level :doc:`pipeline helper <../examples/pipe_state>` :class:`~renderdoc.PipeState` which lets you query the details of logical bindings without needing to worry about the API details or how those bindings are set. This can be checked against the :doc:`shader_refl` to see what resources are bound where (see also :doc:`../examples/shader_refl`).
This removes a significant amount of API-specific complexity or variance in bindings and lets you directly see e.g. which particular texture was bound to a given shader parameter. All you need to understand is that a 'descriptor' is the umbrella term for how a given resource or set of data is configured and provided to a shader by the CPU-side API.
.. warning::
By its nature this abstraction is complex and doesn't map directly to any one API's concepts.
For the large majority of use cases you *very likely* do not need something more complex than the :class:`~renderdoc.PipeState` helper.
You should only delve into the details below if this high-level helper does not provide the information you need like specific binding points, or you want API-specific information not represented in the information from that helper.
Access to descriptors or fixed resource bindings is an area of graphics APIs that varies significantly between each API. To avoid implementing a large amount of access code duplicated per-API with each API's quirks, RenderDoc builds the high-level helper on top of a more detailed and more direct abstraction.
Allowances are provided for looking up API-specific information or interpreting the bindings with an API-specific lens, if the code knows which API it is being used with and how it wants to interpret that data.
.. _descriptor-abstraction:
Overview
--------
The descriptor abstraction is designed around a modern API structure, with mappings for older APIs that don't fit this natively.
Descriptors for different types of resources are created possibly with different sizes. These descriptors are written into memory in objects called descriptor stores, and finally those descriptor stores are made available to shaders and accessed from declared shader bindings.
This concept maps more closely in some APIs (like Vulkan descriptor sets or D3D12 descriptor heaps) but most APIs do not match this exactly in all cases. Where necessary this abstraction is effectively emulated - for example on D3D11 or OpenGL there are fixed binding slots, so a virtual descriptor store is created with a fixed size representing emulated storage for the available fixed binding slots.
Descriptor Access
-----------------
The RenderDoc replay can be queried for which descriptors were accessed via :meth:`~renderdoc.ReplayController.GetDescriptorAccess`. This returns a number of :class:`~renderdoc.DescriptorAccess` mappings.
Each access indicates a single use of a descriptor by a shader binding:
Descriptor
* :data:`~renderdoc.DescriptorAccess.descriptorStore` = ``Descriptor Store XYZ``
* :data:`~renderdoc.DescriptorAccess.byteOffset` = ``0x1000``
* :data:`~renderdoc.DescriptorAccess.byteSize` = ``64``
Binding
* :data:`~renderdoc.DescriptorAccess.type` = :data:`~renderdoc.DescriptorType.Image`
* :data:`~renderdoc.DescriptorAccess.index` = ``3``
* (if the binding is arrayed) :data:`~renderdoc.DescriptorAccess.arrayElement` = ``500``
.. note::
Depending on the API and usage pattern this may either reflect a dynamically determined access from the shader at runtime, or it may be a statically declared access.
These :class:`~renderdoc.DescriptorAccess` mappings provide a relation of what happened at the current event. "This binding" was accessed and it read from "that descriptor".
Descriptor Contents
-------------------
To find information about the descriptor, you can query the contents of a descriptor via :meth:`~renderdoc.ReplayController.GetDescriptors` and :meth:`~renderdoc.ReplayController.GetSamplerDescriptors`. It is encouraged to call these functions in batch rather than individually per descriptor.
It is safe to call :meth:`~renderdoc.ReplayController.GetDescriptors` on a sampler descriptor and vice versa as long as it is a valid descriptor, a default-initialised structure will be returned if there is a mismatch in descriptor type. On some APIs descriptors can contain both a resource and a sampler in which case both functions will return valid appropriate data.
For this query you need to know the :doc:`descriptor store <resourceids>`, and the offset and size within it referred to. When querying a range with a :data:`~renderdoc.DescriptorRange.count` greater than 1, the size becomes the stride.
.. warning::
Due to the emulation/virtualisation mentioned above where this does not map directly onto a real concept on all APIs, you should not make any assumptions about what valid descriptor sizes and offsets are. Although in some cases these may literally be bytes in user-visible memory, this is not guaranteed.
The descriptor data returned (:class:`~renderdoc.Descriptor`) will have optional fields as it will depend on the particular type of the resource as well as what functionality the API provides. Common information would be not only the :doc:`bound resource <resourceids>` (:data:`~renderdoc.Descriptor.resource`) but also a byte offset (:data:`~renderdoc.Descriptor.byteOffset`) or stride (:data:`~renderdoc.Descriptor.elementByteSize`) of buffer data, or a format cast (:data:`~renderdoc.Descriptor.format`) or mip level (:data:`~renderdoc.Descriptor.firstMip`) of a texture.
.. note::
You can query a descriptor at any time, however without outside knowledge of the valid offset and size of a descriptor within a descriptor store you might not find valid data.
For the common case of looking up the currently accessed descriptors you can use :meth:`~renderdoc.PipeState.GetAllUsedDescriptors`, which provides both resource descriptor and sampler descriptors per accessed descriptors all together.
Shader Bindings
---------------
Finally to correlate to the shader binding in the :doc:`shader reflection <shader_refl>`, each descriptor access gives information about which binding performed the access.
The shader binding is identified by a shader stage, a descriptor type, an index, and an array element. The descriptor type can be :ref:`categorised <binding-types>` as sampler, constant block, read-only or read-write resource according to helper function :func:`~renderdoc.CategoryForDescriptorType` or per-type by :func:`~renderdoc.IsConstantBlockDescriptor` etc.
For example if a texture "DiffuseTexture" was element ``[2]`` in the shader reflection's :data:`~renderdoc.ShaderReflection.readOnlyResources` list, the descriptor access would contain something like:
.. highlight:: python
.. code:: python
access.stage == renderdoc.ShaderStage.Pixel
access.descriptorType == renderdoc.DescriptorType.Image
access.index == 2
access.arrayElement == 0
.. note::
On some APIs, it is possible for descriptor accesses to go directly to a descriptor store from shader code with no binding or reflection information declared at all. In this case the :data:`~renderdoc.DescriptorAccess.index` member will be set to :data:`~renderdoc.DescriptorAccess.NoShaderBinding`.
Location and binding information
--------------------------------
With the above process you can determine which bindings are used, which descriptors they reference, and the contents of those descriptors. However on most APIs there is additional API-specific binding or location information associated either with a binding or a descriptor which can be helpful to display or filter by.
In the shader reflection, each binding contains two additional values: :ref:`fixedBindSetOrSpace <fixed-bind-numbers>` and :ref:`fixedBindNumber <fixed-bind-numbers>`. These values are entirely arbitrary and they serve no purpose within RenderDoc's general APIs for accessing descriptors, as their interpretation is API-specific. On some APIs these values may not be set at all. They are provided for informational purposes, if you want to look up resources in a way only relevant for a particular graphics API.
Similarly, descriptors in a descriptor store may have locations associated. In the same way that you can query descriptor contents with :meth:`~renderdoc.ReplayController.GetDescriptors` you can query locations with :meth:`~renderdoc.ReplayController.GetDescriptorLocations` which returns a list of :class:`~renderdoc.DescriptorLogicalLocation`.
Again this information is API-specific and is not used for any lookups or processing, only for user display or API-specific details.
The logical location contains a ``fixedBindNumber`` value, which depending on the API may match the binding in a shader reflection resource but is not guaranteed to. It also contains a mask of shader stages which can legally access it, the category of shader binding it may contain (if known), and a string which can be used for user display of this particular descriptor.
API-specific information
------------------------
This section provides information about API-specific details and how they are surfaced. This may change in future but generally is expected to be stable.
D3D11
^^^^^
Descriptor access is determined at load time based on shader reflection, all resources are assumed to be used and skipping due to control flow is not considered. The shader reflection ``fixedBindNumber`` gives the register number for each resource with RenderDoc's descriptor types corresponding naturally to ``cX``, ``tX``, ``uX`` and ``sX`` registers.
A single fake descriptor storage object is used for all current bindings, with the descriptor offset identifying the binding.
The descriptor location information gives the stage and category based on the binding, and the string name is an encoded ``t0`` or ``b5`` register declaration corresponding to the HLSL declarations.
This means it is possible to iterate over all descriptors in a store without any access, and identify them according to the D3D11 binding spots. However if you do this note that although UAVs have a descriptor per stage for ease of access, in D3D11's binding model all non-compute stages share the same bindings so these will be duplicated for every stage.
OpenGL
^^^^^^
Descriptor access is determined per-event based on a combination between shader reflection and querying current uniform values. Resources which are declared but known to be unused will be marked with :data:`~renderdoc.DescriptorAccess.staticallyUnused` being set to ``True``. The shader reflection ``fixedBindNumber`` will be set to 0 as the binding number is not necessarily fixed and could vary per-event via uniform.
A single fake descriptor storage object is used for all current bindings, with the descriptor offset identifying the binding.
The descriptor location information gives the stage and category based on the binding, and the string name will be a type and index something akin to ``Tex2D 3`` or ``SSBO 5``.
This means it is possible to iterate over all descriptors in a store without any access, and identify them according to the name given. The descriptor contents will also reflect this as unbound textures will still have the correct texture type when queried for their contents.
D3D12
^^^^^
Descriptor access is combined from access to single bindings being determined statically from reflection, and arrayed/bindless or direct-heap SM6.6 access being fetched at runtime per event. The shader reflection ``fixedBindNumber`` and ``fixedBindSetOrSpace`` gives the register number and register space for each resource.
RenderDoc does not directly provide root signature mappings, but the unrolled root signature is available in the D3D12 pipeline state member :data:`~renderdoc.D3D12State.rootSignature`.
SM6.6 direct-heap access will be identified with a descriptor access with :data:`~renderdoc.DescriptorAccess.index` set to :data:`~renderdoc.DescriptorAccess.NoShaderBinding`.
Descriptor storage is primarily in descriptor heap objects, however root constants, root descriptors, and static samplers will be stored in virtualised storage elsewhere. The exact objects used as storage of these descriptor for querying should not be relied upon. Similarly the descriptor size in all cases is RenderDoc-defined and will not necessarily match the descriptor size used in D3D12 during capture.
Descriptor locations have their index in the heap listed as the ``fixedBindNumber`` and the string name is the SM6.6 indexed ``ResourceDescriptorHeap[]`` or ``SamplerDescriptorHeap[]``. As descriptors are implicitly untyped and fully visible, there is no type or shader stage information in a descriptor's location.
Vulkan
^^^^^^
Descriptor access is combined from access to single bindings being determined statically from reflection, and arrayed/bindless access being fetched at runtime per event. The shader reflection ``fixedBindNumber`` and ``fixedBindSetOrSpace`` gives the binding number and set number for each resource.
Descriptor storage is primarily in descriptor set objects, however push constants, specialisation constants, and immutable samplers will be stored elsewhere. The exact objects used as 'virtual' storage of these descriptor for querying should not be relied upon.
Descriptor locations have their index listed the as binding number within the set, and the string name will be the ``bind[arrayIndex]`` flattened value with arrays unrolled contiguously. The type will only reflect the most recently written descriptor data and may be undefined for unwritten descriptors even if only one type is valid, and the visible shader mask will be determined by the descriptor set layout visibility flags.
+38
View File
@@ -0,0 +1,38 @@
.. _eventids:
Event IDs
=========
The events within a capture are all assigned Event IDs or EIDs for short. These are simple integers and the first real event in a capture is given EID 1. EID 0 represents the point just before the first event happens.
Actions like draws, dispatches and copies are also events and so they are assigned EIDs as normal.
Event IDs *typically* correspond 1-to-1 with function calls made by the application but this is not guaranteed. With function calls like multi-draw or indirect execution it may be that a single CPU-side function call turns into multiple events on the GPU and so there will be multiple Event IDs assigned.
Event IDs are normally contiguous and ascending starting from 1, however there is an exception to this. When a capture has no marker regions in it and you have the option enabled to add fake marker regions, these will be given higher EIDs so you may find a marker region with EID 100 with children 5-10.
For how event IDs are used to browse the frame and control RenderDoc's replay, see also information about :ref:`the current event <currentevent>`.
.. _actions:
Actions
-------
Actions are typically what are used to browse the frame. Actions include any event which will execute shader code such as a draw or dispatch, but also includes anything that can modify memory or have visible side-effects like copies and clears. Although debug markers do not modify anything and have no semantic impact they are considered actions so that they can form the hierarchy that organises the actions in a capture.
RenderDoc organises event information around actions, as the list of actions in a capture can be returned via :meth:`~qrenderdoc.CaptureContext.CurRootActions` or :meth:`~renderdoc.ReplayController.GetRootActions`. These actions are those immediately at the root of the capture but each action can have children - commonly marker regions but also multi-draw calls.
Actions are represented as a :class:`~renderdoc.ActionDescription` which contains a number of optional properties depending on the type of the action. Some properties are unified between different variations for ease, so e.g. :data:`~renderdoc.ActionDescription.numIndices` represents both the number of rendered indices for an indexed draw as well as the number of rendered vertices for a non-indexed draw.
For historical reasons in RenderDoc each action contains a list of events in :data:`~renderdoc.ActionDescription.events`. Each action has the events leading up to it - e.g. for a draw it will have any state-setting that happened between the previous action and the current one.
.. _apiparams:
API parameters
--------------
The :class:`~renderdoc.APIEvent` does not contain any details about the parameters to the call itself or even its name, it is a lightweight representation. For getting this information you can cross-reference to the structured data representation which contains an iterable record of the parameters and their contents.
This can be looked up by cross-referencing from :data:`~renderdoc.APIEvent.chunkIndex` into the list of chunks in :class:`~renderdoc.SDFile` obtained from :meth:`~qrenderdoc.CaptureContext.GetStructuredFile` or :meth:`~renderdoc.ReplayController.GetStructuredFile`.
For more information see :doc:`the page on Structured Data <structured_data>`.
@@ -0,0 +1,47 @@
Frame Viewers
=============
A common desire for a UI extension is to receive callbacks when certain actions happen in order to process the capture and current event. This is used internally for most panels to update their state to reflect newly selected events and react to captures opening and closing.
.. note::
This interface is not specific to UI extensions but extreme care should be taken about adding capture viewers from scripts as the lifetime is harder to manage. If a capture viewer is added it will continue to receive events until it is removed, which can be harder to do if the object is not still accessible.
An interface (:class:`~qrenderdoc.CaptureViewer`) is provided which can be inherited from and implemented in python to receive these events, as shown below:
.. highlight:: python
.. code:: python
import qrenderdoc as qrd
class Viewer(qrd.CaptureViewer):
def OnCaptureLoaded(self):
print("A new capture was loaded")
def OnCaptureClosed(self):
print("The capture was closed")
def OnSelectedEventChanged(self, eventId):
print(f"The selected event is {eventId}")
def OnEventChanged(self, eventId):
print(f"The current event is {eventId}")
view = Viewer()
def register(version, ctx):
ctx.AddCaptureViewer(view)
def unregister():
pyrenderdoc.RemoveCaptureViewer(view)
Within a UI extension this will add a new viewer when the extension is initialised. Note that it is important to remove the viewer when the extension is unregistered - when reloading an extension if this isn't done the old viewers will remain alive and will continue to get callbacks.
The object will receive callbacks both when a capture is loaded (via ``OnCaptureLoaded``) and closed (via ``OnCaptureClosed``). These are both called *while the capture is open*, so immediately inside ``OnCaptureLoaded`` it is safe to call replay functions and in ``OnCaptureClosed`` queries will still include the capture status. It is not recommended that you perform any replay calls during capture closing as they may not all be safe and there is no guaranteed that all :ref:`asynchronous replay invokes <pythreading>` will be processed.
The ``OnEventChanged`` and ``OnSelectedEventChanged`` are called when an event is selected. The difference between them comes down to what the :ref:`effective event <currentevent>` is upon selecting a marker or other region with many children.
.. note::
Because of this difference it is possible for the selected event to change *without* the event changing, because a user could first select the root of a marker region, and then the last event within it. In this case the selected event would change even though the effective event does not and there would only be a call to ``OnSelectedEventChanged`` and no corresponding call to ``OnEventChanged``.
If a capture is already loaded when a viewer is first added, ``OnCaptureLoaded``, ``OnEventChanged``, and ``OnSelectedEventChanged`` will immediately be called, so there is no need to manually account for whether a capture is open or not.
+25
View File
@@ -0,0 +1,25 @@
Further Reading on Python Scripting
===================================
This section contains explanations of common concepts, useful interfaces, and advanced uses of python that go beyond scripts that are a few lines to accomplish a quick task.
If you are writing python scripts more often, these concepts can let you go into much more complex and powerful customisations of RenderDoc's workflow.
.. toctree::
:maxdepth: 1
resourceids
event_ids
curevent
frame_viewers
replay_controller
threading
lifetimes
shader_refl
descriptors_bindings
structured_data
miniqt
outputs
capture_access
launching_programs
remote_replay
@@ -0,0 +1,42 @@
Launching programs manually
===========================
If you are using the RenderDoc UI for scripting, you should use :doc:`the UI interfaces <../examples/exe_launching>` for launching executables. This integrates well with the UI and displays to the user what is happening while still being automatable.
If you are using the RenderDoc python module directly and do not have the UI present, then you can launch and capture from executables directly.
Starting a program
------------------
We will assume you know the program you want to launch and the capture options you want to provide, as detailed in the :doc:`UI example <../examples/exe_launching>`. From here you will use :func:`~renderdoc.ExecuteAndInject` to launch the program.
This function will take all the parameters that can be customised when launching an executable, including not only the executable path and working directory but also :class:`~renderdoc.EnvironmentModification` changes to environment variables, any options with :class:`~renderdoc.CaptureOptions`, and a target path for any captures to be made.
You can choose whether or not this function will be blocking - if you wait for the program to exit then control will not return until the program has exited. This is not recommended when automating as it means you will need to determine which captures were made in another way.
Typically you would not wait, and use the :class:`~renderdoc.ExecuteResult` to determine whether the program launched correctly and how to connect to it.
Connecting to a running program
-------------------------------
If the program was launched successfully, then :data:`~renderdoc.ExecuteResult.ident` tells you the identifier of the running program that can be used to connect to it. It is also possible to enumerate available identifiers on a particular hostname using :func:`~renderdoc.EnumerateRemoteTargets` which allows iterative querying of available identifiers - it will not be detailed here as you are assumed to have the ident from :func:`~renderdoc.ExecuteAndInject` above.
You can make a target control connection to a particular program by connecting to it using :func:`~renderdoc.CreateTargetControl`. This requires the hostname and identifier above, the hostname can be blank for locally launched programs. Only one target control connection can be made to a program at any one time - the client name specified when connecting can be used to disambiguate, and it is also possible to forcibly disconnect any existing connection when connected - RenderDoc assumes co-operation rather than competition for these connections between multiple users.
If the connection was made successfully a :class:`~renderdoc.TargetControl` will be returned which must be managed by python and closed using :meth:`~renderdoc.TargetControl.Shutdown` when finished with.
Target control
--------------
A target control connection allows you to both send and receive messages to the running program, to get information about its status as well as to send commands. Commands can be sent at any time using e.g. :meth:`~renderdoc.TargetControl.TriggerCapture` or :meth:`~renderdoc.TargetControl.QueueCapture`. Responses from these will be received as messages, as well as messages for other information such as new child processes or new captures being made (which may be triggered by user actions).
The target control connection uses a simple message loop to return information to the user without blocking. Calling :meth:`~renderdoc.TargetControl.ReceiveMessage` will check for a new message and return either the new message or a no-op message. The receive function internally will wait a short time if no message is pending so it is safe to call repeatedly in a loop with no extra waits. This also keeps the connection alive so you must call :meth:`~renderdoc.TargetControl.ReceiveMessage` at least once every few seconds to maintain the connection.
The message returned will have a type as specified by :class:`~renderdoc.TargetControlMessageType`, which can be switched on to examine the different data available in the message types. For example if a new capture is made then a :data:`~renderdoc.TargetControlMessageType.NewCapture` type message will be returned and the :data:`~renderdoc.TargetControlMessage.newCapture` member will be valid containing the information about the capture.
Transferring captures
---------------------
If the target control connection is local, any new captures identified will be immediately replayable using :doc:`capture_access` and :meth:`~renderdoc.CaptureFile.OpenCapture`. If the connection is remote it may be necessary to transfer the capture across the connection from the remote machine. This can be done using :meth:`~renderdoc.TargetControl.CopyCapture` and will be notified using a :data:`~renderdoc.TargetControlMessageType.CaptureCopied` message.
It is also possible to leave the capture on the remote machine and use a :class:`~renderdoc.RemoteServer` connection to replay directly on the remote machine - see :doc:`remote_replay`.
+72
View File
@@ -0,0 +1,72 @@
.. _lifetimes:
Object Lifetimes
================
The python API exposed by RenderDoc is a fairly thin auto-generated wrapper around the underlying C++ API. This means that a significant amount of functionality is exposed 'for free', but it also means that the API does not always perfectly match the expected python semantics. This can have unexpected behaviour particularly around how long these objects are valid for.
Here we will talk about the lifetime management of objects and what things to look out for, with particular note of some common areas where things do not behave as they normally would for python objects.
We also mention when objects should be treated as read-only, which is not a typical python concept. Normally in python objects are either copied or references are freely mutable, but in some cases with the RenderDoc python API you should avoid modifying python objects which are owned by C++.
Plain structures
----------------
With exceptions listed below, most plain data structures can be treated normally by python and have natural python reference counting. Modifying the properties of these structures will not affect underlying data stored in C++ and they can be held naturally in python variables to be destroyed when they are no longer accessible.
Lists of such structures also behave as common python lists, with each structure a reference inside the list.
These structures can also be created in python just like normal objects, with no special handling.
RenderDoc-owned objects
-----------------------
Any structure with a lifetime exclusively managed by RenderDoc such as :class:`~renderdoc.ReplayController` or :class:`~renderdoc.CaptureFile` can't be created or destroyed directly in python. A handle to these is **only** valid for as long as the underlying object exists, and it is possible for the underlying object to be destroyed while a handle still exists in python.
In this case, it is invalid to access the handle after the object is destroyed and this will very likely lead to crashes.
Typically these objects are either only available while a given capture is open, or must be explicitly destroyed with a ``Shutdown`` method, but this will be context-dependent so care should be taken.
Actions
-------
The :class:`~renderdoc.ActionDescription` objects returned by queries for the current set of actions in a capture contain members :data:`~renderdoc.ActionDescription.parent`, :data:`~renderdoc.ActionDescription.previousAction`, :data:`~renderdoc.ActionDescription.nextAction`, which contain references to neighbouring actions.
These members are internally represented by C++ pointers and so do not refer to the same copied objects that may be returned to and owned by python. Their properties will be the same, but care must be taken to **not** modify through these references as this will affect the internal C++ structures. They should be treated as read-only (which is not representable in python except via deep copy).
This also means that although any :class:`~renderdoc.ActionDescription` objects stored by python will remain valid indefinitely, these members will no longer be valid after the capture is closed.
Structured data
---------------
Structured data as returned by :class:`~renderdoc.SDFile` possibly takes up a huge amount of memory for storage. For this reason, it is not copied and instead the C++-owned object is returned to python directly.
This means that you should treat the object as well as all chunks and buffers within as read-only and ensure that it is not used beyond the scope of where the capture is open.
Shader Reflection
-----------------
:class:`~renderdoc.ShaderReflection` objects can be quite numerous in some captures, as well as potentially including original shader source that takes up a large amount of memory. This means they are not feasible to store as copies and instead these reflection objects are returned to python directly.
This means that you should treat these objects as read-only and ensure that they are not used beyond the scope of where the capture is open.
Widgets
-------
When using :class:`~qrenderdoc.MiniQtHelper` it is possible to access Qt widgets from python. These handles are directly into the Qt objects themselves and so must respect the lifetime rules of Qt which are not reference counted but are owned from parents to children. Qt widgets sit in a hierarchy from the top level window down through each widget contained within. When a Qt widget is destroyed, it also destroys all children.
All widget handles returned via RenderDoc's APIs are *not* owned by python, and must be destroyed implicitly as above or explicitly with :class:`~qrenderdoc.MiniQtHelper.DestroyWidget`.
.. note::
If using PySide and creating widgets through its interfaces, you should refer to PySide's documentation for ownership, as that will differ.
This also means that it is possible to keep a reference to a widget in python even after it has been destroyed. These handles are no longer valid once a widget is destroyed and must not be used from python or passed into any other API functions. When creating a top-level widget with :meth:`~qrenderdoc.MiniQtHelper.CreateToplevelWidget` you can provide a callback to be called when the widget is closed, so that you can be aware that any children are no longer valid.
Some panels are considered 'temporary' when attached to the UI - for example viewing a constant buffer with :meth:`~qrenderdoc.CaptureContext.ViewConstantBuffer` will return a :class:`~qrenderdoc.BufferViewer` that views the given constant buffer but when a capture is closed all constant buffers will be removed as they are no longer referenced. You should take care not to access these handles after the capture is closed as they will refer to deleted objects.
Shader traces
-------------
When debugging a shader via the RenderDoc API, a :class:`~renderdoc.ShaderDebugTrace` is returned with information about the trace as well as the debug engine.
This trace's lifetime must be explicitly managed, and destroyed with :meth:`~renderdoc.ReplayController.FreeTrace`. After calling that function to destroy it, the trace and all members must not be accessed.
+99
View File
@@ -0,0 +1,99 @@
Mini-Qt Helper
==============
By default RenderDoc ships with PySide-provided Qt bindings to allow users in UI extensions access to the Qt API for creating their own UIs.
The full Qt UI has a fair amount of complexity though that is outside the scope of this documentation, and may be inconvenient for small or quick UIs. For that reason RenderDoc itself provides a limited simplified API for creating UI elements - :class:`~qrenderdoc.MiniQtHelper`.
.. note::
Although intended for only interacting with user-created UI elements, the helper does use the normal Qt API internally which means there is no distinction made between user-created widgets and the baseline widgets in the RenderDoc UI itself.
Care should be taken for any interactions like this as it is possible to modify or interact with the normal UI through this helper.
Creating widgets
----------------
Qt is a declarative UI system, you create widgets in a hierarchy with layout information. RenderDoc's docking system allows you to create a top-level widget that becomes docked as a panel, and then you have full control over the contents of the panel which can be changed dynamically.
Creating a top-level widget is done with :meth:`~qrenderdoc.MiniQtHelper.CreateToplevelWidget`. This function takes a string for the window title of the panel as well as an optional :func:`~qrenderdoc.MiniQtHelper.WidgetCallback` that will be called if the top level widget is closed.
A number of standard interactive or display widget types are available, each with its own properties:
- :meth:`~qrenderdoc.MiniQtHelper.CreateButton`
- :meth:`~qrenderdoc.MiniQtHelper.CreateCheckbox`
- :meth:`~qrenderdoc.MiniQtHelper.CreateComboBox`
- :meth:`~qrenderdoc.MiniQtHelper.CreateRadiobox`
- :meth:`~qrenderdoc.MiniQtHelper.CreateLabel`
- :meth:`~qrenderdoc.MiniQtHelper.CreateProgressBar`
- :meth:`~qrenderdoc.MiniQtHelper.CreateSpinbox`
- :meth:`~qrenderdoc.MiniQtHelper.CreateTextBox`
Interactive widgets can take a :ref:`callback <widget-callback>` for when they are changed or interacted with, as well as the ones with state having queries to fetch their state.
These functions return a handle to the widget, which is owned by python but has an explicit lifetime. A top-level panel that is closed by the user or by :meth:`~qrenderdoc.MiniQtHelper.CloseToplevelWidget` will automatically destroy all of its children recursively, which is the common way to handle :doc:`lifetimes <lifetimes>` as long as all widgets have been added. You should be careful not to access any lingering widget handles after they may have been closed as they are no longer valid.
If a widget is not currently attached anywhere it must be destroyed explicitly with :meth:`~qrenderdoc.MiniQtHelper.DestroyWidget`, which similarly will destroy any of its children.
Widget layouts
--------------
Widgets can't be placed freely using this API, but instead are laid out in one of three ways that adjust to the size of the available space:
#. In a vertical container where widgets are added in order, with :meth:`~qrenderdoc.MiniQtHelper.CreateVerticalContainer`.
#. In a horizontal container where widgets are added in order, with :meth:`~qrenderdoc.MiniQtHelper.CreateHorizontalContainer`.
#. In a grid container where widgets are placed in 2D cells, with :meth:`~qrenderdoc.MiniQtHelper.CreateGridContainer`.
These containers can be used recursively to create more complex UI layouts. By default widgets will either remain a fixed size where it makes sense (e.g. for buttons or checkboxes) and expand to fill available space (e.g. text boxes).
Widgets are added to these containers with :meth:`~qrenderdoc.MiniQtHelper.AddWidget` and :meth:`~qrenderdoc.MiniQtHelper.InsertWidget` for vertical or horizontal containers, and :meth:`~qrenderdoc.MiniQtHelper.AddGridWidget` for grid containers.
Widgets can't be removed individually but can be removed all at once using :meth:`~qrenderdoc.MiniQtHelper.ClearContainedWidgets`. You can query for the current set of children with :meth:`~qrenderdoc.MiniQtHelper.GetNumChildren` and :meth:`~qrenderdoc.MiniQtHelper.GetChild`.
By default widgets that can contain others have an implicit vertical container - these include top level widgets created with :meth:`~qrenderdoc.MiniQtHelper.CreateToplevelWidget` and group boxes created with :meth:`~qrenderdoc.MiniQtHelper.CreateGroupBox`.
Widget properties
-----------------
Most widgets have some kind of state associated, for example a label or button has its text contents, a checkbox has a flag of whether it's on or off, etc.
Although not listed exhaustively here, functions to both query and set these states are provided. Most functions are generic and will apply to many different widgets - for example :meth:`~qrenderdoc.MiniQtHelper.SetWidgetText` will set the widget's "text" property, but that will mean different things depending on the widget. For a label this directly sets the text content of the label, but for example on a group box or top-level widget it sets the title.
This can also be used to query for or set the state of user-interactive elements such as checkboxes with :meth:`~qrenderdoc.MiniQtHelper.IsWidgetChecked` or :meth:`~qrenderdoc.MiniQtHelper.SetWidgetChecked`. If called on an invalid widget these queries will return empty data and the setters will do nothing.
Some general widget properties can also be set here, such as with :meth:`~qrenderdoc.MiniQtHelper.SetWidgetFont` to change the font of a widget including bold or italic, or :meth:`~qrenderdoc.MiniQtHelper.SetWidgetVisible` and :meth:`~qrenderdoc.MiniQtHelper.SetWidgetEnabled` which can show/hide or enable/disable widgets respectively.
.. _widget-callback:
Widget callbacks
----------------
A number of functions offer a callback when some event happens, such as a widget being pressed or changed. Each of these places uses the same form of callback:
.. highlight:: python
.. code:: python
def WidgetCallback(context: qrenderdoc.CaptureContext, widget: QWidget, text: str):
...
The first parameter is the same :class:`~qrenderdoc.CaptureContext` as is available elsewhere, with the widget being the one emitting the event. The text parameter is contextually relevant and depends on the exact event, but could provide the current or selected text for example.
Widget callbacks are optional, and do not have to be provided, but note that it is not currently possible to add or remove callbacks after widget creation.
Example and Conclusion
----------------------
A simple example can be found under the name "Mini-Qt UI" in the python scripting window.
.. only:: html and not htmlhelp
:download:`Download the example script <../examples/miniqt_ui.py>`.
.. literalinclude:: ../examples/miniqt_ui.py
.. figure:: ../../imgs/python/MiniQtHelper.png
The window produced by the example.
This is not an exhaustive API reference listing all possible pieces of functionality, you are encouraged to look at the :class:`~qrenderdoc.MiniQtHelper` documentation for the full list of features available.
This API does not allow you to create complex and highly controlled UIs, but for simple interfaces to allow for control and display of data it gives a quick way to create those UIs.
+45
View File
@@ -0,0 +1,45 @@
Replay Outputs
==============
Some of RenderDoc's functionality for analysis can't be easily presented through pure text or data, and is far better to be represented visually. For example the texture overlays or 3D mesh previews.
This is handled through RenderDoc's replay output system.
Creating an output
------------------
RenderDoc creates replay outputs onto a native window or widget, with a 1:1 relationship. Some APIs like D3D12 will take exclusive access to a native window and make it impossible to re-use afterwards, so it is recommended to recreate any widget after you are finished using for a replay output. If you use :meth:`~qrenderdoc.MiniQtHelper.CreateOutputRenderingWidget` this is automatically handled for you and you don't have to worry about it.
Once you have a window or widget you want to display to, it is necessary to retrieve the :class:`~renderdoc.WindowingData` that RenderDoc can use internally to refer to it. if you have used :meth:`~qrenderdoc.MiniQtHelper.CreateOutputRenderingWidget` then you can use :meth:`~qrenderdoc.MiniQtHelper.GetWidgetWindowingData` to fetch it directly. Otherwise you will need to use a platform specific function like :func:`~renderdoc.CreateWin32WindowingData` or :func:`~renderdoc.CreateXCBWindowingData` to create windowing data for a native window.
It is also possible to render to a fixed off-screen fake window. You can use :func:`~renderdoc.CreateHeadlessWindowingData` to create a fixed-size window with an internal buffer that can then be queried later using :meth:`~renderdoc.ReplayOutput.ReadbackOutputTexture`. This could be used for example to save results to an image file on disk.
With the windowing data and a :class:`~renderdoc.ReplayController` you can call :meth:`~renderdoc.ReplayController.CreateOutput` to create a :class:`~renderdoc.ReplayOutput` of a given type - either texture or mesh rendering. Once created you should manage the :doc:`lifetime <lifetimes>` of the output and only use it on the :ref:`same thread <pythreading>` as the :class:`~renderdoc.ReplayController`, and call :meth:`~renderdoc.ReplayOutput.Shutdown` when you are finished.
Configuring an output
---------------------
Once created, an output is configured using :meth:`~renderdoc.ReplayOutput.SetTextureDisplay` or :meth:`~renderdoc.ReplayOutput.SetMeshDisplay` depending on its type. These take a configuration struct which specifies everything needed to display the relevant resources. For a mesh display you will need a camera which can be initialised with :func:`~renderdoc.InitCamera` - two camera types are available, flycam (which can double as a look-at camera) and arcball. These either have position + direction, or position + angle + distance respectively.
Rendering an output
-------------------
Outputs are rendered and refreshed by calling :meth:`~renderdoc.ReplayOutput.Display`. You should call this function when needed to re-draw - either after changing the configuration or if the native window needs to be redrawn for platform specific reasons. If you are using a widget created with :meth:`~qrenderdoc.MiniQtHelper.CreateOutputRenderingWidget` this updating automatically handled for you and you only have to manually call :meth:`~renderdoc.ReplayOutput.Display` after changing the configuration.
Sub-windows
-----------
For texture outputs, it is common to want to also display small thumbnails and RenderDoc's replay outputs have a system for handling child thumbnails with low overhead. You can call :meth:`~renderdoc.ReplayOutput.AddThumbnail` and pass it the :class:`~renderdoc.WindowingData` of the window to render onto. This window is then owned until the replay output is shut down. You can manually release all current thumbnails with :meth:`~renderdoc.ReplayOutput.ClearThumbnails` without shutting down the main replay output itself.
You can also render a thumbnail and return the raw bytes for quick previews using :meth:`~renderdoc.ReplayOutput.DrawThumbnail` which returns the bytes directly.
Another similar helper is the pixel context, which allows you to render a fixed highly-zoomed view of the current texture and location to another window. As with thumbnails you can pass a native window using :meth:`~renderdoc.ReplayOutput.SetPixelContext` and this window will then be owned by the replay output until it is shut down. It will be automatically rendered when you call :meth:`~renderdoc.ReplayOutput.Display` on the main output, and the location displayed can be updated with :meth:`~renderdoc.ReplayOutput.SetPixelContextLocation`.
Extra helpers
-------------
For some replay output types there are extra helpers that are available.
On texture displaying outputs, if available you can query the :ref:`IDs <resourceids>` for internal textures used for displaying the current texture overlay (:meth:`~renderdoc.ReplayOutput.GetDebugOverlayTexID`) or output from a custom display shader (:meth:`~renderdoc.ReplayOutput.GetCustomShaderTexID`).
These IDs are of internal resources and so should not be cached for long as the texture may be destroyed the next time the configuration or current event is changed, but can be used to obtain the direct contents of the output of those processes.
@@ -0,0 +1,40 @@
Remote Replay
=============
RenderDoc supports remotely replaying a capture, with display and UI interaction happening locally. When using RenderDoc's scripting through the UI, remote replay is generally invisible to the script. The user will select a given host to replay on and as far as any script calls are concerned it is available with all functionality as-if it were open locally.
If you are running purely from a script without the UI to help, you will need to handle making the remote server connection yourself.
Remote Servers
--------------
RenderDoc's remote replay works via RPC over a socket to an instance of RenderDoc running what is terms a remote server. Any python script can launch and listen as a remote server by calling :func:`~renderdoc.BecomeRemoteServer`, as well as the Android version of RenderDoc functioning as a remote server by default. Launching a remote server is not detailed in this guide and it is assumed that you have one running and know its hostname.
Connecting to a remote server is done with :func:`~renderdoc.CreateRemoteServerConnection`, which returns a :class:`~renderdoc.RemoteServer` if the connection was successful. From the remote server connection you can then both launch applications to create new captures as well as replaying capture files.
To launch a program for capture you can use :meth:`~renderdoc.RemoteServer.ExecuteAndInject`. This function is analogous to :func:`~renderdoc.ExecuteAndInject` detailed in :doc:`launching_programs` except it happens on the remote host.
Limited remote browsing is possible using :meth:`~renderdoc.RemoteServer.GetHomeFolder` and :meth:`~renderdoc.RemoteServer.ListFolder` to allow users to browse for remote executables for launch. Note that on some platforms this may not list a literal filesystem but a virtualised list of available applications. It is expected that the results will be compatible with the executable needed in :meth:`~renderdoc.RemoteServer.ExecuteAndInject`.
When disconnecting from a remote server there are two options - :meth:`~renderdoc.RemoteServer.ShutdownConnection` and :meth:`~renderdoc.RemoteServer.ShutdownServerAndConnection`. The former only closes the connection and leaves the remote server running. The latter asks the remote server to shut its process down and then closes the connection. When a remote server closes a connection it will delete any temporary captures that it owns which can be specified using :meth:`~renderdoc.RemoteServer.TakeOwnershipCapture`.
You are expected to keep the remote server connection alive with :meth:`~renderdoc.RemoteServer.Ping` when not actively performing any other commands. If you do not, the connection may time out due to inactivity.
Capture transfer
----------------
Captures can only be opened by the remote server if they exist on disk on the server. Captures can be transferred in both directions using the remote server connection - copied from the server using :meth:`~renderdoc.RemoteServer.CopyCaptureFromRemote` and copied to the server using :meth:`~renderdoc.RemoteServer.CopyCaptureToRemote`.
Copying a capture to the server does not specify a target filename. The server itself decides where to store the file and returns the pathname from the function. This file on the replay server is owned by it as a temporary capture and will be deleted when the connection is closed.
API selection
-------------
When connected to a remote host you can query what APIs it supports for replay via :meth:`~renderdoc.RemoteServer.RemoteSupportedReplays`. This can be used to determine whether or not a capture is capable of being replayed, or possibly for selection of a remote host among several options.
For the remote replay, RenderDoc must use an API locally in a limited fashion to display textures and render meshes. This is completely independent of the API used in the capture and only requires minimal functionality. You can enumerate the available APIs for local proxying using :meth:`~renderdoc.RemoteServer.LocalProxies`.
Opening a capture
-----------------
To open a capture for replay you use :meth:`~renderdoc.RemoteServer.OpenCapture`. This is analogous to :meth:`~renderdoc.CaptureFile.OpenCapture` and returns the same tuple including a :class:`~renderdoc.ReplayController` if successful. When opening a capture you can specify a local proxy API using an index in the returned list from :meth:`~renderdoc.RemoteServer.LocalProxies`. If you have no preference (this is recommended as it usually does not matter) you can pass ``-1``.
@@ -0,0 +1,18 @@
Replay Controller
=================
The :class:`~renderdoc.ReplayController` gives direct access to RenderDoc's analysis. Some of the information it provides is cached and provided by the higher level interface such as pipeline states and lists of actions, so does not need to be queried again.
Exclusively available through the controller is more complex or stateful data such as the current contents of buffers (:meth:`~renderdoc.ReplayController.GetBufferData`), textures (:meth:`~renderdoc.ReplayController.GetTextureData`), as well as the results of analysis steps like pixel history (:meth:`~renderdoc.ReplayController.PixelHistory`) or shader debugging (:meth:`~renderdoc.ReplayController.DebugPixel`, :meth:`~renderdoc.ReplayController.DebugThread`).
Most information the replay controller will return will be context-specific, varying across the frame depending on the :ref:`current event <currentevent>`. Information that does not vary will include things like the lists of buffers, textures, resources, or the frame information and API properties.
The :class:`~renderdoc.ReplayController` also provides access to functionality like shader editing, allowing you to compile custom shader source and receive a new shader to replace the existing one in the capture.
The python bindings for this level of interface are more 'literal' and provide not much more safety than the underlying C++ API. For this reason illegal or invalid calls can cause corruption, unexpected behaviour or even :ref:`crashes <python-crashes>`. Working at this level gives the greatest level of flexibility but does require the greatest level of responsibility.
Using the replay controller directly can cause desyncs from the UI as there is nothing to prevent you changing the internal state in ways the UI may not reflect. It is strongly recommended that for example changing the current frame event is done via the UI interfaces so the UI can remain consistent.
Obtaining a :class:`~renderdoc.ReplayController` from a UI script can be done in two ways. Firstly :meth:`~qrenderdoc.CaptureContext.GetBlockingController` can return a *blocking* replay controller, if available while a capture is open. Otherwise using :meth:`~qrenderdoc.ReplayManager.AsyncInvoke` or can provide asynchronous access. Refer to the dedicated page for more information about :ref:`RenderDoc's threading <pythreading>`.
Replay controllers must not be used after a :ref:`capture is closed <lifetimes>`.
+26
View File
@@ -0,0 +1,26 @@
.. _resourceids:
Resource IDs
============
Within RenderDoc all resources are referenced internally by a unique ID. This ID should be considered arbitrary and unordered but will never overlap so will always uniquely identify an object. The python class for this ID is :class:`~renderdoc.ResourceId`. IDs can't be constructed directly by python scripts so you must find the ID in some reference point - from a list of resources by name, or from a pipeline binding point.
Resource IDs are only unique within a capture, and there is no correlation between the IDs used in one capture and another.
Every API object like a texture, buffer, or shader will have its own ID. When looking up a resource or cross referencing from an API call or pipeline binding you will often find that a resource is referenced only by its ID.
Anywhere that having ``NULL`` or no resource is a valid concept you may find a "Null" Resource ID. This can be compared either to a default-constructed resource ID e.g. ``renderdoc.ResourceId()`` or explicitly via the helper :meth:`~renderdoc.ResourceId.Null`.
.. highlight:: python
.. code:: python
pipe = pyrenderdoc.CurPipelineState()
buffer_id = pipe.GetIBuffer().resourceId
if buffer_id == renderdoc.ResourceId():
print("No index buffer is bound")
else:
buffer_info = pyrenderdoc.GetBuffer(buffer_id)
print(f"Index buffer is {buffer_info.length} bytes in size")
+196
View File
@@ -0,0 +1,196 @@
Shader Reflection
=================
RenderDoc provides access to shader reflection data through the :class:`~renderdoc.ShaderReflection`, which shows the interfaces that the shader expects to be bound as well as their format and contents. The amount of data that is available will depend heavily on the API as well as the amount of metadata kept in the shader or available as separate debug information - on some APIs information may be stripped out at which point RenderDoc will show the best data that it can.
This reflection data is presented as an abstraction to enable as much as possible for scripts to be written API-agnostically, but allow API-specific concepts to be easily addressed in most cases. We will also list how API-specific concepts map to RenderDoc's reflection.
.. note::
We will not go into details about how resources are bound to shaders as that is covered in :doc:`descriptors_bindings`. We will only mention the details of how resources may be bound outside of those normal paths.
.. _binding-types:
.. _binding-categories:
Binding interfaces
------------------
RenderDoc maps all kinds of resource bindings into four different broad categories. In some cases there are resources which may not fit cleanly into one or the other but generally there should be no surprises if you are familiar with typical API concepts:
#. Constant blocks (:class:`~renderdoc.ConstantBlock`)
These are bindings which are formatted as plain values, and are read-only to the shader. The most common example would be constant/uniform buffers. Depending on the API there may be multiple ways for these buffers to be bound but any buffer which is intended to be used only for small amounts of constant data will be listed as a constant block.
Bindings which *could* be written to but are either marked read-only or are coincidentally only read from are not included here.
#. Samplers (:class:`~renderdoc.ShaderSampler`)
These are separate sampler-only bindings, bound to the shader via API binding mechanisms. This does not include possible combined texture-and-sampler objects which are supported on some APIs.
#. Read-only resources (:class:`~renderdoc.ShaderResource`)
These are other types of resources which are bound explicitly as read-only to the shader. This includes textures as well as type-converted buffers (sometimes called texture buffers) and formatted or structured buffers.
Some overlap exists here between constant blocks and buffer read-only resources. Each API has its own way of distinguishing a read-only buffer from a constant block, and that will be detailed below. Typically the distinction is that a read-only buffer is intended for reading significantly more data than a constant block and may have higher API limits such as allowing more than 64KB of data, or it may be designed to have a large array of small structures or even vectors.
#. Read-write resources (:class:`~renderdoc.ShaderResource`)
Similarly to read-only resources above, these bindings can be either textures or buffers but can be modified and written by the shader as well as being read.
If a resources is marked as 'write only' it will still be listed here under read-write resources as it is not a read-only resource.
.. _fixed-bind-numbers:
Common properties
-----------------
All types of bindings have a few common properties that mean the same regardless of which type of binding is being examined:
* Each binding has a ``name`` member with the name of the variable in the shader.
* For APIs that support arrays of bindings, ``bindArraySize`` gives the size of the array as declared in the shader.
* ``fixedBindSetOrSpace`` and ``fixedBindNumber`` may give an API-specific notion of a particular binding point where this binding exists. This may be absolute or relative to the type. These values are informational only and are not used for locating any bindings or resources.
D3D11
On D3D11 ``fixedBindNumber`` gives the register number of the binding, and ``fixedBindSetOrSpace`` is ignored.
D3D12
On D3D12 ``fixedBindSetOrSpace`` gives the ``space`` of the binding and ``fixedBindNumber`` gives the register number, which are then remapped in the root signature.
OpenGL
On OpenGL ``fixedBindNumber`` and ``fixedBindSetOrSpace`` **are not used** because the binding for a given resource may be dynamic based on the uniform value at runtime.
Vulkan
On Vulkan ``fixedBindSetOrSpace`` gives the ``set`` of the binding, and ``fixedBindNumber`` gives the binding number within that descriptor set.
Constant blocks
---------------
Constant blocks are generally small fixed-size structures of data that are bound to read-only values in the API, described by :class:`~renderdoc.ConstantBlock`. This will describe the layout of the data as :class:`~renderdoc.ShaderConstant` descriptions of the variables within.
The most common case for a constant block will be a simple buffer or region of memory that is bound, but it is also possible for constant blocks to be set directly as values with no explicit memory location (:data:`~renderdoc.ConstantBlock.inlineDataBytes`), or even as compile-time constants (:data:`~renderdoc.ConstantBlock.compileConstants`). There are several flags within each :class:`~renderdoc.ConstantBlock` that describe these different types of block.
D3D11
In D3D11 the only types of constant blocks are constant buffers, bound per-stage to the pipeline. :data:`~renderdoc.ConstantBlock.bufferBacked` will be set to ``True``.
D3D12
In D3D12 the only types of constant blocks are constant buffers. These may be bound either via root constants, root descriptors, through a root table. :data:`~renderdoc.ConstantBlock.bufferBacked` will be ``True`` on all of the constant blocks. The shader itself does not specify if a constant buffer will be set from a real buffer or root constants, so the reflection does not give this information.
Constant buffers accessed via ``ResourceDescriptorHeap`` will not be listed in the shader reflection - as of the time of writing DXC does not emit any reflection data for such resources and so they can't be described.
OpenGL
In OpenGL a constant block could either be a uniform buffer, or it could be a special virtual block which contains all 'bare' uniforms. Uniform buffers are bound to the pipeline through one of the available uniform binding points and 'bare' uniforms are set via ``glUniform*`` entry points on the program object itself.
The special virtual block for 'bare' uniforms will have :data:`~renderdoc.ConstantBlock.bufferBacked` set to ``False`` to distinguish it, all other bindings will have it as ``True``. :data:`~renderdoc.ConstantBlock.inlineDataBytes` will be ``False`` because the storage is opaque and not actually backed by addressable bytes.
Vulkan
Vulkan has several different types of constant block.
A uniform buffer is backed by normal memory and so will present as a constant block with no flags other than :data:`~renderdoc.ConstantBlock.bufferBacked` set to ``True``.
The push constants region will have :data:`~renderdoc.ConstantBlock.bufferBacked` set to ``False`` and :data:`~renderdoc.ConstantBlock.inlineDataBytes` will be ``True``.
Specialization constants will be represented by a constant block with :data:`~renderdoc.ConstantBlock.bufferBacked` set to ``False`` and :data:`~renderdoc.ConstantBlock.compileConstants` set to ``True``. :data:`~renderdoc.ConstantBlock.inlineDataBytes` will also be ``True``.
Samplers
--------
There is no significant variation between APIs for samplers so these map quite directly to the concept of a sampler object in a shader. The exception is that OpenGL does not have the concept of true separate samplers in shaders - only as an API convenience for setting sampler state. The samplers array on OpenGL will always be empty.
On D3D12 samplers accessed via ``SamplerDescriptorHeap`` will not be listed in the shader reflection - as of the time of writing DXC does not emit any reflection data for such resources and so they can't be described.
.. note::
Although APIs have concepts of samplers that are bound vs. defined as immutable or static, this is done outside the shader and so is not listed here.
Read-only resources
-------------------
Because the :class:`~renderdoc.ShaderResource` structure is shared for both read-only and read-write resources, :data:`~renderdoc.ShaderResource.isReadOnly` will be set accordingly to be able to differentiate.
:data:`~renderdoc.ShaderResource.descriptorType` can be used to differentiate different types of descriptors within a single binding, which should generally map 1:1 to different API binding types.
For texture-type bindings, :data:`~renderdoc.ShaderResource.textureType` gives the type of texture being accessed - e.g. :data:`~renderdoc.TextureType.Texture2D` or :data:`~renderdoc.TextureType.Texture3D` etc. The type given in :data:`~renderdoc.ShaderResource.variableType` will give information about the component type and number expected of the texture.
For buffer-type bindings, :data:`~renderdoc.ShaderResource.variableType` gives the information about what the inner variable type is of the buffer.
D3D11 & D3D12
On D3D read-only resources map directly to shader resource views (SRVs). All types of SRV bindings are represented in read-only resources, and for D3D12, acceleration structures are *not* considered texture resources.
On D3D a ``StructuredBuffer`` maps to :data:`~renderdoc.DescriptorType.Buffer` and a ``Buffer`` maps to :data:`~renderdoc.DescriptorType.TypedBuffer` as the latter allows for format conversion and lists at most one vector type as its element type.
For buffer resources the :data:`~renderdoc.ShaderResource.variableType` is the structure in an array of structures layout buffer.
OpenGL
On OpenGL read-only resources are textures, including buffer textures which will be listed as :data:`~renderdoc.DescriptorType.TypedBuffer`.
For buffer resources the :data:`~renderdoc.ShaderResource.variableType` may have a trailing child of unbounded size, indicating that the rest of the buffer is an array of that type.
Vulkan
On Vulkan read-only resources are sampled images, combined image/samplers, input attachments, texel buffers, and acceleration structures. Acceleration structures are *not* considered texture resources.
For input attachments, :data:`~renderdoc.ShaderResource.isInputAttachment` will be ``True``.
For combined image/samplers :data:`~renderdoc.ShaderResource.hasSampler` will be ``True``.
For buffer resources the :data:`~renderdoc.ShaderResource.variableType` may have a trailing child of unbounded size, indicating that the rest of the buffer is an array of that type.
Read-write resources
--------------------
Because the :class:`~renderdoc.ShaderResource` structure is shared for both read-only and read-write resources, :data:`~renderdoc.ShaderResource.isReadOnly` will be set accordingly to be able to differentiate.
Most members have the same meaning as above in read-only resources, and so are not documented again here.
D3D11 & D3D12
On D3D read-write resources map to unordered resource views (UAVs).
OpenGL
On OpenGL read-write resources are SSBOs, load/store images, and atomic counter buffers.
Atomic counter buffers are treated as a read-write buffer with a single unsigned integer member. The variable name will be ``atomic_uint``.
Vulkan
On Vulkan read-write resources are storage images and storage buffers.
Debug information
-----------------
Debug information for the shader is stored in :data:`~renderdoc.ShaderReflection.debugInfo`, a structure of type :data:`~renderdoc.ShaderDebugInfo`.
All APIs can provide extra shader debug information when compiling shaders, though depending on the API and compilation pipeline this may have to be explicitly enabled or it may be stripped out by default. On some APIs debug information can be separated out into an offline file so that the bytes passed to the graphics API don't contain the debug information directly but do contain an identifier of how to find it.
How to configure this compilation and set up RenderDoc to locate this debug information is documented in :ref:`how_shader_debug_info` but it is useful to note that :data:`~renderdoc.ShaderDebugInfo.debugInfoLoadingLog` contains a log of debug info loading which can be useful for diagnosing issues.
If not all the debug information is present, RenderDoc will fill out as much of it as is possible from what is available, other fields may be left blank or have less information than usual - similar to the more direct reflection information above. :data:`~renderdoc.ShaderDebugInfo.sourceDebugInformation` is a flag which indicates that RenderDoc has received enough information that source-level debugging is available, which generally means all information has been found.
Source code
-----------
The source files for the shader are stored in :data:`~renderdoc.ShaderDebugInfo.files`. If the shader debug information contains a preprocessor-output file with only a single source file using ``#line`` directives to refer to other files, the files list will contain both the original preprocessor-output file as well as virtually split apart files with the partial lines as referenced by those ``#line`` directives. This allows shader debugging to refer to the original lines in original files even if not all of those files are present.
The files have both a filename and string contents, but the filename may vary depending on the particular compiler and how it generates debug information - it is not known whether it will be an absolute path, truncated path, relative path (with ``../../`` elements) or just a filename. It is also not known if the filenames will be case sensitive or not. These conventions all come from the shader compiler that produced the debug information in the first place.
The entry point is the function that begins the execution for a particular shader - a shader reflection object corresponds to one shader, and so it may share source code with other shaders with other entry points. The location of the entry point will be given in :data:`~renderdoc.ShaderDebugInfo.entryLocation` if available in the debug information but depending on the debug information not all members may be present.
On some APIs, the entry point may be renamed between what it is in the source code and what is exposed to the API - if this happens :data:`~renderdoc.ShaderDebugInfo.entrySourceName` will contain the name of the entry point in the source code itself, and all other uses of the entry point name will refer to the API-facing name.
Compiler & Binary information
-----------------------------
Shaders can be compiled to different encodings, and in D3D12 for example multiple shader encodings are accepted - both DXBC shaders produced by ``fxc`` and DXIL shaders produced by ``dxc``. These are considered separate encodings by RenderDoc even if they share a container format as they are mostly distinct.
The shader encoding of the shader binary itself is given by :data:`~renderdoc.ShaderReflection.encoding`, and the encoding of the shader source (if different and known) is given by :data:`~renderdoc.ShaderDebugInfo.encoding`.
The compiler used, if corresponding to a known shader tool, will be given by :data:`~renderdoc.ShaderDebugInfo.compiler`. If an unknown compiler is used, this field will not be set to unknown. The compilation flags used will be given in :data:`~renderdoc.ShaderDebugInfo.compileFlags` - this is a series of key-value pairs with some special known keys:
* ``@cmdline`` will be set to a string containing the command line parameters for the compiler.
* ``@spirver`` will be available for SPIR-V shaders containing a target SPIR-V version for recompiling, e.g. ``spirv1.3``.
Other flags may be available depending on the shader compiler, API, and shader encoding.
Debugging
---------
If you are :doc:`debugging shaders <../examples/history_debug>` you will need to check if a shader supports debugging. This can be determined using :data:`~renderdoc.ShaderDebugInfo.debuggable` which is a single flag indicating if this shader can be debugged or not.
If the shader can't be debugged it is likely due to an unsupported feature or capability in the shader, and information can this be found in a string :data:`~renderdoc.ShaderDebugInfo.debugStatus`.
@@ -0,0 +1,31 @@
Structured Data
===============
RenderDoc includes a system for representing arbitrary structured data - including specific byte-sized types . This system is used internally for representing a readable form of the serialised data as well as for other systems like configuration (:func:`~renderdoc.GetConfigSetting`) and :doc:`annotations <../../window/annotation_viewer>` (:data:`~renderdoc.APIEvent.annotations`).
Structured Objects
------------------
Data is represented as a tree structure. Each object is represented by :class:`~renderdoc.SDObject` and can be either a leaf node (containing a single value) or a node with children (a structure or array). For example a structure will be represented by a :class:`~renderdoc.SDObject` with one child per structure member. An array will contain one child per array element.
The type of an object is defined by :data:`~renderdoc.SDObject.type` of type :class:`~renderdoc.SDType`. This gives the basic type, size in bytes, and name of the object type. it also contains a number of flags (:class:`~renderdoc.SDTypeFlags`) which can determine e.g. if this object was stored as a pointer and could be ``NULL``, if the object was an enumeration and so has both an integer and a string value. It also has hints for display such as if this object is considered 'important', or if this object is considered internal/hidden.
The value of an object is stored in an :class:`~renderdoc.SDObjectData`. In this value storage the size of the element is irrelevant, it is always stored with the maximum possible precision. In python without an unsigned/signed integer distinction care should be taken to access through the correct member especially when modifying the value of a structured object. For :data:`~renderdoc.SDBasic.Buffer` values they are not stored directly but instead as an index into a separate list of buffers. See below with serialised capture data for more information.
The :class:`~renderdoc.SDObject` object contains a number of helper accessors and functions for fetching its contents as different types as well as for modifying it if this is a mutable object.
Serialised Capture data
-----------------------
.. warning::
The structured data for a serialised capture is entirely undocumented and may change! You may find this data useful and generally it will closely match the expectation from API function calls, but that will not always be the case and you should not treat this data as guaranteed.
When a capture is loaded, the serialised data is all stored in a structured data representation, rooted at a :class:`~renderdoc.SDFile`. For normal opening of a capture, the contents of buffers are *not* stored as this would represent too much wasted memory that is rarely accessed. The structured data still contains everything except the contents of large buffer values.
To obtain the structured data for a capture including buffers it is necessary to use :doc:`capture_access` which will serialise and load a capture including buffer data. This can be done without replaying even when the capture is otherwise open in the UI.
The :class:`~renderdoc.SDFile` contains a number of :data:`~renderdoc.SDFile.chunks`, each of which corresponds to one self-contained serialised function call. Note that although most of the serialised function calls will be directly taken from the calls the application made, as in the warning above some of the serialised function calls will be internal to RenderDoc. In both cases the serialised form backwards compatibility is handled internally by RenderDoc's serialisation and may still change.
You can look up the chunk for a given API event using :data:`~renderdoc.APIEvent.chunkIndex` - as long as this is not set to :data:`~renderdoc.APIEvent.NoChunk` then it gives the index in the corresponding :data:`~renderdoc.SDFile.chunks` list.
Each chunk can be thought of as a nameless struct with children - for an API event, the children will usually correspond to input parameters, but again note that this rule is not guaranteed and some children may be return values or internal data. You should make use of the flags (:class:`~renderdoc.SDTypeFlags`) on object types to determine whether or not an object should be displayed or is considered hidden. For the purposes of displaying summary views of events you can also use the 'important' flags to indicate which parameters are most likely to be relevant to users and which should be given less priority for display.
+30
View File
@@ -0,0 +1,30 @@
.. _pythreading:
Threading in RenderDoc's UI
===========================
RenderDoc runs with two main threads: The UI thread created by the operating system and where UI interactions are processed, and a replay thread where most replay work happens.
UI extension python code runs on the UI thread itself for direct access to widgets and other UI panels.
Most replay work does not take a long time but it can still be noticeable enough that it would cause UI stalls if it were not run asynchronously on a thread. This also allows for occasional long-running tasks that may take multiple seconds to happen without the UI becoming completely unresponsive.
Replay thread
-------------
From python the replay thread is handled in :class:`~qrenderdoc.ReplayManager`. While a capture is open, the replay manager provides access to the replay thread via callbacks that can be invoked either with :meth:`~qrenderdoc.ReplayManager.AsyncInvoke` or :meth:`~qrenderdoc.ReplayManager.BlockInvoke`. Both functions are identical and queue processing of a callback which receives the :class:`~renderdoc.ReplayController` for use. The asynchronous version will not wait for the callback to happen whereas the blocking version will stall the caller until the callback has been called and returned. For this reason blocking invokes should be used very sparingly from the UI thread.
It is recommended that most work that can be done on the replay thread is moved there via callbacks, as long-running work on the UI thread can cause unpleasant stalls or hangs.
For convenience when working with simple scripts, you can obtain a blocking version of :class:`~renderdoc.ReplayController` via :meth:`~qrenderdoc.CaptureContext.GetBlockingController` which will automatically blocking invoke onto the correct thread for each call through its API.
Python script thread
--------------------
When running scripts in the RenderDoc UI directly in the :doc:`../../window/python_scripting` window the script executes in a special thread to prevent long-running scripts from freezing the UI, which automatically blocks the UI thread when accessing any UI elements.
The python thread allows the use of :meth:`~qrenderdoc.CaptureContext.GetBlockingController` as noted above without causing UI stalls and for simple scripts is convenient.
.. warning::
If using Qt directly via PySide you should ensure that you run code directly on the UI thread (:meth:`~qrenderdoc.CaptureContext.InvokeOntoUIThread`) as Qt is not always thread-safe.