blob: 1b1878b03ee480b67cf318dbd46eb7e70116711f [file] [edit]
/* Copyright © 2022 Friedrich Vock
* Copyright © 2024 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#version 460
layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
#include "anv_bvh_defines.h"
#define VK_USED_BUILD_FLAGS ANV_ENCODE_BUILD_FLAGS
#include "anv_bvh_helpers.h"
#include "encode.h"
#define READY_TO_WRITE(offset) ((offset) < VK_NULL_BVH_OFFSET)
#define ASSIGNED_NODE_TO_ENCODE ((gl_GlobalInvocationID.x + push_args.start_node_offset) \
< DEREF(args.header).ir_internal_node_count)
/* Debugging helper: disable encoding by exiting early. Ensure compiler doesn't
* dead code eliminate by comparing to value that should never evaluate as true.
*/
#define DEBUG_DISABLE_WRITE 0
#define DEBUG_EXIT_EARLY(val) (DEBUG_DISABLE_WRITE == 1) && ((val) != 123456)
/* IR_NODE refers to memory that holds IR NODEs which are to be encoded. */
#define IR_NODE uint32_t
#define NODE_OFFSET(node) (OFFSET(args.intermediate_bvh, ir_id_to_offset(node)))
layout(push_constant) uniform CONSTS {
encode_args push_args;
};
anv_batch_args args = DEREF(INDEX(anv_batch_args, push_args.batch_args,
push_args.batch_offset + gl_GlobalInvocationID.y));
void
debug_dump(uint32_t offset, uint32_t value)
{
REF(uint32_t) msg = REF(uint32_t)(OFFSET(args.output_bvh, offset));
DEREF(msg) = value;
}
struct anv_cluster {
/* simd lane inside cluster: 0 .. 7 */
uint32_t idx;
/* ID of cluster: 0 .. globalInvocations.x/8-1 */
uint32_t cluster_id;
/* size = 8 */
uint32_t size;
};
/* cluster_size has to be a power of two and <32. */
void
anv_cluster_init(out anv_cluster cluster, uint32_t size)
{
cluster.idx = gl_SubgroupInvocationID & (size - 1);
cluster.cluster_id = gl_SubgroupInvocationID / size;
cluster.size = size;
}
#define anv_shuffle(cluster, cluster_idx, value) \
subgroupShuffle(value, (gl_SubgroupInvocationID & (~(cluster.size - 1))) + cluster_idx)
void
encode_leaf_node(uint32_t type, IR_NODE child, uint64_t dst_node, REF(anv_accel_struct_header) dst_header)
{
uint64_t src_node = NODE_OFFSET(child);
if (DEBUG_EXIT_EARLY(type))
return;
switch (type) {
case vk_ir_node_triangle: {
vk_ir_triangle_node src = DEREF(REF(vk_ir_triangle_node)(src_node));
if (VK_TEST_BUILD_FLAG_HAS_QUADS) {
vk_ir_triangle_node_quad quad =
vk_ir_triangle_node_get_quad(REF(vk_ir_triangle_node)(src_node));
if (quad.triangle_id == VK_QUAD_TRIANGLE_ID_UNUSED) {
anv_encode_triangle(dst_node, src);
} else {
anv_encode_quad(dst_node, src, quad);
}
break;
}
anv_encode_triangle(dst_node, src);
break;
}
case vk_ir_node_aabb: {
vk_ir_aabb_node src = DEREF(REF(vk_ir_aabb_node)(src_node));
anv_encode_aabb(dst_node, src);
break;
}
case vk_ir_node_instance: {
vk_ir_instance_node src = DEREF(REF(vk_ir_instance_node)(src_node));
anv_encode_instance(dst_node, src);
uint64_t instance_leaves_addr_base = args.instance_leaves_addr;
uint64_t slot = ir_id_to_offset(child) / SIZEOF(vk_ir_instance_node);
DEREF(INDEX(uint64_t, instance_leaves_addr_base, slot)) = dst_node;
break;
}
}
}
/* Determine the node_type based on type of its children.
* If children are all the same leaves, this internal node is a fat leaf;
* Otherwise, it's a mixed node.
*/
uint8_t
determine_internal_node_type(anv_cluster cluster, uint32_t child, uint child_count)
{
if (child_count == 0)
return uint8_t(ANV_NODE_TYPE_INVALID);
uint32_t type = ir_id_to_type(child);
uint32_t first_type_of_child = subgroupClusteredMin(type, 8);
uint32_t second_type_of_child = subgroupClusteredMax(type, 8);
if (first_type_of_child != second_type_of_child)
return uint8_t(ANV_NODE_TYPE_MIXED);
/* All children have same type. Now check what type they are. */
switch (first_type_of_child){
case vk_ir_node_triangle:
return uint8_t(ANV_NODE_TYPE_QUAD);
case vk_ir_node_aabb:
return uint8_t(ANV_NODE_TYPE_PROCEDURAL);
case vk_ir_node_instance:
return uint8_t(ANV_NODE_TYPE_INSTANCE);
case vk_ir_node_internal:
return uint8_t(ANV_NODE_TYPE_MIXED);
default:
return uint8_t(ANV_NODE_TYPE_INVALID);
}
}
vk_aabb
quantize_bounds(vk_aabb aabb, vec3 base, i8vec3 exp)
{
vk_aabb quant_aabb;
vec3 lower = aabb.min - base;
vec3 upper = aabb.max - base;
vec3 qlower = ldexp(lower, -exp + 8);
vec3 qupper = ldexp(upper, -exp + 8);
qlower = min(max(floor(qlower), vec3(0.0)), vec3(255.0));
qupper = min(max(ceil(qupper), vec3(0.0)), vec3(255.0));
quant_aabb.min = qlower;
quant_aabb.max = qupper;
return quant_aabb;
}
void
encode_internal_node(uint32_t child, uint32_t child_block_offset_from_internal_node,
uint child_count, vk_aabb child_aabb, uint32_t bvh_block_offset,
anv_cluster cluster)
{
if (DEBUG_EXIT_EARLY(child_count))
return;
REF(anv_internal_node) dst_node =
REF(anv_internal_node)(OFFSET(args.output_bvh, ANV_RT_BLOCK_SIZE * bvh_block_offset));
bool valid_leaf = cluster.idx < child_count;
uint8_t block_incr_and_start_prim = uint8_t(0);
if (valid_leaf) {
vk_aabb box;
box.min = subgroupClusteredMin(child_aabb.min, 8);
box.max = subgroupClusteredMax(child_aabb.max, 8);
vk_aabb conservative_child_aabb = conservative_aabb(box);
float up = 1.0 + ULP;
ivec3 exp;
vec3 len = aabb_size(conservative_child_aabb) * up;
vec3 mant = frexp(len, exp);
exp.x += int((mant.x > (255.0f / 256.0f)));
exp.y += int((mant.y > (255.0f / 256.0f)));
exp.z += int((mant.z > (255.0f / 256.0f)));
i8vec3 exponent_i8 = i8vec3(exp);
i8vec3 exp_i8 = {max(int8_t(-128), exponent_i8.x),
max(int8_t(-128), exponent_i8.y),
max(int8_t(-128), exponent_i8.z)};
uint8_t node_type = determine_internal_node_type(cluster, child, child_count);
if (cluster.idx == 0) {
DEREF(dst_node).child_block_offset = child_block_offset_from_internal_node;
DEREF(dst_node).lower[0] = conservative_child_aabb.min.x;
DEREF(dst_node).lower[1] = conservative_child_aabb.min.y;
DEREF(dst_node).lower[2] = conservative_child_aabb.min.z;
DEREF(dst_node).exp_x = exp_i8[0];
DEREF(dst_node).exp_y = exp_i8[1];
DEREF(dst_node).exp_z = exp_i8[2];
DEREF(dst_node).node_mask = uint8_t(0xff);
DEREF(dst_node).node_type = node_type;
DEREF(dst_node).pad = uint8_t(0);
if (ANV_TEST_BUILD_FLAG_WRITE_LOOKUP_MAPS_FOR_UPDATE &&
args.parent_child_count_map != 0) {
DEREF(INDEX(uint8_t, args.parent_child_count_map, bvh_block_offset)) =
uint8_t(child_count);
}
}
child_aabb = conservative_aabb(child_aabb);
vk_aabb quantize_aabb = quantize_bounds(child_aabb, conservative_child_aabb.min, exp_i8);
DEREF(dst_node).lower_x[cluster.idx] = uint8_t(quantize_aabb.min.x);
DEREF(dst_node).lower_y[cluster.idx] = uint8_t(quantize_aabb.min.y);
DEREF(dst_node).lower_z[cluster.idx] = uint8_t(quantize_aabb.min.z);
DEREF(dst_node).upper_x[cluster.idx] = uint8_t(quantize_aabb.max.x);
DEREF(dst_node).upper_y[cluster.idx] = uint8_t(quantize_aabb.max.y);
DEREF(dst_node).upper_z[cluster.idx] = uint8_t(quantize_aabb.max.z);
/* blockIncr and child_block_offset are how HW used to find children during traversal.
* If not set properly, gpu could hang.
*/
uint32_t type = ir_id_to_type(child);
block_incr_and_start_prim = type == vk_ir_node_instance ? uint8_t(2) : uint8_t(1);
/* for a mixed node, encode type of each children in startPrim in childdata */
if (node_type == uint8_t(ANV_NODE_TYPE_MIXED))
switch (type) {
case vk_ir_node_triangle:
block_incr_and_start_prim |= uint8_t(ANV_NODE_TYPE_QUAD) << 2;
break;
case vk_ir_node_aabb:
block_incr_and_start_prim |= uint8_t(ANV_NODE_TYPE_PROCEDURAL) << 2;
break;
case vk_ir_node_instance:
block_incr_and_start_prim |= uint8_t(ANV_NODE_TYPE_INSTANCE) << 2;
break;
case vk_ir_node_internal:
block_incr_and_start_prim |= uint8_t(ANV_NODE_TYPE_MIXED) << 2;
break;
default:
block_incr_and_start_prim |= uint8_t(ANV_NODE_TYPE_INVALID) << 2;
break;
}
} else {
DEREF(dst_node).lower_x[cluster.idx] = uint8_t(0x80);
DEREF(dst_node).lower_y[cluster.idx] = uint8_t(0);
DEREF(dst_node).lower_z[cluster.idx] = uint8_t(0);
DEREF(dst_node).upper_x[cluster.idx] = uint8_t(0);
DEREF(dst_node).upper_y[cluster.idx] = uint8_t(0);
DEREF(dst_node).upper_z[cluster.idx] = uint8_t(0);
}
DEREF(dst_node).data[cluster.idx].block_incr_and_start_prim = block_incr_and_start_prim;
}
/* Collapse nodes until reaching 6 children, which typically can be
* 5 internal nodes, or run out of nodes to collapse which often
* happens at tips of tree. Tree is collapsed in direction of
* largest surface areas, resulting in a quality bvh tree.
*
* Early find_children phase defers node collapse as it's a
* speculative phase. Nodes are collapsed only if parent node
* is found to be real (not collapsed node.)
*/
uint32_t
find_children(vk_ir_box_node src, inout uint32_t children[6],
inout uint32_t collapsed_nodes[6],
out uint32_t collapsed_child_count, bool defer_collapse)
{
uint32_t found_child_count = 0;
collapsed_child_count = 0;
/* Initial node can have at most two children */
for (uint32_t i = 0; i < 2; ++i)
if (src.children[i] != VK_BVH_INVALID_NODE)
children[found_child_count++] = src.children[i];
/* For this node, try to collapse binary to 6-ary children */
while (found_child_count < 6) {
/* find vk_ir_node_internal children with largest surface areas */
int32_t collapsed_child_index = -1;
float largest_surface_area = -INFINITY;
for (int32_t i = 0; i < found_child_count; ++i) {
/* Only collapse internal nodes, not leaf nodes. */
if (ir_id_to_type(children[i]) != vk_ir_node_internal)
continue;
vk_aabb bounds = DEREF(REF(vk_ir_node)NODE_OFFSET(children[i])).aabb;
float surface_area = aabb_surface_area(bounds);
if (surface_area > largest_surface_area) {
largest_surface_area = surface_area;
collapsed_child_index = i;
}
}
if (collapsed_child_index != -1) {
/* If deferred, save nodes to collapse later */
if (defer_collapse && collapsed_child_count < 6)
collapsed_nodes[collapsed_child_count] =
ir_id_to_offset(children[collapsed_child_index]);
collapsed_child_count++;
/* Once I found a good vk_ir_node_internal child, try to connect myself
* to this child's children, i.e. my grandchildren. Grandchildren can be
* internal nodes or leaves.
*/
REF(vk_ir_box_node) child_node =
REF(vk_ir_box_node)NODE_OFFSET(children[collapsed_child_index]);
IR_NODE grandchildren[2] = DEREF(child_node).children;
uint32_t valid_grandchild_count = 0;
if (grandchildren[1] != VK_BVH_INVALID_NODE)
++valid_grandchild_count;
if (grandchildren[0] != VK_BVH_INVALID_NODE)
++valid_grandchild_count;
else
grandchildren[0] = grandchildren[1];
/* Grandchild now becomes my direct child, and can possibly be collapsed
* in the next iteration if found_child_count has not reached 6.
*/
if (valid_grandchild_count > 1)
children[found_child_count++] = grandchildren[1];
if (valid_grandchild_count > 0)
children[collapsed_child_index] = grandchildren[0];
else {
/* This child doesn't have valid children, then I don't consider this
* child as my child anymore. This is possible depending on how and
* when lbvh/ploc algorithm marks a node as VK_BVH_INVALID_NODE.
*/
found_child_count--;
children[collapsed_child_index] = children[found_child_count];
}
if (!defer_collapse)
DEREF(child_node).bvh_offset = VK_NULL_BVH_OFFSET;
} else
break;
}
return found_child_count;
}
void
main()
{
/* Each lane will process one vk_ir_node_internal. The root node is sitting
* at the end of the IR BVH, and we let the lane with
* gl_GlobalInvocationID.x == 0 to take care of it.
*/
uint32_t global_id = DEREF(args.header).ir_internal_node_count -
(gl_GlobalInvocationID.x + push_args.start_node_offset + 1);
uint32_t intermediate_leaf_node_size = vk_ir_node_size(args.geometry_type);
/* Each invocation cluster encodes one internal node. */
anv_cluster cluster;
anv_cluster_init(cluster, 8);
uint32_t intermediate_leaf_nodes_size =
args.leaf_node_count * intermediate_leaf_node_size;
REF(vk_ir_box_node) intermediate_internal_nodes =
REF(vk_ir_box_node)OFFSET(args.intermediate_bvh,
intermediate_leaf_nodes_size);
REF(vk_ir_box_node) src_node =
INDEX(vk_ir_box_node, intermediate_internal_nodes, global_id);
vk_ir_box_node src = DEREF(src_node);
bool is_root_node = (gl_GlobalInvocationID.x + push_args.start_node_offset) == 0;
REF(anv_accel_struct_header) header =
REF(anv_accel_struct_header)(args.output_bvh - ANV_RT_BVH_HEADER_SIZE);
if (is_root_node) {
/* Tracks BLOCK where the next children should be encoded. */
DEREF(args.header).dst_node_offset = 1;
DEREF(header).instance_count = 0;
DEREF(header).root_flags = src.flags;
if (ANV_TEST_BUILD_FLAG_WRITE_LOOKUP_MAPS_FOR_UPDATE && args.parent_child_map != 0) {
DEREF(INDEX(uint32_t, args.parent_child_map, 0)) = VK_NULL_BVH_OFFSET;
}
}
IR_NODE children[6] = {VK_BVH_INVALID_NODE, VK_BVH_INVALID_NODE,
VK_BVH_INVALID_NODE, VK_BVH_INVALID_NODE,
VK_BVH_INVALID_NODE, VK_BVH_INVALID_NODE};
uint32_t collapsed_nodes[6];
uint32_t collapsed_child_count;
uint32_t found_child_count;
uint32_t num_blocks_to_add;
/* Every simd lane is assigned an IR BVH internal node to encode. Since
* we are collapsing a binary tree into a hex tree, most simd lanes will
* never need to encode.
*
* To increase performance, have all IR BVH speculatively calculate which
* nodes they would collapse. Most of this work will be thrown away since
* over half the IR internal nodes never get written, but reduces latency.
*/
if (ASSIGNED_NODE_TO_ENCODE) {
found_child_count = find_children(src, children, collapsed_nodes,
collapsed_child_count, true);
/* Count the number of instance children found. For each one found,
* it contributes to 2 blocks to dst_node_offset
*/
num_blocks_to_add = 0;
for (uint32_t i = 0; i < found_child_count; ++i) {
uint32_t type = ir_id_to_type(children[i]);
num_blocks_to_add += (type == vk_ir_node_instance) ? 2 : 1;
}
}
BLOCK bvh_block_offset = (is_root_node) ? 0 :
(ASSIGNED_NODE_TO_ENCODE ? VK_UNKNOWN_BVH_OFFSET
: VK_NULL_BVH_OFFSET);
/* For all but the root internal node, nodes wait until their parent node
* informs them whether they are a valid child (valid bvh offset written)
* or were collapsed (VK_NULL_BVH_OFFSET written) and have no work to do.
*/
for (;;) {
/* Make changes to the current node's BVH offset value visible. */
memoryBarrier(gl_ScopeDevice, gl_StorageSemanticsBuffer,
gl_SemanticsAcquire | gl_SemanticsMakeVisible);
/* Indicate where this internal node should be encoded. Offset measured
* in number of 64B blocks and started from output_bvh.
*/
if (bvh_block_offset == VK_UNKNOWN_BVH_OFFSET)
bvh_block_offset = DEREF(src_node).bvh_offset;
/* The invocation that processes this node is spinning, since its parent
* hasn't told it bvh_offset
*/
BLOCK first_child_block;
if (READY_TO_WRITE(bvh_block_offset)) {
/* Used for finding where to encode children. Also, update dst_node_offset
* so other invocations know where to start encoding
*/
first_child_block =
atomicAdd(DEREF(args.header).dst_node_offset, num_blocks_to_add);
/* Yes, we are potentially calling find_children again here. This is to
* handle an edge case where some bvh trees have nodes with only a single
* child. This can potentially lead to lots of nodes needing to be
* collapsed, overflowing the 6-element buffer allocated. To handle these
* rare cases we find the children again, immediately collapsing them as
* we find them.
*/
if (collapsed_child_count > 6)
find_children(src, children, collapsed_nodes, collapsed_child_count, false);
BLOCK child_offset = first_child_block;
for (uint32_t i = 0; i < found_child_count; ++i) {
/* Retrieve type and location of the child from IR BVH */
uint32_t type = ir_id_to_type(children[i]);
if (type == vk_ir_node_internal) {
REF(vk_ir_box_node) child_node =
REF(vk_ir_box_node)NODE_OFFSET(children[i]);
DEREF(child_node).bvh_offset = child_offset;
}
child_offset += (type == vk_ir_node_instance) ? 2 : 1;
}
/* Mark this collapsed internal node as NULL,
* so whichever lane that would have processed it will return.
*/
for (uint32_t i = 0; i < collapsed_child_count; i++) {
REF(vk_ir_box_node) child_node =
REF(vk_ir_box_node)OFFSET(args.intermediate_bvh, collapsed_nodes[i]);
DEREF(child_node).bvh_offset = VK_NULL_BVH_OFFSET;
}
/* Make changes to the children's BVH offset value available to child threads. */
memoryBarrier(gl_ScopeDevice, gl_StorageSemanticsBuffer,
gl_SemanticsRelease | gl_SemanticsMakeAvailable);
}
/* While all work prior was performed with each simd lane working on
* separate nodes, encoding is achieved with all simd lanes in cluster
* working together on same internal node. Walk through each ready node
* and encode in concert.
*/
while (READY_TO_WRITE(subgroupClusteredMin(bvh_block_offset, 8))) {
/* Select next ready simd lane to write */
uint32_t idx = (READY_TO_WRITE(bvh_block_offset)) ? cluster.idx : -1;
idx = subgroupClusteredMin(idx, 8);
/* Propagate src child and dest blocks of next simd lane to other lanes */
IR_NODE child = VK_BVH_INVALID_NODE;
BLOCK child_block = anv_shuffle(cluster, idx, first_child_block);
BLOCK internal_node_block = anv_shuffle(cluster, idx, bvh_block_offset);
vk_aabb child_aabb = {vec3(INFINITY), vec3(-INFINITY)};
bvh_block_offset = (cluster.idx == idx) ? VK_NULL_BVH_OFFSET
: bvh_block_offset;
if (cluster.idx >= 6)
continue;
if (cluster.idx < anv_shuffle(cluster, idx, found_child_count)) {
for (uint32_t i = 0; ; i++) {
child = anv_shuffle(cluster, idx, children[i]);
if (i == cluster.idx)
break;
/* Invalid node should not contribute to child_block count */
if (child == VK_BVH_INVALID_NODE)
continue;
uint32_t type = ir_id_to_type(child);
child_block += (type == vk_ir_node_instance) ? 2 : 1;
}
if (child != VK_BVH_INVALID_NODE)
child_aabb = DEREF(REF(vk_ir_node)NODE_OFFSET(child)).aabb;
uint32_t type = ir_id_to_type(child);
if (ANV_TEST_BUILD_FLAG_WRITE_LOOKUP_MAPS_FOR_UPDATE &&
child != VK_BVH_INVALID_NODE &&
args.parent_child_map != 0 &&
args.leaf_block_offset_map != 0) {
if (type == vk_ir_node_triangle || type == vk_ir_node_aabb) {
uint32_t ir_offset = ir_id_to_offset(child);
uint32_t leaf_id = ir_offset / intermediate_leaf_node_size;
/* Block offset 0 is assigned to root, so avoid accidental
* assignment.
*/
DEREF(INDEX(uint32_t, args.leaf_block_offset_map, leaf_id)) =
(child_block != 0) ? child_block : VK_NULL_BVH_OFFSET;
if (VK_TEST_BUILD_FLAG_HAS_QUADS && type == vk_ir_node_triangle) {
vk_ir_triangle_node src = DEREF(REF(vk_ir_triangle_node)NODE_OFFSET(child));
vk_ir_triangle_node_quad quad =
vk_ir_triangle_node_get_quad(REF(vk_ir_triangle_node)NODE_OFFSET(child));
if (quad.triangle_id != VK_QUAD_TRIANGLE_ID_UNUSED) {
uint32_t merge_id = quad.triangle_id & 0x0fffffff;
uint32_t merge_leaf_id = leaf_id - src.triangle_id + merge_id;
DEREF(INDEX(uint32_t, args.leaf_block_offset_map, merge_leaf_id)) =
VK_NULL_BVH_OFFSET;
}
}
}
/* Track each children's parent in the map. */
if (type != vk_ir_node_instance && child_block != 0) {
uint32_t pcm = 0;
pcm = internal_node_block | (cluster.idx << 26);
DEREF(INDEX(uint32_t, args.parent_child_map, child_block)) = pcm;
}
}
if (child != VK_BVH_INVALID_NODE && type != vk_ir_node_internal) {
encode_leaf_node(type, child, BLOCK_OFFSET(child_block),
header);
}
}
BLOCK child_block_offset =
anv_shuffle(cluster, 0, child_block) - internal_node_block;
encode_internal_node(child, child_block_offset,
anv_shuffle(cluster, idx, found_child_count),
child_aabb, internal_node_block, cluster);
}
uint32_t is_done = (bvh_block_offset == VK_NULL_BVH_OFFSET) ? 1 : 0;
if (subgroupClusteredAdd(is_done, 8) == 8)
break;
}
if (is_root_node) {
DEREF(header).aabb = src.base.aabb;
}
}