scena 1.5.1

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GltfExtensionStatus {
    Supported,
    Degraded,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GltfDecoderPolicy {
    BuiltIn,
    FeatureFlag {
        feature: &'static str,
        crate_name: &'static str,
        license: &'static str,
    },
    External {
        feature: &'static str,
        crate_name: &'static str,
        license: &'static str,
    },
    V1xDeferred,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GltfExtensionDiagnostic {
    extension: String,
    status: GltfExtensionStatus,
    help: &'static str,
    suggested_fix: &'static str,
    decoder_policy: GltfDecoderPolicy,
}

impl GltfExtensionDiagnostic {
    pub fn extension(&self) -> &str {
        &self.extension
    }

    pub const fn status(&self) -> GltfExtensionStatus {
        self.status
    }

    pub const fn help(&self) -> &'static str {
        self.help
    }

    pub const fn suggested_fix(&self) -> &'static str {
        self.suggested_fix
    }

    pub const fn decoder_policy(&self) -> GltfDecoderPolicy {
        self.decoder_policy
    }
}

pub(super) fn is_v1_required_gltf_extension(extension: &str) -> bool {
    let built_in = matches!(
        extension,
        "KHR_lights_punctual"
            | "KHR_materials_unlit"
            | "KHR_materials_emissive_strength"
            | "KHR_texture_transform"
            | "KHR_mesh_quantization"
            | "KHR_materials_variants"
            | "EXT_mesh_gpu_instancing"
    );
    built_in
        || (extension == "KHR_texture_basisu" && cfg!(feature = "ktx2"))
        || (extension == "EXT_meshopt_compression" && cfg!(feature = "meshopt"))
}

pub(super) fn collect_extension_diagnostics(
    extensions_used: &[String],
) -> Vec<GltfExtensionDiagnostic> {
    extensions_used
        .iter()
        .filter(|extension| {
            !is_v1_required_gltf_extension(extension)
                || matches!(
                    extension.as_str(),
                    "KHR_texture_basisu"
                        | "KHR_materials_variants"
                        | "EXT_meshopt_compression"
                        | "EXT_mesh_gpu_instancing"
                )
        })
        .map(|extension| GltfExtensionDiagnostic {
            extension: extension.clone(),
            status: optional_extension_status(extension),
            help: optional_extension_help(extension),
            suggested_fix: optional_extension_suggested_fix(extension),
            decoder_policy: optional_extension_decoder_policy(extension),
        })
        .collect()
}

fn optional_extension_status(extension: &str) -> GltfExtensionStatus {
    match extension {
        "KHR_materials_variants" => GltfExtensionStatus::Supported,
        "EXT_mesh_gpu_instancing" => GltfExtensionStatus::Supported,
        "KHR_texture_basisu" if cfg!(feature = "ktx2") => GltfExtensionStatus::Supported,
        "EXT_meshopt_compression" if cfg!(feature = "meshopt") => GltfExtensionStatus::Supported,
        _ => GltfExtensionStatus::Degraded,
    }
}

fn optional_extension_help(extension: &str) -> &'static str {
    match extension {
        "KHR_materials_clearcoat" => {
            "clearcoat factors and texture slots are parsed, and clearcoat/roughness/normal texture channels are wired through CPU/reference and GPU shader paths; structured degradation remains for required usage until approved backend screenshot or readback proof covers the target lane"
        }
        "KHR_materials_sheen" => {
            "sheen material extension factors and color/roughness texture slots are parsed, and CPU/reference plus GPU shader paths sample the same roles; structured degradation remains for required usage until approved backend screenshot or readback proof covers the target lane"
        }
        "KHR_materials_anisotropy" => {
            "anisotropy material extension strength/rotation factors and direction/strength texture slots are parsed, and CPU/reference plus GPU shader paths sample the same roles; structured degradation remains for required usage until approved backend screenshot or readback proof covers the target lane"
        }
        "KHR_materials_iridescence" => {
            "iridescence material extension factor, IOR, thickness range, and factor/thickness texture slots are parsed, and CPU/reference plus GPU shader paths sample the same roles; structured degradation remains for required usage until approved backend screenshot or readback proof covers the target lane"
        }
        "KHR_materials_dispersion" => {
            "dispersion material extension factor is parsed, and CPU/reference plus GPU shader paths apply a channel-spread specular approximation; structured degradation remains for required usage until approved backend screenshot or readback proof covers the target lane"
        }
        "KHR_materials_transmission" | "KHR_materials_ior" | "KHR_materials_volume" => {
            "transmission, IOR, and volume material extension factors and texture slots are parsed, and CPU/reference shading uses transmission, thickness, and attenuation; structured degradation remains for required usage until approved backend screenshot or readback proof covers the target lane"
        }
        "KHR_materials_specular" => {
            "specular material extension is optional in this glTF and currently uses structured degradation; required usage fails during asset load"
        }
        "KHR_materials_variants" => {
            "material variants are supported for v1.0: top-level variants and per-primitive mappings are parsed into typed runtime variant selection"
        }
        "EXT_mesh_gpu_instancing" => {
            "EXT_mesh_gpu_instancing is parsed into scene-owned InstanceSet nodes using built-in TRS accessors"
        }
        "EXT_texture_webp" => {
            "WebP texture extension is v1.x-deferred; plain .webp image paths are accepted but EXT_texture_webp texture-source rebinding is not implemented"
        }
        "KHR_texture_basisu" => {
            if cfg!(feature = "ktx2") {
                "KTX2/Basis texture loading is decoder-backed by basisu_c_sys; decodable KTX2/Basis bytes become renderer-visible RGBA8 pixels"
            } else {
                "KTX2/Basis texture loading requires the ktx2 feature and currently uses structured degradation; required usage fails during asset load"
            }
        }
        "KHR_draco_mesh_compression" => {
            "Draco mesh compression requires a future decoder feature and currently uses structured degradation; required usage fails during asset load"
        }
        "EXT_meshopt_compression" => {
            if cfg!(feature = "meshopt") {
                "EXT_meshopt_compression is decoder-backed by the meshopt crate before mesh/material access"
            } else {
                "meshopt compression requires the meshopt feature and currently uses structured degradation; required usage fails during asset load"
            }
        }
        _ => {
            "optional glTF extension is not implemented and currently uses structured degradation; required usage fails during asset load"
        }
    }
}

