scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
#[cfg(not(target_arch = "wasm32"))]
use crate::diagnostics::RenderError;

#[cfg(not(target_arch = "wasm32"))]
use super::super::RasterTarget;
#[cfg(not(target_arch = "wasm32"))]
use super::pipeline::{GPU_COLOR_FORMAT, UnlitPipelines};
#[cfg(not(target_arch = "wasm32"))]
use super::{GpuPreparedResources, MsaaColorResources};

#[cfg(not(target_arch = "wasm32"))]
pub(super) fn offscreen_pipelines_for_sample_count(
    resources: &GpuPreparedResources,
    sample_count: u32,
) -> UnlitPipelines<'_> {
    match sample_count {
        4 => resources.offscreen_msaa4_pipelines.refs(),
        8 => resources
            .offscreen_msaa8_pipelines
            .as_ref()
            .expect("offscreen MSAA8 pipelines must be prepared before encoding")
            .refs(),
        _ => resources.offscreen_pipelines.refs(),
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub(super) fn ensure_offscreen_msaa8_pipelines(
    adapter: &wgpu::Adapter,
    device: &wgpu::Device,
    resources: &mut GpuPreparedResources,
    target: RasterTarget,
) -> Result<(), RenderError> {
    if resources.offscreen_msaa8_pipelines.is_some() {
        return Ok(());
    }
    if !texture_format_supports_sample_count(device, adapter, GPU_COLOR_FORMAT, 8) {
        return Err(RenderError::UnsupportedSampleCount {
            backend: target.backend,
            requested: 8,
            maximum: max_supported_sample_count(device, adapter, &[GPU_COLOR_FORMAT]),
        });
    }
    resources.offscreen_msaa8_pipelines = Some(super::pipeline::create_unlit_pipeline_set(
        device,
        GPU_COLOR_FORMAT,
        &resources.output_bind_group_layout,
        &resources.material_bind_group_layout,
        &resources.draw_bind_group_layout,
        resources.texture_binding_mode,
        resources.depth_compare,
        8,
    ));
    Ok(())
}

#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(super) fn max_supported_sample_count(
    device: &wgpu::Device,
    adapter: &wgpu::Adapter,
    formats: &[wgpu::TextureFormat],
) -> u32 {
    [8, 4, 1]
        .into_iter()
        .find(|sample_count| {
            formats.iter().all(|format| {
                texture_format_supports_sample_count(device, adapter, *format, *sample_count)
            })
        })
        .unwrap_or(1)
}

#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(super) fn texture_format_supports_sample_count(
    device: &wgpu::Device,
    adapter: &wgpu::Adapter,
    format: wgpu::TextureFormat,
    sample_count: u32,
) -> bool {
    if sample_count > 4
        && !device
            .features()
            .contains(wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
    {
        return false;
    }
    adapter
        .get_texture_format_features(format)
        .flags
        .sample_count_supported(sample_count)
}

#[cfg(not(target_arch = "wasm32"))]
pub(super) fn ensure_msaa_color_resources(
    device: &wgpu::Device,
    resources: &mut GpuPreparedResources,
    target: RasterTarget,
    format: wgpu::TextureFormat,
    sample_count: u32,
) {
    if resources.msaa_color.as_ref().is_some_and(|msaa| {
        msaa.target == target && msaa.format == format && msaa.sample_count == sample_count
    }) {
        return;
    }
    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some("scena.headless_gpu.msaa_color"),
        size: wgpu::Extent3d {
            width: target.width,
            height: target.height,
            depth_or_array_layers: 1,
        },
        mip_level_count: 1,
        sample_count,
        dimension: wgpu::TextureDimension::D2,
        format,
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
        view_formats: &[],
    });
    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
    resources.msaa_color = Some(MsaaColorResources {
        target,
        format,
        sample_count,
        texture,
        view,
    });
}