forked from baldurk/renderdoc
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecode_mesh.py
More file actions
277 lines (207 loc) · 8.86 KB
/
Copy pathdecode_mesh.py
File metadata and controls
277 lines (207 loc) · 8.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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):
state = controller.GetPipelineState()
# Get the index & vertex buffers, and fixed vertex inputs
ib = state.GetIBuffer()
vbs = state.GetVBuffers()
attrs = state.GetVertexInputs()
meshInputs = []
for attr in attrs:
# We don't handle instance attributes
if attr.perInstance:
raise RuntimeError("Instanced properties are not supported!")
meshInput = MeshData()
meshInput.indexResourceId = ib.resourceId
meshInput.indexByteOffset = ib.byteOffset
meshInput.indexByteStride = 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.vertexBuffer].byteOffset
meshInput.format = attr.format
meshInput.vertexResourceId = vbs[attr.vertexBuffer].resourceId
meshInput.vertexByteStride = vbs[attr.vertexBuffer].byteStride
meshInput.name = attr.name
meshInputs.append(meshInput)
return meshInputs
# Get a list of MeshData objects describing the vertex outputs at this draw
def getMeshOutputs(controller, postvs):
meshOutputs = []
posidx = 0
vs = controller.GetPipelineState().GetShaderReflection(rd.ShaderStage.Vertex)
# Repeat the process, but this time sourcing the data from postvs.
# Since these are outputs, we iterate over the list of outputs from the
# vertex shader's reflection data
for attr in vs.outputSignature:
# Copy most properties from the postvs struct
meshOutput = MeshData()
meshOutput.indexResourceId = postvs.indexResourceId
meshOutput.indexByteOffset = postvs.indexByteOffset
meshOutput.indexByteStride = postvs.indexByteStride
meshOutput.baseVertex = postvs.baseVertex
meshOutput.indexOffset = 0
meshOutput.numIndices = postvs.numIndices
# The total offset is the attribute offset from the base of the vertex,
# as calculated by the stride per index
meshOutput.vertexByteOffset = postvs.vertexByteOffset
meshOutput.vertexResourceId = postvs.vertexResourceId
meshOutput.vertexByteStride = postvs.vertexByteStride
# Construct a resource format for this element
meshOutput.format = rd.ResourceFormat()
meshOutput.format.compByteWidth = 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()