awsm-renderer 0.3.3

awsm-renderer
Documentation
//! Shader templates for the geometry pass.

use askama::Template;

use crate::{
    render_passes::geometry::shader::cache_key::ShaderCacheKeyGeometry,
    shaders::{AwsmShaderError, Result},
};

/// Geometry pass shader template components.
#[derive(Debug)]
pub struct ShaderTemplateGeometry {
    pub bind_groups: ShaderTemplateGeometryBindGroups,
    pub vertex: ShaderTemplateGeometryVertex,
    pub fragment: ShaderTemplateGeometryFragment,
}

/// Bind group template for the geometry pass.
#[derive(Template, Debug)]
#[template(path = "geometry_wgsl/bind_groups.wgsl", whitespace = "minimize")]
pub struct ShaderTemplateGeometryBindGroups {
    /// `true` when `@group(2) @binding(0)` is a `storage, read` array
    /// of `GeometryMeshMeta` indexed by `@builtin(instance_index)`
    /// (requires the `indirect-first-instance` WebGPU feature on the
    /// device). `false` when it's a uniform with dynamic offset (the
    /// portable shape; also the only shape used by the instanced
    /// path).
    meta_storage_array: bool,
}

impl ShaderTemplateGeometryBindGroups {
    /// Creates a bind group template from the cache key.
    pub fn new(cache_key: &ShaderCacheKeyGeometry) -> Self {
        Self {
            meta_storage_array: cache_key.meta_storage_array,
        }
    }
}

/// Vertex shader template for the geometry pass.
#[derive(Template, Debug)]
#[template(path = "geometry_wgsl/vertex.wgsl", whitespace = "minimize")]
pub struct ShaderTemplateGeometryVertex {
    max_morph_unroll: u32,
    max_skin_unroll: u32,
    instancing_transforms: bool,
    /// Same flag as on [`ShaderTemplateGeometryBindGroups`] — when
    /// true, the vertex `main` loads `geometry_mesh_meta` from the
    /// storage array via `@builtin(instance_index)`. When false, the
    /// uniform binding is already populated (set via dynamic offset
    /// by the CPU) and no shader-side load is needed.
    meta_storage_array: bool,
    /// `true` for a per-material custom-vertex pipeline — renders the gated
    /// `custom_displace_vertex` hook (the struct/loader/body fields below feed
    /// it). Driven by the cache key's `dynamic_vertex_shader`; `false` (the
    /// default for every shared-pipeline mesh) keeps the output byte-identical.
    has_custom_vertex: bool,
    /// The author's WGSL displacement body, wrapped into
    /// `custom_displace_vertex` at render time. Empty unless `has_custom_vertex`.
    dynamic_wgsl_vertex: String,
    /// Auto-generated `struct MaterialData { ... }` decl for the hook.
    /// Empty unless `has_custom_vertex`.
    dynamic_vertex_struct_decl: String,
    /// Auto-generated `material_data_load` accessor for the hook.
    /// Empty unless `has_custom_vertex`.
    dynamic_vertex_loader_decl: String,
}

impl ShaderTemplateGeometryVertex {
    /// Creates a vertex shader template from the cache key.
    pub fn new(cache_key: &ShaderCacheKeyGeometry) -> Self {
        let dv = cache_key.dynamic_vertex_shader.as_ref();
        Self {
            max_morph_unroll: 2,
            max_skin_unroll: 2,
            instancing_transforms: cache_key.instancing_transforms,
            meta_storage_array: cache_key.meta_storage_array,
            has_custom_vertex: dv.is_some(),
            dynamic_wgsl_vertex: dv.map(|d| d.wgsl_vertex.clone()).unwrap_or_default(),
            dynamic_vertex_struct_decl: dv.map(|d| d.struct_decl.clone()).unwrap_or_default(),
            dynamic_vertex_loader_decl: dv.map(|d| d.loader_decl.clone()).unwrap_or_default(),
        }
    }
}

/// Fragment shader template for the geometry pass.
#[derive(Template, Debug)]
#[template(path = "geometry_wgsl/fragment.wgsl", whitespace = "minimize")]
pub struct ShaderTemplateGeometryFragment {}

impl ShaderTemplateGeometryFragment {
    /// Creates a fragment shader template from the cache key.
    pub fn new(_cache_key: &ShaderCacheKeyGeometry) -> Self {
        Self {}
    }
}

impl TryFrom<&ShaderCacheKeyGeometry> for ShaderTemplateGeometry {
    type Error = AwsmShaderError;

    fn try_from(value: &ShaderCacheKeyGeometry) -> Result<Self> {
        Ok(Self {
            bind_groups: ShaderTemplateGeometryBindGroups::new(value),
            vertex: ShaderTemplateGeometryVertex::new(value),
            fragment: ShaderTemplateGeometryFragment::new(value),
        })
    }
}

impl ShaderTemplateGeometry {
    /// Renders the geometry shader template into WGSL.
    pub fn into_source(self) -> Result<String> {
        let bind_groups_source = self.bind_groups.render()?;
        let vertex_source = self.vertex.render()?;
        let fragment_source = self.fragment.render()?;
        let source = format!(
            "{}\n{}\n{}",
            bind_groups_source, vertex_source, fragment_source
        );

        // print_shader_source(&vertex_source, false);
        //print_shader_source(&source, false);

        Ok(source)
    }

    /// Returns an optional debug label for shader compilation.
    pub fn debug_label(&self) -> Option<&str> {
        Some("Geometry")
    }
}