scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use crate::geometry::{Primitive, Vertex};
use crate::material::Color;
use crate::scene::{ClippingPlane, ClippingPlaneSet, PerspectiveCamera, Scene, Transform, Vec3};

use super::Renderer;

impl Renderer {
    fn set_depth_prepass_enabled_for_test(&mut self, enabled: bool) {
        self.depth_prepass_enabled_for_test = enabled;
    }
}

#[test]
fn depth_prepass_prevents_later_far_triangle_from_overdrawing_near_triangle() {
    let Ok(mut with_prepass) = Renderer::headless_gpu(64, 64) else {
        return;
    };
    let Ok(mut without_prepass) = Renderer::headless_gpu(64, 64) else {
        return;
    };
    without_prepass.set_depth_prepass_enabled_for_test(false);

    let mut prepass_scene = overlapping_triangles_scene();
    let prepass_camera = prepass_scene.active_camera().expect("camera is active");
    with_prepass
        .prepare(&mut prepass_scene)
        .expect("prepass scene prepares");
    with_prepass
        .render(&prepass_scene, prepass_camera)
        .expect("prepass scene renders");
    let prepass_stats = with_prepass.stats();
    let prepass_center = pixel_at(with_prepass.frame_rgba8(), 64, 32, 32);

    let mut no_prepass_scene = overlapping_triangles_scene();
    let no_prepass_camera = no_prepass_scene.active_camera().expect("camera is active");
    without_prepass
        .prepare(&mut no_prepass_scene)
        .expect("no-prepass scene prepares");
    without_prepass
        .render(&no_prepass_scene, no_prepass_camera)
        .expect("no-prepass scene renders");
    let no_prepass_stats = without_prepass.stats();
    let no_prepass_center = pixel_at(without_prepass.frame_rgba8(), 64, 32, 32);

    assert_eq!(prepass_stats.depth_prepass_passes, 1);
    assert_eq!(prepass_stats.depth_prepass_draws, 2);
    assert_eq!(no_prepass_stats.depth_prepass_passes, 0);
    assert_eq!(no_prepass_stats.depth_prepass_draws, 0);
    assert!(
        prepass_center[1] > prepass_center[0],
        "with depth prepass, near green triangle must win at the overlap center: {prepass_center:?}"
    );
    assert!(
        no_prepass_center[0] > no_prepass_center[1],
        "without depth prepass, the later far red triangle demonstrates the overdraw artifact this pass prevents: {no_prepass_center:?}"
    );
}

fn overlapping_triangles_scene() -> Scene {
    let mut scene = Scene::new();
    let camera = scene
        .add_perspective_camera(
            scene.root(),
            PerspectiveCamera::default(),
            Transform::at(Vec3::new(0.0, 0.0, 3.0)),
        )
        .expect("camera inserts");
    scene.set_active_camera(camera).expect("camera activates");
    let keep_all_plane = scene.add_clipping_plane(ClippingPlane::new(Vec3::Z, 10.0));
    scene
        .set_clipping_planes(ClippingPlaneSet::new().with_plane(keep_all_plane))
        .expect("harmless clipping plane activates");
    scene
        .add_renderable(
            scene.root(),
            vec![Primitive::triangle([
                Vertex {
                    position: Vec3::new(-0.5, -0.5, 0.0),
                    color: Color::from_linear_rgb(0.0, 1.0, 0.0),
                },
                Vertex {
                    position: Vec3::new(0.5, -0.5, 0.0),
                    color: Color::from_linear_rgb(0.0, 1.0, 0.0),
                },
                Vertex {
                    position: Vec3::new(0.0, 0.5, 0.0),
                    color: Color::from_linear_rgb(0.0, 1.0, 0.0),
                },
            ])],
            Transform::default(),
        )
        .expect("near triangle inserts first");
    scene
        .add_renderable(
            scene.root(),
            vec![Primitive::triangle([
                Vertex {
                    position: Vec3::new(-0.5, -0.5, -0.5),
                    color: Color::from_linear_rgb(1.0, 0.0, 0.0),
                },
                Vertex {
                    position: Vec3::new(0.5, -0.5, -0.5),
                    color: Color::from_linear_rgb(1.0, 0.0, 0.0),
                },
                Vertex {
                    position: Vec3::new(0.0, 0.5, -0.5),
                    color: Color::from_linear_rgb(1.0, 0.0, 0.0),
                },
            ])],
            Transform::default(),
        )
        .expect("far triangle inserts second");
    scene
}

fn pixel_at(frame: &[u8], width: usize, x: usize, y: usize) -> [u8; 4] {
    let offset = (y * width + x) * 4;
    [
        frame[offset],
        frame[offset + 1],
        frame[offset + 2],
        frame[offset + 3],
    ]
}