molgfx-render 0.2.1

The render graph, passes and the engine that drives a frame.
Documentation
use super::tests::{camera, engine, structure};
use crate::engine::RenderMode;
use molgfx_core::{LicoriceTemplate, LigandPose, Scene};
use molgfx_math::{Quat, Rgba8, Vec3};

fn pose(index: u32, opacity: f32) -> LigandPose {
    let result = LigandPose::new(
        Vec3::new(index_position(index), 0.0, 0.0),
        Quat::IDENTITY,
        Rgba8::opaque(80, 170, 240),
        opacity,
    );
    let Ok(pose) = result else {
        panic!("valid ligand pose expected");
    };
    pose
}

#[test]
fn realtime_pose_sampling_is_bounded_deterministic_and_opacity_correct() {
    let scene = ligand_scene(10_000, true);
    let mut engine = engine();
    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("compact ligand batch renders: {error}");
    }
    assert!(
        engine.scene_gpu.primitive_groups().is_none(),
        "pose topology must never lower into PrimitiveGpu rows"
    );
    let Some((_, _, groups)) = engine.scene_gpu.ligand_pose_draws() else {
        panic!("compact ligand draws exist");
    };
    assert_eq!(groups.len(), 4, "opaque/translucent spheres and capsules");
    assert_eq!(
        groups
            .iter()
            .map(|group| group.instances)
            .collect::<Vec<_>>(),
        vec![2_730, 1_365, 2_730, 1_365],
        "one batch allocation is split proportionally between opacity classes"
    );
    assert_eq!(
        engine.ligand_pose_stats(),
        super::LigandPoseStats {
            resident_poses: 10_000,
            visible_poses: 10_000,
            selected_poses: 2_730,
            beauty_instances: 8_190,
            shadow_instances: 4_095,
            draw_groups: 6,
        }
    );
    let first_groups = groups.to_vec();
    let before = match engine.device.log.writes.lock() {
        Ok(writes) => writes.len(),
        Err(error) => panic!("write log lock: {error}"),
    };

    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("stable compact ligand batch renders: {error}");
    }
    let Some((_, _, stable_groups)) = engine.scene_gpu.ligand_pose_draws() else {
        panic!("stable compact ligand draws exist");
    };
    assert_eq!(stable_groups, first_groups);
    let Ok(writes) = engine.device.log.writes.lock() else {
        panic!("write log lock");
    };
    assert_eq!(
        writes.len() - before,
        1,
        "stable pose tables add no upload beyond frame uniforms"
    );
    drop(writes);

    let Ok(draws) = engine.device.log.indirect_draws.lock() else {
        panic!("draw log lock");
    };
    assert_eq!(
        draws.len(),
        12,
        "two realtime frames each draw four classes plus two shadow groups"
    );
    assert_eq!(
        draws.iter().map(|(_, offset)| *offset).collect::<Vec<_>>(),
        vec![0, 16, 0, 16, 32, 48, 0, 16, 0, 16, 32, 48],
        "stable frames reuse the same shared argument records"
    );
}

#[test]
fn zero_alpha_poses_use_no_budget_and_fractional_alpha_routes_to_oit() {
    let source = structure();
    let mut scene = match Scene::from_structure(&source) {
        Ok(scene) => scene,
        Err(error) => panic!("fixture scene builds: {error}"),
    };
    let Some((owner, _)) = scene.structures().next() else {
        panic!("fixture owner exists");
    };
    let template = match LicoriceTemplate::new(Vec3::ZERO, vec![Vec3::ZERO, Vec3::X], &[[0, 1]]) {
        Ok(template) => template,
        Err(error) => panic!("ligand template validates: {error}"),
    };
    let poses = vec![
        pose(0, 1.0),
        pose(1, 0.9995),
        pose(2, 0.0),
        colored_pose(3, 0, 1.0),
    ];
    if let Err(error) = scene.add_licorice_poses(owner, template, poses) {
        panic!("ligand batch stores: {error}");
    }
    let mut engine = engine();
    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("compact ligand batch renders: {error}");
    }
    let Some((_, _, groups)) = engine.scene_gpu.ligand_pose_draws() else {
        panic!("compact ligand draws exist");
    };
    assert_eq!(groups.len(), 4);
    assert_eq!(
        groups
            .iter()
            .map(|group| group.instances)
            .collect::<Vec<_>>(),
        vec![2, 1, 2, 1],
        "only one opaque and one fractional-alpha pose reach the GPU draws"
    );
}

