File: analyse.py

package info (click to toggle)
renderdoc 1.27%2Bdfsg-1
  • links: PTS, VCS
  • area: non-free
  • in suites: sid
  • size: 107,796 kB
  • sloc: cpp: 763,519; ansic: 326,847; python: 26,946; xml: 23,189; java: 11,382; cs: 7,181; makefile: 6,707; yacc: 5,682; ruby: 4,648; perl: 3,461; sh: 2,381; php: 2,119; lisp: 1,835; javascript: 1,525; tcl: 1,068; ml: 747
file content (317 lines) | stat: -rw-r--r-- 11,133 bytes parent folder | download | duplicates (2)
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
import struct
from typing import List
import renderdoc

# Alias for convenience - we need to import as-is so types don't get confused
rd = renderdoc


def open_capture(filename="", cap: rd.CaptureFile=None, opts: rd.ReplayOptions=None):
    """
    Opens a capture file and begins a replay.

    :param filename: The filename to open, or empty if cap is used.
    :param cap: The capture file to use, or ``None`` if a filename is given.
    :param opts: The replay options to use, or ``None`` to use the default options.
    :return: A replay controller for the capture
    :rtype: renderdoc.ReplayController
    """

    if opts is None:
        opts = rd.ReplayOptions()

    # Open a capture file handle
    own_cap = False
    api = "Unknown"

    if cap is None:
        own_cap = True

        cap = rd.OpenCaptureFile()

        # Open a particular file
        result = cap.OpenFile(filename, '', None)

        # Make sure the file opened successfully
        if result != rd.ResultCode.Succeeded:
            cap.Shutdown()
            raise RuntimeError("Couldn't open '{}': {}".format(filename, str(result)))

        api = cap.DriverName()

        # Make sure we can replay
        if not cap.LocalReplaySupport():
            cap.Shutdown()
            raise RuntimeError("{} capture cannot be replayed".format(api))

    result, controller = cap.OpenCapture(opts, None)

    if own_cap:
        cap.Shutdown()

    if result != rd.ResultCode.Succeeded:
        raise RuntimeError("Couldn't initialise replay for {}: {}".format(api, str(result)))

    return controller


def fetch_indices(controller: rd.ReplayController, action: rd.ActionDescription, mesh: rd.MeshFormat, index_offset: int, first_index: int, num_indices: int):

    pipe = controller.GetPipelineState()
    restart_idx = pipe.GetRestartIndex() & ((1 << (mesh.indexByteStride*8)) - 1)
    restart_enabled = pipe.IsRestartEnabled()

    # If we have an index buffer
    if mesh.indexResourceId != rd.ResourceId.Null():
        offset = mesh.indexByteStride*(first_index + index_offset)

        avail_bytes = mesh.indexByteSize
        if avail_bytes > offset:
            avail_bytes = avail_bytes - offset
        else:
            avail_bytes = 0

        read_bytes = min([avail_bytes, mesh.indexByteStride*num_indices])

        # Fetch the data
        if read_bytes > 0:
            ibdata = controller.GetBufferData(mesh.indexResourceId,
                                              mesh.indexByteOffset + offset,
                                              read_bytes)
        else:
            ibdata = bytes()

        # Get the character for the width of index
        index_fmt = 'B'
        if mesh.indexByteStride == 2:
            index_fmt = 'H'
        elif mesh.indexByteStride == 4:
            index_fmt = 'I'

        avail_indices = int(len(ibdata) / mesh.indexByteStride)

        # Duplicate the format by the number of indices
        index_fmt = '=' + str(min([avail_indices, num_indices])) + index_fmt

        # Unpack all the indices
        indices = struct.unpack_from(index_fmt, ibdata)

        extra = []
        if avail_indices < num_indices:
            extra = [None] * (num_indices - avail_indices)

        # Apply the baseVertex offset
        return [i if restart_enabled and i == restart_idx else i + mesh.baseVertex for i in indices] + extra
    else:
        # With no index buffer, just generate a range
        return tuple(range(first_index, first_index + num_indices))


