molgfx-render 0.1.2

The render graph, passes and the engine that drives a frame.
Documentation
use super::chunk_residency_tests::{engine, engine_with_derived_budget};
use super::generic_chunk_residency_tests::{
    buffer_count, fixture, relation_geometry_write_count, request_deliver_upload,
};
use super::{
    ChunkPlacementId, InstanceChunkPlacement, InstanceChunkWindow, RelationChunkPlacement,
};
use molgfx_core::{
    AnalyticSphere, AnalyticTemplate, ChunkEntityRef, ChunkPayload, ChunkSpatialKind,
    InstanceChunkPayload, LogicalRow, PagedRelation, PagedSpatialAnchor, PayloadKind,
    RelationChunkPayload, RelationStyle, ResidencyOutput, RigidInstance, SourceNamespace,
    SourceRows,
};
use molgfx_math::{Quat, Rgba8, Vec3};
use std::sync::Arc;

#[test]
fn paged_instance_timeline_samples_resident_pages_without_rebuilding_batches() {
    let mut engine = engine();
    let mut output = ResidencyOutput::default();
    let dataset = molgfx_core::DatasetId::new(0x1_0000_0017);
    let base = instance_chunk(&mut engine, &mut output, dataset, 21, 0.0);
    let start = instance_chunk(&mut engine, &mut output, dataset, 22, 10.0);
    let end = instance_chunk(&mut engine, &mut output, dataset, 23, 20.0);
    let id = ChunkPlacementId::new(301);
    engine
        .set_instance_chunk_placements(&[InstanceChunkPlacement::new(
            id,
            base,
            template(),
            Rgba8::WHITE,
        )])
        .unwrap_or_else(|error| panic!("instance placement validates: {error}"));
    engine
        .set_instance_chunk_windows(&[InstanceChunkWindow::new(id, start, end, 0.25)
            .unwrap_or_else(|error| panic!("instance window validates: {error}"))])
        .unwrap_or_else(|error| panic!("instance window fits: {error}"));
    install_anchored_relations(&mut engine, &mut output, dataset, id, base);

    let scene = molgfx_core::Scene::new();
    engine
        .render(&scene, &super::tests::camera())
        .unwrap_or_else(|error| panic!("paged instance timeline renders: {error}"));
    let buffers = buffer_count(&engine);
    let writes = instance_config_writes(&engine);
    let materialization_writes = instance_timeline_writes(&engine);
    let relation_geometry_writes = relation_geometry_write_count(&engine);
    let timeline_writes = relation_timeline_writes(&engine);
    let start_offset = instance_offset(&engine, start);
    let end_offset = instance_offset(&engine, end);
    assert_eq!(
        latest_timeline_config(&engine),
        [start_offset, end_offset, 0.25_f32.to_bits()]
    );
    assert_eq!(engine.derived_cache_usage().gpu_bytes, 2 * 32);
    assert!(relations_read_materialized_instances(&engine));

    engine
        .set_instance_chunk_windows(&[InstanceChunkWindow::new(id, start, end, 0.75)
            .unwrap_or_else(|error| panic!("advanced window validates: {error}"))])
        .unwrap_or_else(|error| panic!("advanced window fits: {error}"));
    if let Ok(mut passes) = engine.device.log.compute_passes.lock() {
        passes.clear();
    }
    engine
        .render(&scene, &super::tests::camera())
        .unwrap_or_else(|error| panic!("advanced timeline renders: {error}"));
    assert_eq!(buffer_count(&engine), buffers);
    assert_eq!(instance_config_writes(&engine), writes);
    assert_eq!(
        instance_timeline_writes(&engine),
        materialization_writes + 1
    );
    assert_eq!(
        relation_geometry_write_count(&engine),
        relation_geometry_writes
    );
    assert_eq!(relation_timeline_writes(&engine), timeline_writes + 1);
    assert!(compute_pass_recorded(
        &engine,
        "dynamic relation resolution"
    ));
    assert_eq!(
        latest_timeline_config(&engine),
        [start_offset, end_offset, 0.75_f32.to_bits()]
    );
}

