nightshade 0.23.0

A cross-platform data-oriented game engine.
Documentation
#import nightshade::cull_common::{DrawIndexedIndirect, sphere_in_frustum}

struct ShadowOccluder {
    transform_index: u32,
    mesh_geo_id: u32,
    batch_id: u32,
    _pad: u32,
};

struct MeshBounds {
    center: vec3<f32>,
    radius: f32,
};

struct CullUniforms {
    frustum_planes: array<vec4<f32>, 6>,
    occluder_count: u32,
    indirect_offset: u32,
    _pad0: u32,
    _pad1: u32,
};

@group(0) @binding(0) var<storage, read> occluders: array<ShadowOccluder>;
@group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
@group(0) @binding(2) var<storage, read> mesh_bounds: array<MeshBounds>;
@group(0) @binding(3) var<storage, read_write> indirect_commands: array<DrawIndexedIndirect>;
@group(0) @binding(4) var<storage, read_write> visible_indices: array<u32>;
@group(0) @binding(5) var<uniform> cull: CullUniforms;

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let occluder_index = global_id.x;
    if occluder_index >= cull.occluder_count {
        return;
    }

    let occluder = occluders[occluder_index];
    let bounds = mesh_bounds[occluder.mesh_geo_id];
    let model = transforms[occluder.transform_index];

    let world_center = (model * vec4<f32>(bounds.center, 1.0)).xyz;
    let scale_x = length(model[0].xyz);
    let scale_y = length(model[1].xyz);
    let scale_z = length(model[2].xyz);
    let world_radius = bounds.radius * max(scale_x, max(scale_y, scale_z));

    if !sphere_in_frustum(cull.frustum_planes, world_center, world_radius) {
        return;
    }

    let command_index = cull.indirect_offset + occluder.batch_id;
    let slot = atomicAdd(&indirect_commands[command_index].instance_count, 1u);
    let first_instance = indirect_commands[command_index].first_instance;
    visible_indices[first_instance + slot] = occluder.transform_index;
}