class MeshAttribute:
    mesh: rd.MeshFormat
    name: str


def get_vsin_attrs(controller: rd.ReplayController, vertexOffset: int, index_mesh: rd.MeshFormat):
    pipe: rd.PipeState = controller.GetPipelineState()
    inputs: List[rd.VertexInputAttribute] = pipe.GetVertexInputs()

    attrs: List[MeshAttribute] = []
    vbs: List[rd.BoundVBuffer] = pipe.GetVBuffers()

    for a in inputs:
        if not a.used:
            continue

        attr = MeshAttribute()
        attr.name = a.name
        attr.mesh = rd.MeshFormat(index_mesh)

        attr.mesh.vertexByteStride = vbs[a.vertexBuffer].byteStride
        attr.mesh.instStepRate = a.instanceRate
        attr.mesh.instanced = a.perInstance
        attr.mesh.vertexResourceId = vbs[a.vertexBuffer].resourceId

        offs = a.byteOffset + vertexOffset * attr.mesh.vertexByteStride

        attr.mesh.vertexByteOffset = vbs[a.vertexBuffer].byteOffset + offs
        attr.mesh.vertexByteSize = max([0, vbs[a.vertexBuffer].byteSize - offs])

        attr.mesh.format = a.format

        attrs.append(attr)

    return attrs


def get_postvs_attrs(controller: rd.ReplayController, mesh: rd.MeshFormat, data_stage: rd.MeshDataStage):
    pipe: rd.PipeState = controller.GetPipelineState()

    if data_stage == rd.MeshDataStage.VSOut:
        shader = pipe.GetShaderReflection(rd.ShaderStage.Vertex)
    else:
        shader = pipe.GetShaderReflection(rd.ShaderStage.Geometry)
        if shader is None:
            shader = pipe.GetShaderReflection(rd.ShaderStage.Domain)

    attrs: List[MeshAttribute] = []
    posidx = 0

    for sig in shader.outputSignature:
        attr = MeshAttribute()
        attr.mesh = rd.MeshFormat(mesh)

        if pipe.GetRasterizedStream() >= 0:
            if sig.stream != pipe.GetRasterizedStream():
                continue
        else:
            if sig.stream != 0:
                continue

        # Construct a resource format for this element
        attr.mesh.format = rd.ResourceFormat()
        attr.mesh.format.compByteWidth = rd.VarTypeByteSize(sig.varType)
        attr.mesh.format.compCount = sig.compCount
        attr.mesh.format.compType = rd.VarTypeCompType(sig.varType)
        attr.mesh.format.type = rd.ResourceFormatType.Regular

        attr.name = sig.semanticIdxName if sig.varName == '' else sig.varName

        if sig.systemValue == rd.ShaderBuiltin.Position:
            posidx = len(attrs)

        attrs.append(attr)

    # Shuffle the position element to the front
    if posidx > 0:
        pos = attrs[posidx]
        del attrs[posidx]
        attrs.insert(0, pos)

    accum_offset = 0

    for i in range(0, len(attrs)):
        # Note that some APIs such as Vulkan will pad the size of the attribute here
        # while others will tightly pack
        fmt = attrs[i].mesh.format

        elem_size = (8 if fmt.compByteWidth > 4 else 4)

        alignment = elem_size
        if fmt.compCount == 2:
            alignment = elem_size * 2
        elif fmt.compCount > 2:
            alignment = elem_size * 4

        if pipe.HasAlignedPostVSData(data_stage) and (accum_offset % alignment) != 0:
            accum_offset += alignment - (accum_offset % alignment)

        attrs[i].mesh.vertexByteOffset += accum_offset

        accum_offset += elem_size * fmt.compCount

    return attrs