#[test]
fn zero_budget_paged_instances_sample_two_resident_pages_directly() {
    let mut engine = engine_with_derived_budget(super::DerivedCacheBudget {
        cpu_bytes: 0,
        gpu_bytes: 0,
    });
    let mut output = ResidencyOutput::default();
    let dataset = molgfx_core::DatasetId::new(0x1_0000_0018);
    let base = instance_chunk(&mut engine, &mut output, dataset, 31, 0.0);
    let start = instance_chunk(&mut engine, &mut output, dataset, 32, 10.0);
    let end = instance_chunk(&mut engine, &mut output, dataset, 33, 20.0);
    let id = ChunkPlacementId::new(311);
    engine
        .set_instance_chunk_placements(&[InstanceChunkPlacement::new(
            id,
            base,
            template(),
            Rgba8::WHITE,
        )])
        .unwrap_or_else(|error| panic!("direct placement validates: {error}"));
    let window = InstanceChunkWindow::new(id, start, end, 0.5)
        .unwrap_or_else(|error| panic!("direct window validates: {error}"));
    engine
        .set_instance_chunk_windows(&[window])
        .unwrap_or_else(|error| panic!("direct window fits: {error}"));
    engine
        .render(&molgfx_core::Scene::new(), &super::tests::camera())
        .unwrap_or_else(|error| panic!("direct timeline renders: {error}"));

    assert_eq!(engine.derived_cache_usage().gpu_bytes, 0);
    assert_eq!(
        buffer_id(&engine, "materialized paged instance timeline"),
        u32::MAX
    );
    assert_eq!(
        latest_direct_config(&engine),
        [
            instance_offset(&engine, start),
            instance_offset(&engine, end),
            0.5_f32.to_bits(),
        ]
    );
}

fn compute_pass_recorded(
    engine: &super::Engine<crate::testing::MockDevice>,
    label: &'static str,
) -> bool {
    engine
        .device
        .log
        .compute_passes
        .lock()
        .is_ok_and(|passes| passes.contains(&label))
}

fn install_anchored_relations(
    engine: &mut super::Engine<crate::testing::MockDevice>,
    output: &mut ResidencyOutput,
    dataset: molgfx_core::DatasetId,
    instance: ChunkPlacementId,
    source: molgfx_core::ResidencyTicket,
) {
    let instance = ChunkEntityRef::new(
        dataset,
        source.key.chunk,
        instance,
        LogicalRow::new(100),
        ChunkSpatialKind::Instance,
    );
    let payload = RelationChunkPayload::new(Arc::from([
        PagedRelation {
            start: PagedSpatialAnchor::World(Vec3::ZERO),
            end: PagedSpatialAnchor::Entity(instance),
        },
        PagedRelation {
            start: PagedSpatialAnchor::World(Vec3::Y),
            end: PagedSpatialAnchor::Entity(instance),
        },
    ]))
    .unwrap_or_else(|error| panic!("relation payload validates: {error}"));
    let (request, data) = fixture(
        dataset,
        24,
        PayloadKind::RelationBatch,
        ChunkPayload::RelationBatch(payload),
    );
    let ticket = request_deliver_upload(engine, output, request, data);
    let placement =
        RelationChunkPlacement::new(ChunkPlacementId::new(302), ticket, RelationStyle::default())
            .unwrap_or_else(|error| panic!("relation placement validates: {error}"));
    engine
        .set_relation_chunk_placements(&[placement])
        .unwrap_or_else(|error| panic!("relation placement fits: {error}"));
}

fn instance_chunk(
    engine: &mut super::Engine<crate::testing::MockDevice>,
    output: &mut ResidencyOutput,
    dataset: molgfx_core::DatasetId,
    chunk: u64,
    x: f32,
) -> molgfx_core::ResidencyTicket {
    let payload = InstanceChunkPayload::new(Arc::from([
        rigid(Vec3::new(x, 0.0, 0.0)),
        rigid(Vec3::new(x + 1.0, 0.0, 0.0)),
    ]))
    .unwrap_or_else(|error| panic!("instance payload validates: {error}"));
    let (request, data) = fixture(
        dataset,
        chunk,
        PayloadKind::InstanceBatch,
        ChunkPayload::InstanceBatch(payload),
    );
    request_deliver_upload(engine, output, request, data)
}

