• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3import ctypes
4import sys
5import json
6
7def get_header_properties(header):
8    return {
9        'rootNodeOffset': header.rootNodeOffset,
10        'aabb': {
11            'min_x': header.aabb.min_x,
12            'min_y': header.aabb.min_y,
13            'min_z': header.aabb.min_z,
14            'max_x': header.aabb.max_x,
15            'max_y': header.aabb.max_y,
16            'max_z': header.aabb.max_z,
17        },
18        'instance_flags': header.instance_flags,
19        'copy_dispatch_size': list(header.copy_dispatch_size),
20        'compacted_size': header.compacted_size,
21        'serialization_size': header.serialization_size,
22        'size': header.size,
23        'instance_count': header.instance_count,
24        'self_ptr': header.self_ptr,
25        'padding': f"{len(header.padding)} uint32_t paddings",
26    }
27
28def get_aabb_leaf_properties(node):
29    return {
30        'leaf_desc': {
31            'shaderIndex': node.leaf_desc.shader_index_and_geom_mask & 0xFFFFFF,
32            'geomMask': (node.leaf_desc.shader_index_and_geom_mask >> 24) & 0xFF,
33            'geomIndex': node.leaf_desc.geometry_id_and_flags & 0xFFFFFF,
34            'subType': (node.leaf_desc.geometry_id_and_flags >> 24) & 0xF,
35            'reserved0': (node.leaf_desc.geometry_id_and_flags >> 28) & 0x1,
36            'DisableOpacityCull': (node.leaf_desc.geometry_id_and_flags >> 29) & 0x1,
37            'OpaqueGeometry': (node.leaf_desc.geometry_id_and_flags >> 30) & 0x1,
38            'IgnoreRayMultiplier': (node.leaf_desc.geometry_id_and_flags >> 31) & 0x1
39        },
40        'DW1': node.DW1,
41        'primIndex': f"{len(node.primIndex)} uint32"
42    }
43
44def get_quad_leaf_properties(node):
45    return {
46        'leaf_desc': {
47            'shaderIndex': node.leaf_desc.shader_index_and_geom_mask & 0xFFFFFF,
48            'geomMask': (node.leaf_desc.shader_index_and_geom_mask >> 24) & 0xFF,
49            'geomIndex': node.leaf_desc.geometry_id_and_flags & 0xFFFFFF,
50            'subType': (node.leaf_desc.geometry_id_and_flags >> 24) & 0xF,
51            'reserved0': (node.leaf_desc.geometry_id_and_flags >> 28) & 0x1,
52            'DisableOpacityCull': (node.leaf_desc.geometry_id_and_flags >> 29) & 0x1,
53            'OpaqueGeometry': (node.leaf_desc.geometry_id_and_flags >> 30) & 0x1,
54            'IgnoreRayMultiplier': (node.leaf_desc.geometry_id_and_flags >> 31) & 0x1
55        },
56        'prim_index0': node.prim_index0,
57        'prim_index1_and_flags':{
58            'primIndex1Delta': node.prim_index1_and_flags & 0xFFFF,
59            'j0': (node.prim_index1_and_flags >> 16) & 0x3,
60            'j1': (node.prim_index1_and_flags >> 18) & 0x3,
61            'j2': (node.prim_index1_and_flags >> 20) & 0x3,
62            'last': (node.prim_index1_and_flags >> 22) & 0x1,
63            'pad': (node.prim_index1_and_flags >> 23) & 0x1FF
64        },
65        'v': [[node.v[i][j] for j in range(3)] for i in range(4)]
66    }
67
68def get_internal_node_properties(node):
69    # Calculate the actual coordinates, just for visualizing and debugging
70    actual_coords = []
71    for i in range(6):
72        # Turns out the formula is like: x = lower.x + pow(2,exp_x) * 0.xi
73        xi_lower = node.lower_x[i] / 256.0  # Convert mantissa to fractional value
74        xi_upper = node.upper_x[i] / 256.0  # Convert mantissa to fractional value
75        yi_lower = node.lower_y[i] / 256.0  # Convert mantissa to fractional value
76        yi_upper = node.upper_y[i] / 256.0  # Convert mantissa to fractional value
77        zi_lower = node.lower_z[i] / 256.0  # Convert mantissa to fractional value
78        zi_upper = node.upper_z[i] / 256.0  # Convert mantissa to fractional value
79
80        x_lower = node.lower[0] + (2 ** node.exp_x) * xi_lower
81        x_upper = node.lower[0] + (2 ** node.exp_x) * xi_upper
82        y_lower = node.lower[1] + (2 ** node.exp_y) * yi_lower
83        y_upper = node.lower[1] + (2 ** node.exp_y) * yi_upper
84        z_lower = node.lower[2] + (2 ** node.exp_z) * zi_lower
85        z_upper = node.lower[2] + (2 ** node.exp_z) * zi_upper
86
87        actual_coords.append({
88            'x_lower': x_lower,
89            'x_upper': x_upper,
90            'y_lower': y_lower,
91            'y_upper': y_upper,
92            'z_lower': z_lower,
93            'z_upper': z_upper
94        })
95
96    return {
97        'lower': list(node.lower),
98        'child_offset': node.child_offset,
99        'node_type': {
100            'nodeType': node.node_type & 0xF,
101            'subType': (node.node_type >> 4) & 0xF
102        },
103        'reserved': node.reserved,
104        'exp_x': node.exp_x,
105        'exp_y': node.exp_y,
106        'exp_z': node.exp_z,
107        'node_mask': node.node_mask,
108        'child_data': [{
109            'blockIncr': node.child_data[i].blockIncr_and_startPrim & 0x3,
110            'startPrim': (node.child_data[i].blockIncr_and_startPrim >> 2) & 0xf
111        } for i in range(6)],
112        'lower_x': list(node.lower_x),
113        'upper_x': list(node.upper_x),
114        'lower_y': list(node.lower_y),
115        'upper_y': list(node.upper_y),
116        'lower_z': list(node.lower_z),
117        'upper_z': list(node.upper_z),
118        'actual_coords': actual_coords
119    }
120
121def get_instance_leaf_properties(node):
122    return {
123        'part0': {
124            'shaderIndex': node.part0.shader_index_and_geom_mask & 0xFFFFFF,
125            'geomMask': (node.part0.shader_index_and_geom_mask >> 24) & 0xFF,
126            'instanceContribution': node.part0.instance_contribution_and_geom_flags & 0xFFFFFF,
127            'pad0': (node.part0.instance_contribution_and_geom_flags >> 24) & 0x1F,
128            'DisableOpacityCull': (node.part0.instance_contribution_and_geom_flags >> 29) & 0x1,
129            'OpaqueGeometry': (node.part0.instance_contribution_and_geom_flags >> 30) & 0x1,
130            'pad1': (node.part0.instance_contribution_and_geom_flags >> 31) & 0x1,
131            'startNodePtr': node.part0.start_node_ptr_and_inst_flags & 0xFFFFFFFFFFFF,
132            'instFlags': (node.part0.start_node_ptr_and_inst_flags >> 48) & 0xFF,
133            'ComparisonMode': (node.part0.start_node_ptr_and_inst_flags >> 56) & 0x1,
134            'ComparisonValue': (node.part0.start_node_ptr_and_inst_flags >> 57) & 0x7F,
135            'world2obj_vx': [node.part0.world2obj_vx_x, node.part0.world2obj_vx_y, node.part0.world2obj_vx_z],
136            'world2obj_vy': [node.part0.world2obj_vy_x, node.part0.world2obj_vy_y, node.part0.world2obj_vy_z],
137            'world2obj_vz': [node.part0.world2obj_vz_x, node.part0.world2obj_vz_y, node.part0.world2obj_vz_z],
138            'obj2world_p': [node.part0.obj2world_p_x, node.part0.obj2world_p_y, node.part0.obj2world_p_z]
139        },
140        'part1': {
141            'bvh_ptr': node.part1.bvh_ptr,
142            'instance_id': node.part1.instance_id,
143            'instance_index': node.part1.instance_index,
144            'obj2world_vx': [node.part1.obj2world_vx_x, node.part1.obj2world_vx_y, node.part1.obj2world_vx_z],
145            'obj2world_vy': [node.part1.obj2world_vy_x, node.part1.obj2world_vy_y, node.part1.obj2world_vy_z],
146            'obj2world_vz': [node.part1.obj2world_vz_x, node.part1.obj2world_vz_y, node.part1.obj2world_vz_z],
147            'world2obj_p': [node.part1.world2obj_p_x, node.part1.world2obj_p_y, node.part1.world2obj_p_z]
148        }
149    }
150
151class NodeType:
152    NODE_TYPE_MIXED = 0x0
153    NODE_TYPE_INTERNAL = 0x0
154    NODE_TYPE_INSTANCE = 0x1
155    NODE_TYPE_QUAD128_STOC = 0x2
156    NODE_TYPE_PROCEDURAL = 0x3
157    NODE_TYPE_QUAD = 0x4
158    NODE_TYPE_QUAD128 = 0x5
159    NODE_TYPE_MESHLET = 0x6
160    NODE_TYPE_INVALID = 0x7
161
162class VkAabb(ctypes.Structure):
163    _fields_ = (
164        ('min_x', ctypes.c_float),
165        ('min_y', ctypes.c_float),
166        ('min_z', ctypes.c_float),
167        ('max_x', ctypes.c_float),
168        ('max_y', ctypes.c_float),
169        ('max_z', ctypes.c_float),
170    )
171
172class AnvAccelStructHeader(ctypes.Structure):
173    _fields_ = (
174        ('rootNodeOffset', ctypes.c_uint64),
175        ('aabb', VkAabb),
176        ('instance_flags', ctypes.c_uint32),
177        ('copy_dispatch_size', ctypes.c_uint32 * 3),
178        ('compacted_size', ctypes.c_uint64),
179        ('serialization_size', ctypes.c_uint64),
180        ('size', ctypes.c_uint64),
181        ('instance_count', ctypes.c_uint64),
182        ('self_ptr', ctypes.c_uint64),
183        ('padding', ctypes.c_uint32 * 42),
184    )
185
186class ChildData(ctypes.Structure):
187    _fields_ = (
188        ('blockIncr_and_startPrim', ctypes.c_uint8),  # Assuming child_data has startPrim field
189    )
190
191class AnvInternalNode(ctypes.Structure):
192    _fields_ = (
193        ('lower', ctypes.c_float * 3),
194        ('child_offset', ctypes.c_uint32),
195        ('node_type', ctypes.c_uint8),
196        ('reserved', ctypes.c_uint8),
197        ('exp_x', ctypes.c_int8),
198        ('exp_y', ctypes.c_int8),
199        ('exp_z', ctypes.c_int8),
200        ('node_mask', ctypes.c_uint8),
201        ('child_data', ChildData * 6),
202        ('lower_x', ctypes.c_uint8 * 6),
203        ('upper_x', ctypes.c_uint8 * 6),
204        ('lower_y', ctypes.c_uint8 * 6),
205        ('upper_y', ctypes.c_uint8 * 6),
206        ('lower_z', ctypes.c_uint8 * 6),
207        ('upper_z', ctypes.c_uint8 * 6),
208    )
209
210class AnvPrimLeafDesc(ctypes.Structure):
211    _fields_ = (
212        ('shader_index_and_geom_mask', ctypes.c_uint32),
213        ('geometry_id_and_flags', ctypes.c_uint32),
214    )
215
216class AnvQuadLeafNode(ctypes.Structure):
217    _fields_ = (
218        ('leaf_desc', AnvPrimLeafDesc),
219        ('prim_index0', ctypes.c_uint32),
220        ('prim_index1_and_flags', ctypes.c_uint32),
221        ('v', (ctypes.c_float * 3) * 4),
222    )
223
224class AnvProceduralLeafNode(ctypes.Structure):
225    _fields_ = (
226        ('leaf_desc', AnvPrimLeafDesc),
227        ('DW1', ctypes.c_uint32),
228        ('primIndex', ctypes.c_uint32 * 13),
229    )
230
231class InstanceLeafPart0(ctypes.Structure):
232    _fields_ = (
233        ('shader_index_and_geom_mask', ctypes.c_uint32),
234        ('instance_contribution_and_geom_flags', ctypes.c_uint32),
235        ('start_node_ptr_and_inst_flags', ctypes.c_uint64),
236        ('world2obj_vx_x', ctypes.c_float),
237        ('world2obj_vx_y', ctypes.c_float),
238        ('world2obj_vx_z', ctypes.c_float),
239        ('world2obj_vy_x', ctypes.c_float),
240        ('world2obj_vy_y', ctypes.c_float),
241        ('world2obj_vy_z', ctypes.c_float),
242        ('world2obj_vz_x', ctypes.c_float),
243        ('world2obj_vz_y', ctypes.c_float),
244        ('world2obj_vz_z', ctypes.c_float),
245        ('obj2world_p_x', ctypes.c_float),
246        ('obj2world_p_y', ctypes.c_float),
247        ('obj2world_p_z', ctypes.c_float),
248    )
249
250class InstanceLeafPart1(ctypes.Structure):
251    _fields_ = (
252        ('bvh_ptr', ctypes.c_uint64),
253        ('instance_id', ctypes.c_uint32),
254        ('instance_index', ctypes.c_uint32),
255        ('obj2world_vx_x', ctypes.c_float),
256        ('obj2world_vx_y', ctypes.c_float),
257        ('obj2world_vx_z', ctypes.c_float),
258        ('obj2world_vy_x', ctypes.c_float),
259        ('obj2world_vy_y', ctypes.c_float),
260        ('obj2world_vy_z', ctypes.c_float),
261        ('obj2world_vz_x', ctypes.c_float),
262        ('obj2world_vz_y', ctypes.c_float),
263        ('obj2world_vz_z', ctypes.c_float),
264        ('world2obj_p_x', ctypes.c_float),
265        ('world2obj_p_y', ctypes.c_float),
266        ('world2obj_p_z', ctypes.c_float),
267    )
268
269class AnvInstanceLeaf(ctypes.Structure):
270    _fields_ = (
271        ('part0', InstanceLeafPart0),
272        ('part1', InstanceLeafPart1),
273    )
274
275class BVHInterpreter:
276    def __init__(self, data):
277        self.data = data
278        self.nodes = []
279        self.relationships = {}
280        self.node_counter = 0
281
282    def interpret_structure(self, offset, structure):
283        size = ctypes.sizeof(structure)
284        if(offset + size > len(self.data)):
285            raise ValueError("Not enought data to interpret this structure.")
286        buffer = self.data[offset:offset + size]
287        return structure.from_buffer_copy(buffer)
288
289    def parse_bvh(self):
290        offset = 0
291        # Interpret the header
292        header = self.interpret_structure(offset, AnvAccelStructHeader)
293        offset += header.rootNodeOffset
294
295        # Interpret the rootNode
296        self.dfs_interpret_node(offset, AnvInternalNode)
297
298        output = {
299            'header': get_header_properties(header),
300            'nodes': self.nodes,
301            'relationships': self.relationships
302        }
303        return output
304
305    def determine_child_structure(self, child_node_type):
306        if child_node_type == NodeType.NODE_TYPE_MIXED:
307            return AnvInternalNode
308        elif child_node_type == NodeType.NODE_TYPE_INSTANCE:
309            return AnvInstanceLeaf
310        elif child_node_type == NodeType.NODE_TYPE_QUAD:
311            return AnvQuadLeafNode
312        elif child_node_type == NodeType.NODE_TYPE_PROCEDURAL:
313            return AnvProceduralLeafNode
314        else:
315            raise ValueError(f"Unknown node type: {child_node_type}")
316
317    def dfs_interpret_node(self, offset, structure):
318        node = self.interpret_structure(offset, structure)
319        node_id = self.node_counter;
320        self.node_counter += 1
321
322        if structure == AnvInternalNode:
323            node_type_str = "AnvInternalNode"
324            node_properties = get_internal_node_properties(node)
325        elif structure == AnvInstanceLeaf:
326            node_type_str = "AnvInstanceLeaf"
327            node_properties = get_instance_leaf_properties(node)
328        elif structure == AnvQuadLeafNode:
329            node_type_str = "AnvQuadLeafNode"
330            node_properties = get_quad_leaf_properties(node)
331        elif structure == AnvProceduralLeafNode:
332            node_type_str = "AnvProceduralLeafNode"
333            node_properties = get_aabb_leaf_properties(node)
334        else:
335            raise ValueError(f"Unknown structure type: {structure}")
336
337        self.nodes.append({
338            'id': node_id,
339            'type': node_type_str,
340            'properties': node_properties
341        })
342
343        self.relationships[node_id] = []
344
345        if node_type_str == "AnvInternalNode":
346            # DFS its children
347            children_offset_start = offset + node.child_offset * 64 # this node's position + child_offset
348            isFatLeaf = True if node.node_type != NodeType.NODE_TYPE_MIXED else False
349            added_blocks = 0
350            for i in range(6):
351                blockIncr = node.child_data[i].blockIncr_and_startPrim & 0x3
352                child_is_valid = not (node.lower_x[i] & 0x80) or (node.upper_x[i] & 0x80)
353                if(not child_is_valid):
354                    continue
355
356                # now determine the children's type
357                child_node_type = node.node_type if isFatLeaf else ((node.child_data[i].blockIncr_and_startPrim >> 2) & 0xf)
358
359                # find where my child is
360                child_offset = children_offset_start + 64 * added_blocks
361                added_blocks += blockIncr
362
363                child_node_id = self.dfs_interpret_node(child_offset, self.determine_child_structure(child_node_type))
364                self.relationships[node_id].append(child_node_id)
365
366        return node_id
367
368
369def main():
370    with open(sys.argv[1], 'rb') as file1:
371        data = file1.read()
372    interpreter = BVHInterpreter(data)
373    json_output = interpreter.parse_bvh()
374    with open("bvh_dump.json", 'w') as f:
375        json.dump(json_output, f, indent=4)
376
377
378if __name__=="__main__":
379    main()
380