# Unpack a tuple of the given format, from the data
def unpack_data(fmt: rd.ResourceFormat, data: bytes, data_offset: int):
    # 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!")

    format_chars = {
        #                   012345678
        rd.CompType.UInt:  "xBHxIxxxQ",
        rd.CompType.SInt:  "xbhxixxxq",
        rd.CompType.Float: "xxexfxxxd",  # only 2, 4 and 8 are valid
    }

    # These types have identical decodes, but we might post-process them
    format_chars[rd.CompType.UNorm] = format_chars[rd.CompType.UInt]
    format_chars[rd.CompType.UScaled] = format_chars[rd.CompType.UInt]
    format_chars[rd.CompType.SNorm] = format_chars[rd.CompType.SInt]
    format_chars[rd.CompType.SScaled] = format_chars[rd.CompType.SInt]

    # We need to fetch compCount components
    vertex_format = '=' + str(fmt.compCount) + format_chars[fmt.compType][fmt.compByteWidth]

    if data_offset >= len(data):
        return None

    # Unpack the data
    try:
        value = struct.unpack_from(vertex_format, data, data_offset)
    except struct.error as ex:
        raise

    # If the format needs post-processing such as normalisation, do that now
    if fmt.compType == rd.CompType.UNorm:
        divisor = float((1 << (fmt.compByteWidth*8)) - 1)
        value = tuple(float(i) / divisor for i in value)
    elif fmt.compType == rd.CompType.SNorm:
        max_neg = -(1 << (fmt.compByteWidth*8 - 1))
        divisor = -float(max_neg+1)
        value = tuple(-1.0 if (i == max_neg) else float(i / divisor) for i in value)
    elif fmt.compType == rd.CompType.UScaled or fmt.compType == rd.CompType.SScaled:
        value = tuple(float(i) 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


def decode_mesh_data(controller: rd.ReplayController, indices: List[int], display_indices: List[int],
                     attrs: List[MeshAttribute], instance: int = 0, indexOffset: int = 0):
    ret = []

    buffer_ranges = {}
    for attr in attrs:
        begin = attr.mesh.vertexByteOffset
        end = min(begin + attr.mesh.vertexByteSize, 0xffffffffffffffff)

        # This could be more optimal if we figure out the lower/upper bounds of any attribute and only fetch the
        # data we need. For each referenced buffer, pick the attribute that references the largest range and fetch that
        if attr.mesh.vertexResourceId in buffer_ranges:
            buf_range = buffer_ranges[attr.mesh.vertexResourceId]

            if buf_range[0] < begin:
                begin = buf_range[0]
            if buf_range[1] > end:
                end = buf_range[1]

        buffer_ranges[attr.mesh.vertexResourceId] = (begin, end)

    buffer_data = {}
    for buf, buf_range in buffer_ranges.items():
        buffer_data[buf] = controller.GetBufferData(buf, buf_range[0], buf_range[1] - buf_range[0])

    # Calculate the strip restart index for this index width
    striprestart_index = None
    if controller.GetPipelineState().IsRestartEnabled() and attrs[0].mesh.indexResourceId != rd.ResourceId.Null():
        striprestart_index = (controller.GetPipelineState().GetRestartIndex() &
                              ((1 << (attrs[0].mesh.indexByteStride*8)) - 1))

    for i,idx in enumerate(indices):
        vertex = {'vtx': i, 'idx': display_indices[i]}

        if striprestart_index is None or idx != striprestart_index:
            for attr in attrs:
                if idx is None:
                    vertex[attr.name] = None
                    continue

                offset = attr.mesh.vertexByteStride * idx

                if attr.mesh.instanced:
                    offset = (attr.mesh.vertexByteStride +
                              attr.mesh.vertexByteStride * int(instance / max(attr.mesh.instStepRate, 1)))

                vertex[attr.name] = unpack_data(attr.mesh.format, buffer_data[attr.mesh.vertexResourceId],
                                                attr.mesh.vertexByteOffset + offset -
                                                buffer_ranges[attr.mesh.vertexResourceId][0])

        ret.append(vertex)

    return ret