#[test]
fn many_small_batches_have_a_fixed_realtime_raster_draw_ceiling() {
    let source = structure();
    let mut scene = match Scene::from_structure(&source) {
        Ok(scene) => scene,
        Err(error) => panic!("fixture scene builds: {error}"),
    };
    let Some((owner, _)) = scene.structures().next() else {
        panic!("fixture owner exists");
    };
    let template = match LicoriceTemplate::new(Vec3::ZERO, vec![Vec3::ZERO, Vec3::X], &[[0, 1]]) {
        Ok(template) => template,
        Err(error) => panic!("ligand template validates: {error}"),
    };
    for index in 0..200 {
        if let Err(error) =
            scene.add_licorice_poses(owner, template.clone(), vec![pose(index, 1.0)])
        {
            panic!("ligand batch stores: {error}");
        }
    }
    let mut engine = engine();
    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("compact ligand batches render: {error}");
    }
    let Some((_, _, groups)) = engine.scene_gpu.ligand_pose_draws() else {
        panic!("compact ligand draws exist");
    };
    assert_eq!(groups.len(), 128, "beauty draw groups are globally capped");
    let Ok(draws) = engine.device.log.indirect_draws.lock() else {
        panic!("draw log lock");
    };
    assert_eq!(
        draws.len(),
        256,
        "opaque shadow repeats stay within the documented raster ceiling"
    );
}

#[test]
fn quality_mode_switch_restores_every_pose_without_rebuilding_the_scene() {
    let scene = ligand_scene(10_000, true);
    let mut engine = engine();
    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("realtime compact ligand batch renders: {error}");
    }
    engine.set_render_mode(RenderMode::Cinematic);
    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("quality compact ligand batch renders: {error}");
    }
    let Some((_, _, groups)) = engine.scene_gpu.ligand_pose_draws() else {
        panic!("quality compact ligand draws exist");
    };
    assert_eq!(
        groups
            .iter()
            .map(|group| u64::from(group.instances))
            .sum::<u64>(),
        30_000,
        "quality keeps all two-atom and one-bond pose instances"
    );
}

#[test]
fn realtime_keeps_a_four_hundred_pose_batch_exact_when_it_fits() {
    let scene = ligand_scene(400, false);
    let mut engine = engine();
    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("compact ligand batch renders: {error}");
    }
    let Some((_, _, groups)) = engine.scene_gpu.ligand_pose_draws() else {
        panic!("compact ligand draws exist");
    };
    assert_eq!(
        groups
            .iter()
            .map(|group| u64::from(group.instances))
            .sum::<u64>(),
        1_200,
    );
    assert_eq!(
        engine.ligand_pose_stats(),
        super::LigandPoseStats {
            resident_poses: 400,
            visible_poses: 400,
            selected_poses: 400,
            beauty_instances: 1_200,
            shadow_instances: 1_200,
            draw_groups: 4,
        }
    );
}

#[test]
fn small_opaque_ligand_batches_cast_grouped_sphere_and_capsule_shadows() {
    let scene = ligand_scene(20, false);
    let mut engine = engine();
    if let Err(error) = engine.render(&scene, &camera()) {
        panic!("compact ligand batch renders: {error}");
    }
    assert!(engine.scene_gpu.ligand_pose_shadow_draws(false).is_some());
    let Ok(draws) = engine.device.log.indirect_draws.lock() else {
        panic!("draw log lock");
    };
    assert_eq!(
        draws.iter().map(|(_, offset)| *offset).collect::<Vec<_>>(),
        vec![0, 16, 0, 16],
        "shadow and beauty each consume one sphere and one capsule range"
    );
}

fn ligand_scene(count: u32, alternating_translucency: bool) -> Scene {
    let source = structure();
    let mut scene = match Scene::from_structure(&source) {
        Ok(scene) => scene,
        Err(error) => panic!("fixture scene builds: {error}"),
    };
    let Some((owner, _)) = scene.structures().next() else {
        panic!("fixture owner exists");
    };
    let template = match LicoriceTemplate::new(Vec3::ZERO, vec![Vec3::ZERO, Vec3::X], &[[0, 1]]) {
        Ok(template) => template,
        Err(error) => panic!("ligand template validates: {error}"),
    };
    let poses = (0..count)
        .map(|index| {
            let opacity = if alternating_translucency && index % 2 != 0 {
                0.5
            } else {
                1.0
            };
            pose(index, opacity)
        })
        .collect();
    if let Err(error) = scene.add_licorice_poses(owner, template, poses) {
        panic!("ligand batch stores: {error}");
    }

    scene
}

fn colored_pose(index: u32, alpha: u8, opacity: f32) -> LigandPose {
    let result = LigandPose::new(
        Vec3::new(index_position(index), 0.0, 0.0),
        Quat::IDENTITY,
        Rgba8::new(80, 170, 240, alpha),
        opacity,
    );
    let Ok(pose) = result else {
        panic!("valid ligand pose expected");
    };
    pose
}

fn index_position(index: u32) -> f32 {
    let Ok(index) = u16::try_from(index) else {
        panic!("test pose index fits u16");
    };
    f32::from(index) * 0.01
}