blob: ee6cc493115c0aa3b85c500fe2493857a409d835 [file] [log] [blame]
#!/usr/bin/env python3
import ctypes
import sys
import json
def get_header_properties(header):
return {
'rootNodeOffset': header.rootNodeOffset,
'aabb': {
'min_x': header.aabb.min_x,
'min_y': header.aabb.min_y,
'min_z': header.aabb.min_z,
'max_x': header.aabb.max_x,
'max_y': header.aabb.max_y,
'max_z': header.aabb.max_z,
},
'instance_flags': header.instance_flags,
'copy_dispatch_size': list(header.copy_dispatch_size),
'compacted_size': header.compacted_size,
'serialization_size': header.serialization_size,
'size': header.size,
'instance_count': header.instance_count,
'self_ptr': header.self_ptr,
'enable_64b_rt': header.enable_64b_rt,
'padding': f"{len(header.padding)} uint32_t paddings",
}
def get_aabb_leaf_properties(node):
return {
'leaf_desc': {
'shaderIndex': node.leaf_desc.shader_index_and_geom_mask & 0xFFFFFF,
'geomMask': (node.leaf_desc.shader_index_and_geom_mask >> 24) & 0xFF,
'geomIndex': node.leaf_desc.geometry_id_and_flags & 0xFFFFFF,
'subType': (node.leaf_desc.geometry_id_and_flags >> 24) & 0xF,
'reserved0': (node.leaf_desc.geometry_id_and_flags >> 28) & 0x1,
'DisableOpacityCull': (node.leaf_desc.geometry_id_and_flags >> 29) & 0x1,
'OpaqueGeometry': (node.leaf_desc.geometry_id_and_flags >> 30) & 0x1,
'IgnoreRayMultiplier': (node.leaf_desc.geometry_id_and_flags >> 31) & 0x1
},
'DW1': node.DW1,
'primIndex': f"{len(node.primIndex)} uint32"
}
def get_quad_leaf_properties(node):
return {
'leaf_desc': {
'shaderIndex': node.leaf_desc.shader_index_and_geom_mask & 0xFFFFFF,
'geomMask': (node.leaf_desc.shader_index_and_geom_mask >> 24) & 0xFF,
'geomIndex': node.leaf_desc.geometry_id_and_flags & 0xFFFFFF,
'subType': (node.leaf_desc.geometry_id_and_flags >> 24) & 0xF,
'reserved0': (node.leaf_desc.geometry_id_and_flags >> 28) & 0x1,
'DisableOpacityCull': (node.leaf_desc.geometry_id_and_flags >> 29) & 0x1,
'OpaqueGeometry': (node.leaf_desc.geometry_id_and_flags >> 30) & 0x1,
'IgnoreRayMultiplier': (node.leaf_desc.geometry_id_and_flags >> 31) & 0x1
},
'prim_index0': node.prim_index0,
'prim_index1_and_flags':{
'primIndex1Delta': node.prim_index1_and_flags & 0xFFFF,
'j0': (node.prim_index1_and_flags >> 16) & 0x3,
'j1': (node.prim_index1_and_flags >> 18) & 0x3,
'j2': (node.prim_index1_and_flags >> 20) & 0x3,
'last': (node.prim_index1_and_flags >> 22) & 0x1,
'pad': (node.prim_index1_and_flags >> 23) & 0x1FF
},
'v': [[node.v[i][j] for j in range(3)] for i in range(4)]
}
def get_internal_node_properties(node):
# Calculate the actual coordinates, just for visualizing and debugging
actual_coords = []
for i in range(6):
# Turns out the formula is like: x = lower.x + pow(2,exp_x) * 0.xi
xi_lower = node.lower_x[i] / 256.0 # Convert mantissa to fractional value
xi_upper = node.upper_x[i] / 256.0 # Convert mantissa to fractional value
yi_lower = node.lower_y[i] / 256.0 # Convert mantissa to fractional value
yi_upper = node.upper_y[i] / 256.0 # Convert mantissa to fractional value
zi_lower = node.lower_z[i] / 256.0 # Convert mantissa to fractional value
zi_upper = node.upper_z[i] / 256.0 # Convert mantissa to fractional value
x_lower = node.lower[0] + (2 ** node.exp_x) * xi_lower
x_upper = node.lower[0] + (2 ** node.exp_x) * xi_upper
y_lower = node.lower[1] + (2 ** node.exp_y) * yi_lower
y_upper = node.lower[1] + (2 ** node.exp_y) * yi_upper
z_lower = node.lower[2] + (2 ** node.exp_z) * zi_lower
z_upper = node.lower[2] + (2 ** node.exp_z) * zi_upper
actual_coords.append({
'x_lower': x_lower,
'x_upper': x_upper,
'y_lower': y_lower,
'y_upper': y_upper,
'z_lower': z_lower,
'z_upper': z_upper
})
return {
'lower': list(node.lower),
'child_offset': node.child_offset,
'node_type': {
'nodeType': node.node_type & 0xF,
'subType': (node.node_type >> 4) & 0xF
},
'reserved': node.reserved,
'exp_x': node.exp_x,
'exp_y': node.exp_y,
'exp_z': node.exp_z,
'node_mask': node.node_mask,
'child_data': [{
'blockIncr': node.child_data[i].blockIncr_and_startPrim & 0x3,
'startPrim': (node.child_data[i].blockIncr_and_startPrim >> 2) & 0xf
} for i in range(6)],
'lower_x': list(node.lower_x),
'upper_x': list(node.upper_x),
'lower_y': list(node.lower_y),
'upper_y': list(node.upper_y),
'lower_z': list(node.lower_z),
'upper_z': list(node.upper_z),
'actual_coords': actual_coords
}
def get_instance_leaf_properties(node, enable_64b_rt):
if enable_64b_rt:
part0 = {
'instanceContribution': node.part0.DW0 & 0xFFFFFF,
'geomMask': (node.part0.DW0 >> 24) & 0xFF,
'insFlags': node.part0.DW1 & 0xFF,
'ComparisonMode': (node.part0.DW1 >> 8) & 0x1,
'ComparisonValue': (node.part0.DW1 >> 9) & 0x7F,
'pad0': (node.part0.DW1 >> 16) & 0xFF,
'subType': (node.part0.DW1 >> 24) & 0xF,
'pad1': (node.part0.DW1 >> 28) & 0x1,
'DisableOpacityCull': (node.part0.DW1 >> 29) & 0x1,
'OpaqueGeometry': (node.part0.DW1 >> 30) & 0x1,
'IgnoreRayMultiplier': (node.part0.DW1 >> 31) & 0x1,
'startNodePtr': node.part0.QW_startNodePtr,
}
else:
part0 = {
'shaderIndex': node.part0.DW0 & 0xFFFFFF,
'geomMask': (node.part0.DW0 >> 24) & 0xFF,
'instanceContribution': node.part0.DW1 & 0xFFFFFF,
'pad0': (node.part0.DW1 >> 24) & 0x1F,
'DisableOpacityCull': (node.part0.DW1 >> 29) & 0x1,
'OpaqueGeometry': (node.part0.DW1 >> 30) & 0x1,
'pad1': (node.part0.DW1 >> 31) & 0x1,
'startNodePtr': node.part0.QW_startNodePtr & 0xFFFFFFFFFFFF,
'instFlags': (node.part0.QW_startNodePtr >> 48) & 0xFF,
}
return {
'part0': {
**part0,
'world2obj_vx': [node.part0.world2obj_vx_x, node.part0.world2obj_vx_y, node.part0.world2obj_vx_z],
'world2obj_vy': [node.part0.world2obj_vy_x, node.part0.world2obj_vy_y, node.part0.world2obj_vy_z],
'world2obj_vz': [node.part0.world2obj_vz_x, node.part0.world2obj_vz_y, node.part0.world2obj_vz_z],
'obj2world_p': [node.part0.obj2world_p_x, node.part0.obj2world_p_y, node.part0.obj2world_p_z]
},
'part1': {
'bvh_ptr': node.part1.bvh_ptr,
'instance_id': node.part1.instance_id,
'instance_index': node.part1.instance_index,
'obj2world_vx': [node.part1.obj2world_vx_x, node.part1.obj2world_vx_y, node.part1.obj2world_vx_z],
'obj2world_vy': [node.part1.obj2world_vy_x, node.part1.obj2world_vy_y, node.part1.obj2world_vy_z],
'obj2world_vz': [node.part1.obj2world_vz_x, node.part1.obj2world_vz_y, node.part1.obj2world_vz_z],
'world2obj_p': [node.part1.world2obj_p_x, node.part1.world2obj_p_y, node.part1.world2obj_p_z]
}
}
class NodeType:
NODE_TYPE_MIXED = 0x0
NODE_TYPE_INTERNAL = 0x0
NODE_TYPE_INSTANCE = 0x1
NODE_TYPE_QUAD128_STOC = 0x2
NODE_TYPE_PROCEDURAL = 0x3
NODE_TYPE_QUAD = 0x4
NODE_TYPE_QUAD128 = 0x5
NODE_TYPE_MESHLET = 0x6
NODE_TYPE_INVALID = 0x7
class VkAabb(ctypes.Structure):
_fields_ = (
('min_x', ctypes.c_float),
('min_y', ctypes.c_float),
('min_z', ctypes.c_float),
('max_x', ctypes.c_float),
('max_y', ctypes.c_float),
('max_z', ctypes.c_float),
)
class AnvAccelStructHeader(ctypes.Structure):
_fields_ = (
('rootNodeOffset', ctypes.c_uint64),
('aabb', VkAabb),
('instance_flags', ctypes.c_uint32),
('copy_dispatch_size', ctypes.c_uint32 * 3),
('compacted_size', ctypes.c_uint64),
('serialization_size', ctypes.c_uint64),
('size', ctypes.c_uint64),
('instance_count', ctypes.c_uint64),
('self_ptr', ctypes.c_uint64),
('enable_64b_rt', ctypes.c_uint32),
('padding', ctypes.c_uint32 * 41),
)
class ChildData(ctypes.Structure):
_fields_ = (
('blockIncr_and_startPrim', ctypes.c_uint8), # Assuming child_data has startPrim field
)
class AnvInternalNode(ctypes.Structure):
_fields_ = (
('lower', ctypes.c_float * 3),
('child_offset', ctypes.c_uint32),
('node_type', ctypes.c_uint8),
('reserved', ctypes.c_uint8),
('exp_x', ctypes.c_int8),
('exp_y', ctypes.c_int8),
('exp_z', ctypes.c_int8),
('node_mask', ctypes.c_uint8),
('child_data', ChildData * 6),
('lower_x', ctypes.c_uint8 * 6),
('upper_x', ctypes.c_uint8 * 6),
('lower_y', ctypes.c_uint8 * 6),
('upper_y', ctypes.c_uint8 * 6),
('lower_z', ctypes.c_uint8 * 6),
('upper_z', ctypes.c_uint8 * 6),
)
class AnvPrimLeafDesc(ctypes.Structure):
_fields_ = (
('shader_index_and_geom_mask', ctypes.c_uint32),
('geometry_id_and_flags', ctypes.c_uint32),
)
class AnvQuadLeafNode(ctypes.Structure):
_fields_ = (
('leaf_desc', AnvPrimLeafDesc),
('prim_index0', ctypes.c_uint32),
('prim_index1_and_flags', ctypes.c_uint32),
('v', (ctypes.c_float * 3) * 4),
)
class AnvProceduralLeafNode(ctypes.Structure):
_fields_ = (
('leaf_desc', AnvPrimLeafDesc),
('DW1', ctypes.c_uint32),
('primIndex', ctypes.c_uint32 * 13),
)
class InstanceLeafPart0(ctypes.Structure):
_fields_ = (
('DW0', ctypes.c_uint32),
('DW1', ctypes.c_uint32),
('QW_startNodePtr', ctypes.c_uint64),
('world2obj_vx_x', ctypes.c_float),
('world2obj_vx_y', ctypes.c_float),
('world2obj_vx_z', ctypes.c_float),
('world2obj_vy_x', ctypes.c_float),
('world2obj_vy_y', ctypes.c_float),
('world2obj_vy_z', ctypes.c_float),
('world2obj_vz_x', ctypes.c_float),
('world2obj_vz_y', ctypes.c_float),
('world2obj_vz_z', ctypes.c_float),
('obj2world_p_x', ctypes.c_float),
('obj2world_p_y', ctypes.c_float),
('obj2world_p_z', ctypes.c_float),
)
class InstanceLeafPart1(ctypes.Structure):
_fields_ = (
('bvh_ptr', ctypes.c_uint64),
('instance_id', ctypes.c_uint32),
('instance_index', ctypes.c_uint32),
('obj2world_vx_x', ctypes.c_float),
('obj2world_vx_y', ctypes.c_float),
('obj2world_vx_z', ctypes.c_float),
('obj2world_vy_x', ctypes.c_float),
('obj2world_vy_y', ctypes.c_float),
('obj2world_vy_z', ctypes.c_float),
('obj2world_vz_x', ctypes.c_float),
('obj2world_vz_y', ctypes.c_float),
('obj2world_vz_z', ctypes.c_float),
('world2obj_p_x', ctypes.c_float),
('world2obj_p_y', ctypes.c_float),
('world2obj_p_z', ctypes.c_float),
)
class AnvInstanceLeaf(ctypes.Structure):
_fields_ = (
('part0', InstanceLeafPart0),
('part1', InstanceLeafPart1),
)
class BVHInterpreter:
def __init__(self, data):
self.enable_64b_rt = False;
self.data = data
self.nodes = []
self.relationships = {}
self.node_counter = 0
def interpret_structure(self, offset, structure):
size = ctypes.sizeof(structure)
if(offset + size > len(self.data)):
raise ValueError(
f"Not enough data to interpret structure: {structure.__name__}, "
f"required {size} bytes but only {len(self.data) - offset} bytes available."
)
buffer = self.data[offset:offset + size]
return structure.from_buffer_copy(buffer)
def parse_bvh(self):
offset = 0
# Interpret the header
header = self.interpret_structure(offset, AnvAccelStructHeader)
self.enable_64b_rt = header.enable_64b_rt
offset += header.rootNodeOffset
# Interpret the rootNode
self.dfs_interpret_node(offset, AnvInternalNode)
output = {
'header': get_header_properties(header),
'nodes': self.nodes,
'relationships': self.relationships
}
return output
def determine_child_structure(self, child_node_type):
if child_node_type == NodeType.NODE_TYPE_MIXED:
return AnvInternalNode
elif child_node_type == NodeType.NODE_TYPE_INSTANCE:
return AnvInstanceLeaf
elif child_node_type == NodeType.NODE_TYPE_QUAD:
return AnvQuadLeafNode
elif child_node_type == NodeType.NODE_TYPE_PROCEDURAL:
return AnvProceduralLeafNode
else:
raise ValueError(f"Unknown node type: {child_node_type}")
def dfs_interpret_node(self, offset, structure):
node = self.interpret_structure(offset, structure)
node_id = self.node_counter;
self.node_counter += 1
if structure == AnvInternalNode:
node_type_str = "AnvInternalNode"
node_properties = get_internal_node_properties(node)
elif structure == AnvInstanceLeaf:
node_type_str = "AnvInstanceLeaf"
node_properties = get_instance_leaf_properties(node, self.enable_64b_rt)
elif structure == AnvQuadLeafNode:
node_type_str = "AnvQuadLeafNode"
node_properties = get_quad_leaf_properties(node)
elif structure == AnvProceduralLeafNode:
node_type_str = "AnvProceduralLeafNode"
node_properties = get_aabb_leaf_properties(node)
else:
raise ValueError(f"Unknown structure type: {structure}")
self.nodes.append({
'id': node_id,
'type': node_type_str,
'properties': node_properties
})
self.relationships[node_id] = []
if node_type_str == "AnvInternalNode":
# DFS its children
children_offset_start = offset + node.child_offset * 64 # this node's position + child_offset
isFatLeaf = True if node.node_type != NodeType.NODE_TYPE_MIXED else False
added_blocks = 0
for i in range(6):
blockIncr = node.child_data[i].blockIncr_and_startPrim & 0x3
child_is_valid = not (node.lower_x[i] & 0x80) or (node.upper_x[i] & 0x80)
if(not child_is_valid):
continue
# now determine the children's type
child_node_type = node.node_type if isFatLeaf else ((node.child_data[i].blockIncr_and_startPrim >> 2) & 0xf)
# find where my child is
child_offset = children_offset_start + 64 * added_blocks
added_blocks += blockIncr
child_node_id = self.dfs_interpret_node(child_offset, self.determine_child_structure(child_node_type))
self.relationships[node_id].append(child_node_id)
return node_id
def main():
with open(sys.argv[1], 'rb') as file1:
data = file1.read()
interpreter = BVHInterpreter(data)
json_output = interpreter.parse_bvh()
with open("bvh_dump.json", 'w') as f:
json.dump(json_output, f, indent=4)
if __name__=="__main__":
main()