nightshade-renderer 0.57.0

GPU-driven wgpu renderer with a built-in frame graph.
#import nightshade_renderer::meshlet_data::{Meshlet, meshlet_payload, get_meshlet_triangle_count}
#import nightshade_renderer::meshlet_streams::{raster_clusters, meshlets, meshlet_instances, get_meshlet_vertex_id, get_meshlet_vertex_position}

/// Rasterizes the clusters too large for compute, or crossing the near plane
/// where clipping is needed, on the fixed function rasterizer.

/// `counts.x` is the capacity of the cluster list, which the hardware list is
/// indexed back from.
struct MeshletView {
    clip_from_world: mat4x4<f32>,
    camera_position: vec4<f32>,
    screen_size: vec4<f32>,
    counts: vec4<u32>,
}

@group(0) @binding(0) var<uniform> view: MeshletView;
#ifdef MESHLET_ATOMIC_VISIBILITY
@group(0) @binding(1) var visibility_buffer: texture_storage_2d<r64uint, atomic>;
#endif

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) @interpolate(flat) packed_ids: u32,
}

/// One draw covers every cluster this path owns. `vertex_index` walks the
/// cluster's index stream three at a time. A cluster shorter than the maximum
/// emits triangles wholly outside clip space, which the rasterizer discards.
///
/// The two rasters share one cluster list and grow from opposite ends, so this
/// path's clusters are counted from the back. The payload carries where the
/// cluster sits in that list rather than this draw's own instance index, because
/// the resolve knows only the list.
@vertex
fn vertex_main(
    @builtin(instance_index) instance_index: u32,
    @builtin(vertex_index) vertex_index: u32,
) -> VertexOutput {
    let cluster_id = view.counts.x - 1u - instance_index;
    let cluster = raster_clusters[cluster_id];
    var meshlet = meshlets[cluster.offset];

    let triangle_id = vertex_index / 3u;
    if triangle_id >= get_meshlet_triangle_count(&meshlet) {
        return VertexOutput(vec4<f32>(2.0, 2.0, 2.0, 1.0), 0u);
    }

    let vertex_id = get_meshlet_vertex_id(meshlet.start_index_id + vertex_index);
    let local_position = get_meshlet_vertex_position(&meshlet, vertex_id);

    let instance = meshlet_instances[cluster.instance_id];
    let world_position = instance.world_from_local * vec4<f32>(local_position, 1.0);
    let clip_position = view.clip_from_world * world_position;

    return VertexOutput(clip_position, meshlet_payload(cluster_id, triangle_id));
}

#ifdef MESHLET_ATOMIC_VISIBILITY
/// Writes through the same atomic the compute rasterizer uses, so the two paths
/// settle against each other with no depth attachment between them: depth in the
/// high half means the max is the depth test.
///
/// An occluded fragment still writes, because a shader with side effects cannot
/// be depth tested before it runs. The resolve tests depth against the scene
/// again before shading, which is what makes that harmless.
@fragment
fn fragment_main(vertex_output: VertexOutput) {
    let depth_bits = bitcast<u32>(vertex_output.clip_position.z);
    if depth_bits == 0u {
        return;
    }
    let packed = (u64(depth_bits) << 32u) | u64(vertex_output.packed_ids);
    textureAtomicMax(
        visibility_buffer,
        vec2<i32>(vertex_output.clip_position.xy),
        packed,
    );
}
#else
@fragment
fn fragment_main(vertex_output: VertexOutput) -> @location(0) u32 {
    return vertex_output.packed_ids;
}
#endif