mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-06 01:50:38 +00:00
Tidy up python documentation and add examples
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
import renderdoc as rd
|
||||
|
||||
# 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]
|
||||
formatChars[rd.CompType.Double] = formatChars[rd.CompType.Float]
|
||||
|
||||
# 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((1 << fmt.compByteWidth) - 1)
|
||||
value = tuple(float(value[i]) / divisor for i in value)
|
||||
elif fmt.compType == rd.CompType.SNorm:
|
||||
maxNeg = -(1 << (fmt.compByteWidth - 1))
|
||||
divisor = float(-(maxNeg-1))
|
||||
value = tuple((float(value[i]) if (value[i] == maxNeg) else (float(value[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):
|
||||
# This work is API-specific, but the APIs have a lot of similarities.
|
||||
# Here we implement support for D3D11 since it's relatively simple
|
||||
state = controller.GetD3D11PipelineState()
|
||||
|
||||
# Get the index & vertex buffers, and fixed vertex inputs
|
||||
ib = state.inputAssembly.indexBuffer
|
||||
vbs = state.inputAssembly.vertexBuffers
|
||||
attrs = state.inputAssembly.layouts
|
||||
|
||||
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 = draw.indexByteWidth
|
||||
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.DrawFlags.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.inputSlot].byteOffset
|
||||
meshInput.format = attr.format
|
||||
meshInput.vertexResourceId = vbs[attr.inputSlot].resourceId
|
||||
meshInput.vertexByteStride = vbs[attr.inputSlot].byteStride
|
||||
|
||||
# We don't go into the details of semantic matching here, just use both as the name
|
||||
meshInput.name = '%s%d' % (attr.semanticName, attr.semanticIndex)
|
||||
|
||||
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
|
||||
|
||||
state = controller.GetD3D11PipelineState()
|
||||
vs = state.vertexShader.reflection
|
||||
|
||||
# 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 = 8 if attr.compType == rd.CompType.Double else 4
|
||||
meshOutput.format.compCount = attr.compCount
|
||||
meshOutput.format.compType = attr.compType
|
||||
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.compType == rd.CompType.Double 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(vertexOffset, vertexOffset+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.GetDrawcalls():
|
||||
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.name))
|
||||
|
||||
# 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, 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
|
||||
status = cap.OpenFile(filename, '', None)
|
||||
|
||||
# Make sure the file opened successfully
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't open file: " + str(status))
|
||||
|
||||
# Make sure we can replay
|
||||
if not cap.LocalReplaySupport():
|
||||
raise RuntimeError("Capture cannot be replayed")
|
||||
|
||||
# Initialise the replay
|
||||
status,controller = cap.OpenCapture(None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't initialise replay: " + str(status))
|
||||
|
||||
return (cap, controller)
|
||||
|
||||
if 'pyrenderdoc' in globals():
|
||||
pyrenderdoc.Replay().BlockInvoke(sampleCode)
|
||||
else:
|
||||
cap,controller = loadCapture('test.rdc')
|
||||
|
||||
sampleCode(controller)
|
||||
|
||||
controller.Shutdown()
|
||||
cap.Shutdown()
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
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 D3D11 pipeline state, but the same can be done for other APIs:
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
# This work is API-specific, but the APIs have a lot of similarities.
|
||||
# Here we implement support for D3D11 since it's relatively simple
|
||||
state = controller.GetD3D11PipelineState()
|
||||
|
||||
# Get the index & vertex buffers, and fixed vertex inputs
|
||||
ib = state.inputAssembly.indexBuffer
|
||||
vbs = state.inputAssembly.vertexBuffers
|
||||
attrs = state.inputAssembly.layouts
|
||||
|
||||
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 = draw.indexByteWidth
|
||||
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.DrawFlags.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.inputSlot].byteOffset
|
||||
meshInput.format = attr.format
|
||||
meshInput.vertexResourceId = vbs[attr.inputSlot].resourceId
|
||||
meshInput.vertexByteStride = vbs[attr.inputSlot].byteStride
|
||||
|
||||
# We don't go into the details of semantic matching here, just use both as the name
|
||||
meshInput.name = '%s%d' % (attr.semanticName, attr.semanticIndex)
|
||||
|
||||
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(vertexOffset, vertexOffset+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]
|
||||
formatChars[rd.CompType.Double] = formatChars[rd.CompType.Float]
|
||||
|
||||
# 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((1 << fmt.compByteWidth) - 1)
|
||||
value = tuple(float(value[i]) / divisor for i in value)
|
||||
elif fmt.compType == rd.CompType.SNorm:
|
||||
maxNeg = -(1 << (fmt.compByteWidth - 1))
|
||||
divisor = float(-(maxNeg-1))
|
||||
value = tuple((float(value[i]) if (value[i] == maxNeg) else (float(value[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
|
||||
|
||||
state = controller.GetD3D11PipelineState()
|
||||
vs = state.vertexShader.reflection
|
||||
|
||||
# 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 = 8 if attr.compType == rd.CompType.Double else 4
|
||||
meshOutput.format.compCount = attr.compCount
|
||||
meshOutput.format.compType = attr.compType
|
||||
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.compType == rd.CompType.Double else 4) * fmt.compCount
|
||||
|
||||
Example Source
|
||||
--------------
|
||||
|
||||
.. only:: html and not htmlhelp
|
||||
|
||||
:download:`Download the example script <decode_mesh.py>`.
|
||||
|
||||
.. literalinclude:: decode_mesh.py
|
||||
|
||||
Sample output:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
Decoding mesh inputs at 69: DrawIndexed(5580)
|
||||
|
||||
|
||||
Mesh configuration:
|
||||
POSITION0:
|
||||
- vertex: <ResourceId 142> / 44 stride
|
||||
- format: CompType.Float x 3 @ 0
|
||||
TANGENT0:
|
||||
- vertex: <ResourceId 142> / 44 stride
|
||||
- format: CompType.Float x 3 @ 12
|
||||
NORMAL0:
|
||||
- vertex: <ResourceId 142> / 44 stride
|
||||
- format: CompType.Float x 3 @ 24
|
||||
TEXCOORD0:
|
||||
- vertex: <ResourceId 142> / 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: <ResourceId 1000000000000000237> / 68 stride
|
||||
- format: CompType.Float x 4 @ 0
|
||||
POSITION:
|
||||
- vertex: <ResourceId 1000000000000000237> / 68 stride
|
||||
- format: CompType.Float x 4 @ 16
|
||||
TEXCOORD:
|
||||
- vertex: <ResourceId 1000000000000000237> / 68 stride
|
||||
- format: CompType.Float x 2 @ 32
|
||||
TANGENT:
|
||||
- vertex: <ResourceId 1000000000000000237> / 68 stride
|
||||
- format: CompType.Float x 3 @ 40
|
||||
NORMAL:
|
||||
- vertex: <ResourceId 1000000000000000237> / 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)
|
||||
@@ -0,0 +1,118 @@
|
||||
import renderdoc as rd
|
||||
|
||||
def loadCapture(filename):
|
||||
# Open a capture file handle
|
||||
cap = rd.OpenCaptureFile()
|
||||
|
||||
# Open a particular file - see also OpenBuffer to load from memory
|
||||
status = cap.OpenFile(filename, '', None)
|
||||
|
||||
# Make sure the file opened successfully
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't open file: " + str(status))
|
||||
|
||||
# Make sure we can replay
|
||||
if not cap.LocalReplaySupport():
|
||||
raise RuntimeError("Capture cannot be replayed")
|
||||
|
||||
# Initialise the replay
|
||||
status,controller = cap.OpenCapture(None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't initialise replay: " + str(status))
|
||||
|
||||
return controller
|
||||
|
||||
if 'pyrenderdoc' in globals():
|
||||
raise RuntimeError("This sample should not be run within the RenderDoc UI")
|
||||
else:
|
||||
controller = loadCapture('test.rdc')
|
||||
|
||||
# Use tkinter to create windows
|
||||
import tkinter
|
||||
|
||||
# Create a simple window
|
||||
window = tkinter.Tk()
|
||||
window.geometry("1280x720")
|
||||
|
||||
# Create renderdoc windowing data.
|
||||
winsystems = [rd.WindowingSystem(i) for i in controller.GetSupportedWindowSystems()]
|
||||
|
||||
# Pass window system specific data here, See:
|
||||
# - renderdoc.CreateWin32WindowingData
|
||||
# - renderdoc.CreateXlibWindowingData
|
||||
# - renderdoc.CreateXCBWindowingData
|
||||
|
||||
# This example code works on windows as that's simple to integrate with tkinter
|
||||
if not rd.WindowingSystem.Win32 in winsystems:
|
||||
raise RuntimeError("Example requires Win32 windowing system: " + str(winsystems))
|
||||
|
||||
windata = rd.CreateWin32WindowingData(int(window.frame(), 16))
|
||||
|
||||
# Create a texture output on the window
|
||||
out = controller.CreateOutput(windata, rd.ReplayOutputType.Texture)
|
||||
|
||||
# Fetch the list of textures
|
||||
textures = controller.GetTextures()
|
||||
|
||||
# Fetch the list of drawcalls
|
||||
draws = controller.GetDrawcalls()
|
||||
|
||||
# Function to look up the texture descriptor for a given resourceId
|
||||
def getTexture(texid):
|
||||
global textures
|
||||
for tex in textures:
|
||||
if tex.resourceId == texid:
|
||||
return tex
|
||||
return None
|
||||
|
||||
# Our paint function will be called ever 33ms, to display the output
|
||||
def paint():
|
||||
global out, window
|
||||
out.Display()
|
||||
window.after(33, paint)
|
||||
|
||||
# Start on the first drawcall
|
||||
curdraw = 0
|
||||
|
||||
# The advance function will be called every 150ms, to move to the next draw
|
||||
def advance():
|
||||
global out, window, curdraw
|
||||
|
||||
# Move to the current drawcall
|
||||
controller.SetFrameEvent(draws[curdraw].eventId, False)
|
||||
|
||||
# Initialise a default TextureDisplay object
|
||||
disp = rd.TextureDisplay()
|
||||
|
||||
# Set the first colour output as the texture to display
|
||||
disp.resourceId = draws[curdraw].outputs[0]
|
||||
|
||||
# Get the details of this texture
|
||||
texDetails = getTexture(disp.resourceId)
|
||||
|
||||
# Calculate the scale required in width and height
|
||||
widthScale = window.winfo_width() / texDetails.width
|
||||
heightScale = window.winfo_height() / texDetails.height
|
||||
|
||||
# Use the lower scale to fit the texture on the window
|
||||
disp.scale = min(widthScale, heightScale)
|
||||
|
||||
# Update the texture display
|
||||
out.SetTextureDisplay(disp)
|
||||
|
||||
# Set the next drawcall
|
||||
curdraw = (curdraw + 1) % len(draws)
|
||||
|
||||
window.after(150, advance)
|
||||
|
||||
# Start the callbacks
|
||||
advance()
|
||||
paint()
|
||||
|
||||
# Start the main window loop
|
||||
window.mainloop()
|
||||
|
||||
controller.Shutdown()
|
||||
|
||||
cap.Shutdown()
|
||||
@@ -0,0 +1,138 @@
|
||||
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 drawcalls we need some global state first from :py:meth:`~renderdoc.ReplayController.GetTextures` and :py:meth:`~renderdoc.ReplayController.GetDrawcalls`, and we'll also define a helper function to fetch a particular texture by resourceId, so that we can easily look up the details for a texture.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
# Fetch the list of textures
|
||||
textures = controller.GetTextures()
|
||||
|
||||
# Fetch the list of drawcalls
|
||||
draws = controller.GetDrawcalls()
|
||||
|
||||
# Function to look up the texture descriptor for a given resourceId
|
||||
def getTexture(texid):
|
||||
global textures
|
||||
for tex in textures:
|
||||
if tex.resourceId == texid:
|
||||
return tex
|
||||
return None
|
||||
|
||||
We now define two callback functions - ``paint`` and ``advance``. ``paint`` will be called every 33ms, it will display the output with the latest state using :py:meth:`~renderdoc.ReplayOutput.Display`. ``advance`` changes the current state to reflect a new drawcall.
|
||||
|
||||
.. 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 drawcall'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 drawcall.
|
||||
|
||||
When we update to a new texture, we fetch its details using our earlier ``getTexture`` and calculate a scale that keeps the texture fully visible on screen.
|
||||
|
||||
Finally we move to the next drawcall in the list for the next time ``advance`` is called.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
# Start on the first drawcall
|
||||
curdraw = 0
|
||||
|
||||
# The advance function will be called every 150ms, to move to the next draw
|
||||
def advance():
|
||||
global out, window, curdraw
|
||||
|
||||
# Move to the current drawcall
|
||||
controller.SetFrameEvent(draws[curdraw].eventId, False)
|
||||
|
||||
# Initialise a default TextureDisplay object
|
||||
disp = rd.TextureDisplay()
|
||||
|
||||
# Set the first colour output as the texture to display
|
||||
disp.resourceId = draws[curdraw].outputs[0]
|
||||
|
||||
# Get the details of this texture
|
||||
texDetails = getTexture(disp.resourceId)
|
||||
|
||||
# Calculate the scale required in width and height
|
||||
widthScale = window.winfo_width() / texDetails.width
|
||||
heightScale = window.winfo_height() / texDetails.height
|
||||
|
||||
# Use the lower scale to fit the texture on the window
|
||||
disp.scale = min(widthScale, heightScale)
|
||||
|
||||
# Update the texture display
|
||||
out.SetTextureDisplay(disp)
|
||||
|
||||
# Set the next drawcall
|
||||
curdraw = (curdraw + 1) % len(draws)
|
||||
|
||||
window.after(150, advance)
|
||||
|
||||
Once we have the callbacks defined, we call them once to initialise the display and set up the repeated callbacks, and start the tkinter main window loop.
|
||||
|
||||
.. 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 <display_window.py>`.
|
||||
|
||||
.. literalinclude:: display_window.py
|
||||
@@ -0,0 +1,79 @@
|
||||
import renderdoc as rd
|
||||
|
||||
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.fv[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()
|
||||
|
||||
for disasm in targets:
|
||||
print(" - " + disasm)
|
||||
|
||||
target = targets[0]
|
||||
|
||||
# For some APIs, it might be relevant to set the PSO id or entry point name
|
||||
pipe = rd.ResourceId.Null()
|
||||
entry = "main"
|
||||
|
||||
# Get the pixel shader's reflection object
|
||||
ps = controller.GetD3D11PipelineState().pixelShader
|
||||
|
||||
print("Pixel shader:")
|
||||
print(controller.DisassembleShader(pipe, ps.reflection, target))
|
||||
|
||||
cbufferVars = controller.GetCBufferVariableContents(ps.resourceId, entry, 0, ps.constantBuffers[0].resourceId, 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
|
||||
status = cap.OpenFile(filename, '', None)
|
||||
|
||||
# Make sure the file opened successfully
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't open file: " + str(status))
|
||||
|
||||
# Make sure we can replay
|
||||
if not cap.LocalReplaySupport():
|
||||
raise RuntimeError("Capture cannot be replayed")
|
||||
|
||||
# Initialise the replay
|
||||
status,controller = cap.OpenCapture(None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't initialise replay: " + str(status))
|
||||
|
||||
return (cap, controller)
|
||||
|
||||
if 'pyrenderdoc' in globals():
|
||||
pyrenderdoc.Replay().BlockInvoke(sampleCode)
|
||||
else:
|
||||
cap,controller = loadCapture('test.rdc')
|
||||
|
||||
sampleCode(controller)
|
||||
|
||||
controller.Shutdown()
|
||||
cap.Shutdown()
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
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:
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
print("Available disassembly formats:")
|
||||
|
||||
targets = controller.GetDisassemblyTargets()
|
||||
|
||||
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 D3D11 which does not require either, so we set some default values and fetch the shader from the pipeline state. Finally we fetch the disassembled shader string with :py:meth:`~renderdoc.ReplayController.DisassembleShader` and print it:
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
# For some APIs, it might be relevant to set the PSO id or entry point name
|
||||
pipe = rd.ResourceId.Null()
|
||||
entry = "main"
|
||||
|
||||
# Get the pixel shader's reflection object
|
||||
ps = controller.GetD3D11PipelineState().pixelShader
|
||||
|
||||
print("Pixel shader:")
|
||||
print(controller.DisassembleShader(pipe, ps.reflection, 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 :py:class:`~renderdoc.BindPointMap`. 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(ps.resourceId, entry, 0, ps.constantBuffers[0].resourceId, 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.fv[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 <fetch_shader.py>`.
|
||||
|
||||
.. 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
|
||||
@@ -0,0 +1,33 @@
|
||||
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_draws
|
||||
fetch_shader
|
||||
save_texture
|
||||
decode_mesh
|
||||
display_window
|
||||
@@ -0,0 +1,96 @@
|
||||
import renderdoc as rd
|
||||
|
||||
# Set up a hash for storing draw information by event
|
||||
draws = {}
|
||||
|
||||
# Define a recursive function for iterating over draws
|
||||
def iterDraw(d, indent = ''):
|
||||
global draws
|
||||
|
||||
# Print this drawcall
|
||||
print('%s%d: %s' % (indent, d.eventId, d.name))
|
||||
|
||||
# Save the draw by eventId for use later
|
||||
draws[d.eventId] = d
|
||||
|
||||
# Iterate over the draw's children
|
||||
for d in d.children:
|
||||
iterDraw(d, indent + ' ')
|
||||
|
||||
def sampleCode(controller):
|
||||
global draws
|
||||
|
||||
# Iterate over all of the root drawcalls
|
||||
for d in controller.GetDrawcalls():
|
||||
iterDraw(d)
|
||||
|
||||
# Start iterating from the first real draw as a child of markers
|
||||
draw = controller.GetDrawcalls()[0]
|
||||
|
||||
while len(draw.children) > 0:
|
||||
draw = draw.children[0]
|
||||
|
||||
# Counter for which pass we're in
|
||||
passnum = 0
|
||||
# Counter for how many draws are in the pass
|
||||
passcontents = 0
|
||||
# Whether we've started seeing draws in the pass - i.e. we're past any
|
||||
# starting clear calls that may be batched together
|
||||
inpass = False
|
||||
|
||||
print("Pass #0 starts with %d: %s" % (draw.eventId, draw.name))
|
||||
|
||||
while draw != None:
|
||||
# When we encounter a clear
|
||||
if draw.flags & rd.DrawFlags.Clear:
|
||||
if inpass:
|
||||
print("Pass #%d contained %d draws" % (passnum, passcontents))
|
||||
passnum += 1
|
||||
print("Pass #%d starts with %d: %s" % (passnum, draw.eventId, draw.name))
|
||||
passcontents = 0
|
||||
inpass = False
|
||||
else:
|
||||
passcontents += 1
|
||||
inpass = True
|
||||
|
||||
# Advance to the next drawcall
|
||||
if not draw.next in draws:
|
||||
break
|
||||
draw = draws[draw.next]
|
||||
|
||||
if inpass:
|
||||
print("Pass #%d contained %d draws" % (passnum, passcontents))
|
||||
|
||||
def loadCapture(filename):
|
||||
# Open a capture file handle
|
||||
cap = rd.OpenCaptureFile()
|
||||
|
||||
# Open a particular file - see also OpenBuffer to load from memory
|
||||
status = cap.OpenFile(filename, '', None)
|
||||
|
||||
# Make sure the file opened successfully
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't open file: " + str(status))
|
||||
|
||||
# Make sure we can replay
|
||||
if not cap.LocalReplaySupport():
|
||||
raise RuntimeError("Capture cannot be replayed")
|
||||
|
||||
# Initialise the replay
|
||||
status,controller = cap.OpenCapture(None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't initialise replay: " + str(status))
|
||||
|
||||
return controller
|
||||
|
||||
if 'pyrenderdoc' in globals():
|
||||
pyrenderdoc.Replay().BlockInvoke(sampleCode)
|
||||
else:
|
||||
cap,controller = loadCapture('test.rdc')
|
||||
|
||||
sampleCode(controller)
|
||||
|
||||
controller.Shutdown()
|
||||
cap.Shutdown()
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
Iterate drawcall tree
|
||||
=====================
|
||||
|
||||
In this example we will show how to iterate over drawcalls.
|
||||
|
||||
The drawcalls returned from :py:meth:`~renderdoc.ReplayController.GetDrawcalls` are draws or marker regions at the root level - with no parent marker region. There are multiple ways to iterate through the list of draws.
|
||||
|
||||
The first way illustrated in this sample is to walk the tree using :py:attr:`~renderdoc.DrawcallDescription.children`, which contains the list of child draws at any point in the tree. There is also :py:attr:`~renderdoc.DrawcallDescription.parent` which contains the index of the parent drawcall.
|
||||
|
||||
The second is to use :py:attr:`~renderdoc.DrawcallDescription.previous` and :py:attr:`~renderdoc.DrawcallDescription.next`, which contain the indices of the previous and next draw respectively in a linear fashion, regardless of nesting depth.
|
||||
|
||||
In the example we use this iteration to determine the number of passes, using the drawcall flags to denote the start of each pass by a starting clear call.
|
||||
|
||||
Example Source
|
||||
--------------
|
||||
|
||||
.. only:: html and not htmlhelp
|
||||
|
||||
:download:`Download the example script <iter_draws.py>`.
|
||||
|
||||
.. literalinclude:: iter_draws.py
|
||||
|
||||
Sample output:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
1: Scene
|
||||
2: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
|
||||
3: ClearDepthStencilView(D=1.000000, S=00)
|
||||
9: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
|
||||
10: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
|
||||
11: ClearDepthStencilView(D=1.000000, S=00)
|
||||
13: GBuffer
|
||||
28: DrawIndexed(4800)
|
||||
43: DrawIndexed(36)
|
||||
56: DrawIndexed(5220)
|
||||
69: DrawIndexed(5580)
|
||||
82: DrawIndexed(5580)
|
||||
95: DrawIndexed(5580)
|
||||
108: DrawIndexed(5580)
|
||||
121: DrawIndexed(5580)
|
||||
134: DrawIndexed(5580)
|
||||
147: DrawIndexed(5580)
|
||||
160: DrawIndexed(5580)
|
||||
173: DrawIndexed(5580)
|
||||
186: DrawIndexed(5580)
|
||||
199: DrawIndexed(5220)
|
||||
212: DrawIndexed(5220)
|
||||
225: DrawIndexed(5220)
|
||||
238: DrawIndexed(5220)
|
||||
251: DrawIndexed(5220)
|
||||
264: DrawIndexed(5220)
|
||||
277: DrawIndexed(5220)
|
||||
290: DrawIndexed(5220)
|
||||
303: DrawIndexed(5220)
|
||||
316: DrawIndexed(5220)
|
||||
319: ClearDepthStencilView(D=1.000000, S=00)
|
||||
321: Shadowmap
|
||||
333: DrawIndexed(4800)
|
||||
345: DrawIndexed(36)
|
||||
355: DrawIndexed(5220)
|
||||
365: DrawIndexed(5580)
|
||||
375: DrawIndexed(5580)
|
||||
385: DrawIndexed(5580)
|
||||
395: DrawIndexed(5580)
|
||||
405: DrawIndexed(5580)
|
||||
415: DrawIndexed(5580)
|
||||
425: DrawIndexed(5580)
|
||||
435: DrawIndexed(5580)
|
||||
445: DrawIndexed(5580)
|
||||
455: DrawIndexed(5580)
|
||||
465: DrawIndexed(5220)
|
||||
475: DrawIndexed(5220)
|
||||
485: DrawIndexed(5220)
|
||||
495: DrawIndexed(5220)
|
||||
505: DrawIndexed(5220)
|
||||
515: DrawIndexed(5220)
|
||||
525: DrawIndexed(5220)
|
||||
535: DrawIndexed(5220)
|
||||
545: DrawIndexed(5220)
|
||||
555: DrawIndexed(5220)
|
||||
558: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
|
||||
559: ClearDepthStencilView(D=1.000000, S=00)
|
||||
561: Lighting
|
||||
563: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
|
||||
564: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 0.000000)
|
||||
580: DrawIndexed(36)
|
||||
597: DrawIndexed(36)
|
||||
614: DrawIndexed(36)
|
||||
617: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
|
||||
618: ClearDepthStencilView(D=1.000000, S=00)
|
||||
620: Shading
|
||||
630: Draw(6)
|
||||
645: DrawIndexed(960)
|
||||
652: API Calls
|
||||
655: End of Frame
|
||||
Pass #0 starts with 2: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
|
||||
Pass #0 contained 24 draws
|
||||
Pass #1 starts with 319: ClearDepthStencilView(D=1.000000, S=00)
|
||||
Pass #1 contained 24 draws
|
||||
Pass #2 starts with 558: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
|
||||
Pass #2 contained 3 draws
|
||||
Pass #3 starts with 617: ClearRenderTargetView(0.000000, 0.000000, 0.000000, 1.000000)
|
||||
Pass #3 contained 4 draws
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import renderdoc as rd
|
||||
|
||||
# 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.GetDrawcalls():
|
||||
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.name))
|
||||
|
||||
# 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
|
||||
status = cap.OpenFile(filename, '', None)
|
||||
|
||||
# Make sure the file opened successfully
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't open file: " + str(status))
|
||||
|
||||
# Make sure we can replay
|
||||
if not cap.LocalReplaySupport():
|
||||
raise RuntimeError("Capture cannot be replayed")
|
||||
|
||||
# Initialise the replay
|
||||
status,controller = cap.OpenCapture(None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't initialise replay: " + str(status))
|
||||
|
||||
return (cap, controller)
|
||||
|
||||
if 'pyrenderdoc' in globals():
|
||||
pyrenderdoc.Replay().BlockInvoke(sampleCode)
|
||||
else:
|
||||
cap,controller = loadCapture('test.rdc')
|
||||
|
||||
sampleCode(controller)
|
||||
|
||||
controller.Shutdown()
|
||||
cap.Shutdown()
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Save a texture to disk
|
||||
======================
|
||||
|
||||
In this example we will find a particular drawcall, and save the color output to disk as an image file.
|
||||
|
||||
To begin with, so that we have an interesting drawcall selected we iterate over the list of draws finding the drawcall with the highest vertex count. For more on how to iterate through a capture's list of drawcalls, see :doc:`iter_draws`.
|
||||
|
||||
Once we have set the drawcall 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 <save_texture.py>`.
|
||||
|
||||
.. literalinclude:: save_texture.py
|
||||
Reference in New Issue
Block a user