fn optional_extension_suggested_fix(extension: &str) -> &'static str {
    match extension {
        "KHR_materials_variants" | "EXT_mesh_gpu_instancing" => {
            "No action needed for this extension."
        }
        "KHR_texture_basisu" => {
            if cfg!(feature = "ktx2") {
                "No action needed when the ktx2 or production-assets feature is enabled."
            } else {
                "Enable the production-assets or ktx2 feature, or export PNG/JPEG/WebP fallback textures."
            }
        }
        "EXT_meshopt_compression" => {
            if cfg!(feature = "meshopt") {
                "No action needed when the meshopt or production-assets feature is enabled."
            } else {
                "Enable the production-assets or meshopt feature, or export an uncompressed buffer fallback."
            }
        }
        "KHR_draco_mesh_compression" => {
            "Re-export the asset uncompressed or with EXT_meshopt_compression; revisit Draco when a maintained decoder is adopted."
        }
        "KHR_materials_clearcoat" => {
            "Keep KHR_materials_clearcoat optional unless CPU/reference clearcoat is enough, or export a fallback material without clearcoat for required assets that depend on unproven backend parity."
        }
        "KHR_materials_sheen" => {
            "Keep KHR_materials_sheen optional unless CPU/reference sheen is enough, or export a fallback material without sheen for required assets that depend on unproven backend parity."
        }
        "KHR_materials_anisotropy" => {
            "Keep KHR_materials_anisotropy optional unless CPU/reference anisotropy is enough, or export a fallback material without anisotropy for required assets that depend on unproven backend parity."
        }
        "KHR_materials_iridescence" => {
            "Keep KHR_materials_iridescence optional unless CPU/reference iridescence is enough, or export a fallback material without iridescence for required assets that depend on unproven backend parity."
        }
        "KHR_materials_dispersion" => {
            "Keep KHR_materials_dispersion optional unless CPU/reference dispersion is enough, or export a fallback material without dispersion for required assets that depend on unproven backend parity."
        }
        "KHR_materials_transmission" | "KHR_materials_ior" | "KHR_materials_volume" => {
            "Keep the extension optional unless CPU/reference transmission and volume shading is enough, or export a fallback material for required assets that depend on unproven backend parity."
        }
        "KHR_materials_specular" => {
            "Export a fallback material without the extension, or keep the extension optional until the matching renderer feature is supported."
        }
        "EXT_texture_webp" => {
            "Re-export with PNG/JPEG/WebP image URIs outside EXT_texture_webp, or use KTX2 through the ktx2 feature."
        }
        _ => {
            "Make the extension optional with a visual fallback, or add an explicit scena decoder/support policy before relying on it."
        }
    }
}

fn optional_extension_decoder_policy(extension: &str) -> GltfDecoderPolicy {
    match extension {
        "KHR_texture_basisu" => GltfDecoderPolicy::FeatureFlag {
            feature: "ktx2",
            crate_name: "basisu_c_sys",
            license: "MIT OR Apache-2.0",
        },
        "KHR_draco_mesh_compression" => GltfDecoderPolicy::External {
            feature: "draco",
            crate_name: "draco",
            license: "Apache-2.0-compatible decoder required",
        },
        "EXT_meshopt_compression" => GltfDecoderPolicy::FeatureFlag {
            feature: "meshopt",
            crate_name: "meshopt",
            license: "MIT",
        },
        "KHR_materials_variants" => GltfDecoderPolicy::BuiltIn,
        "EXT_mesh_gpu_instancing" => GltfDecoderPolicy::BuiltIn,
        "KHR_materials_clearcoat"
        | "KHR_materials_transmission"
        | "KHR_materials_ior"
        | "KHR_materials_volume"
        | "KHR_materials_sheen"
        | "KHR_materials_specular"
        | "KHR_materials_iridescence"
        | "KHR_materials_anisotropy"
        | "KHR_materials_dispersion"
        | "EXT_texture_webp" => GltfDecoderPolicy::V1xDeferred,
        _ => GltfDecoderPolicy::V1xDeferred,
    }
}