fn template() -> Arc<AnalyticTemplate> {
    Arc::new(
        AnalyticTemplate::new(
            Arc::from([AnalyticSphere {
                center: [0.0; 3],
                radius: 0.5,
            }]),
            Arc::from([]),
            SourceRows::ordered(SourceNamespace(301), 1),
        )
        .unwrap_or_else(|error| panic!("analytic template validates: {error}")),
    )
}

fn rigid(translation: Vec3) -> RigidInstance {
    RigidInstance::new(translation, Quat::IDENTITY, 1.0)
        .unwrap_or_else(|error| panic!("rigid instance validates: {error}"))
}

fn instance_config_writes(engine: &super::Engine<crate::testing::MockDevice>) -> usize {
    let id = buffer_id(engine, "paged instance configuration");
    engine.device.log.writes.lock().map_or(0, |writes| {
        writes
            .iter()
            .filter(|(buffer, _, _, _)| *buffer == id)
            .count()
    })
}

fn relation_timeline_writes(engine: &super::Engine<crate::testing::MockDevice>) -> usize {
    let id = buffer_id(engine, "paged relation rigid timeline");
    engine.device.log.writes.lock().map_or(0, |writes| {
        writes
            .iter()
            .filter(|(buffer, _, _, _)| *buffer == id)
            .count()
    })
}

fn instance_timeline_writes(engine: &super::Engine<crate::testing::MockDevice>) -> usize {
    let id = buffer_id(engine, "paged instance timeline configuration");
    engine.device.log.writes.lock().map_or(0, |writes| {
        writes
            .iter()
            .filter(|(buffer, _, _, _)| *buffer == id)
            .count()
    })
}

fn latest_timeline_config(engine: &super::Engine<crate::testing::MockDevice>) -> [u32; 3] {
    latest_config_words(engine, "paged instance timeline configuration", [20, 24, 0])
}

fn latest_direct_config(engine: &super::Engine<crate::testing::MockDevice>) -> [u32; 3] {
    latest_config_words(engine, "paged instance configuration", [28, 44, 36])
}

fn latest_config_words(
    engine: &super::Engine<crate::testing::MockDevice>,
    label: &'static str,
    offsets: [usize; 3],
) -> [u32; 3] {
    let id = buffer_id(engine, label);
    let index = engine.device.log.writes.lock().ok().and_then(|writes| {
        writes
            .iter()
            .enumerate()
            .rev()
            .find_map(|(index, (buffer, _, _, _))| (*buffer == id).then_some(index))
    });
    let payload = index.and_then(|index| {
        engine
            .device
            .log
            .write_payloads
            .lock()
            .ok()
            .and_then(|payloads| payloads.get(index).cloned())
    });
    let Some(payload) = payload else {
        panic!("instance configuration write must be recorded")
    };
    let word = |offset: usize| {
        u32::from_ne_bytes(
            payload[offset..offset + 4]
                .try_into()
                .unwrap_or_else(|_| panic!("configuration word must exist")),
        )
    };
    offsets.map(word)
}

fn instance_offset(
    engine: &super::Engine<crate::testing::MockDevice>,
    ticket: molgfx_core::ResidencyTicket,
) -> u32 {
    let resident = engine
        .resident_generic_chunk(ticket)
        .unwrap_or_else(|| panic!("instance page must be resident"));
    u32::try_from(resident.byte_offset / 32)
        .unwrap_or_else(|_| panic!("fixture offset must fit u32"))
}

fn relations_read_materialized_instances(
    engine: &super::Engine<crate::testing::MockDevice>,
) -> bool {
    let materialized = buffer_id(engine, "materialized paged instance timeline");
    engine
        .device
        .log
        .buffer_bindings
        .lock()
        .is_ok_and(|bindings| {
            bindings.iter().any(|(group, binding, buffer, _, _)| {
                *group == "group1: paged relation resolver"
                    && [1, 2, 6, 7].contains(binding)
                    && *buffer == materialized
            })
        })
}

fn buffer_id(engine: &super::Engine<crate::testing::MockDevice>, label: &'static str) -> u32 {
    engine
        .device
        .log
        .buffers
        .lock()
        .map_or(u32::MAX, |buffers| {
            buffers
                .iter()
                .find(|(_, candidate, _)| *candidate == label)
                .map_or(u32::MAX, |(id, _, _)| *id)
        })
}