awsm-renderer 0.3.1

awsm-renderer
Documentation
{% include "shared_wgsl/vertex/geometry_mesh_meta.wgsl" %}
{% include "shared_wgsl/camera.wgsl" %}
{% include "shared_wgsl/frame_globals.wgsl" %}
{% include "shared_wgsl/vertex/transform.wgsl" %}
{% include "shared_wgsl/vertex/morph.wgsl" %}
{% include "shared_wgsl/vertex/skin.wgsl" %}
{% include "shared_wgsl/vertex/apply_vertex.wgsl" %}


//***** MAIN *****
struct VertexInput {
    @builtin(vertex_index) vertex_index: u32,
    @location(0) position: vec3<f32>,      // Model-space position
    @location(1) triangle_index: u32,      // Triangle index for this vertex
    @location(2) barycentric: vec2<f32>,   // Barycentric coordinates (x, y) - z = 1.0 - x - y
    @location(3) normal: vec3<f32>,        // Model-space normal
    @location(4) tangent: vec4<f32>,       // Model-space tangent (w = handedness)
    @location(5) original_vertex_index: u32, // Original vertex index (for indexed skin/morph access)
    {% if instancing_transforms %}
    // instance transform matrix
    @location(6) instance_transform_row_0: vec4<f32>,
    @location(7) instance_transform_row_1: vec4<f32>,
    @location(8) instance_transform_row_2: vec4<f32>,
    @location(9) instance_transform_row_3: vec4<f32>,
    {% endif %}
};

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) @interpolate(flat) triangle_index: u32,
    @location(1) barycentric: vec2<f32>,  // Full barycentric coordinates
    @location(2) world_normal: vec3<f32>,     // Transformed world-space normal
    @location(3) world_tangent: vec4<f32>,    // Transformed world-space tangent (w = handedness)
    // Stage-1 leaves this at U32_MAX always; Stage-2 wires
    // `geometry_mesh_meta.instance_attr_base + @builtin(instance_index)`.
    @location(4) @interpolate(flat) instance_id: u32,
    // Non-instanced meshes pull `geometry_mesh_meta`
    // from a storage-array binding into a `var<private>` at vertex
    // entry. `var<private>` is per-shader-stage, so the fragment
    // shader's copy is uninitialised — passing the material-meta
    // byte offset as a flat varying gives the fragment access to
    // the right slot's value without re-loading it (which the
    // fragment can't easily do; it doesn't have `instance_index`).
    // For the instanced path the value comes from the uniform
    // binding directly — but since the field is identical across
    // stages there either way, threading it as a varying is the
    // cheaper / more uniform fix.
    @location(5) @interpolate(flat) material_mesh_meta_offset: u32,
}

@vertex
fn vert_main(
    input: VertexInput,
    @builtin(instance_index) instance_index: u32,
) -> VertexOutput {
    var out: VertexOutput;

    {% if meta_storage_array %}
    // Load per-mesh meta from the storage array indexed by
    // `instance_index`. The compaction shader (or CPU
    // draw_indexed_with_first_instance) sets `first_instance =
    // mesh_meta_idx` so `instance_index` lands on this mesh's slot.
    // Only reachable when the device exposes the
    // `indirect-first-instance` WebGPU feature; the portable
    // fallback (uniform-with-dynamic-offset) leaves
    // `geometry_mesh_meta` populated by the bind-group dynamic
    // offset and skips this load.
    geometry_mesh_meta = geometry_mesh_metas[instance_index];
    {% endif %}

    let camera = camera_from_raw(camera_raw);
    let frame_globals = frame_globals_from_raw(frame_globals_raw);

    let applied = apply_vertex(ApplyVertexInput(
        input.original_vertex_index,
        input.position,
        input.normal,
        input.tangent,
        {% if instancing_transforms %}
            input.instance_transform_row_0,
            input.instance_transform_row_1,
            input.instance_transform_row_2,
            input.instance_transform_row_3,
        {% endif %}
    ), camera);

    out.clip_position = applied.clip_position;
    out.world_normal = applied.world_normal;
    out.world_tangent = applied.world_tangent;

    // Pass through
    out.triangle_index = input.triangle_index;
    out.barycentric = input.barycentric;

    // Per-fragment instance_id. The shading compute pass reads this to look
    // up per-instance attributes (color, size, alpha) from a small storage
    // buffer. For non-instanced meshes the writer side stores `u32::MAX` in
    // `geometry_mesh_meta.instance_attr_base`; we propagate that sentinel
    // through so the read site can branch on a single value.
    let base = geometry_mesh_meta.instance_attr_base;
    if (base == 0xFFFFFFFFu) {
        out.instance_id = 0xFFFFFFFFu;
    } else {
        out.instance_id = base + instance_index;
    }

    // Forward the per-mesh material-meta byte
    // offset to the fragment stage so the fragment's
    // visibility_data write resolves to the correct slot. See
    // VertexOutput's docstring for the rationale.
    out.material_mesh_meta_offset = geometry_mesh_meta.material_mesh_meta_offset;

    return out;
}