molgfx-render 0.2.1

The render graph, passes and the engine that drives a frame.
Documentation
use super::chunk_residency_tests::{complete, engine, upload};
use super::generic_chunk_residency_tests::{
    buffer_count, fixture, generic_fixtures, relation_geometry_write_count, relation_write_count,
    request_deliver_upload,
};
use super::{
    AttributeChunkWindow, ChunkPlacementId, ChunkPlacementStatus, PointChunkPlacement,
    RelationChunkPlacement,
};
use molgfx_core::{
    AttributeChunkPayload, AttributeValues, ChunkDomainKind, ChunkDomainRef, ChunkPayload,
    ChunkVisualBinding, ChunkVisualDescriptor, PayloadKind, RelationStyle, ResidencyOutput,
    VisualColumnKey, VisualProgramBuilder, VisualStyle,
};
use molgfx_math::{Mat4, Rgba8};
use std::sync::Arc;

#[test]
fn paged_relation_visual_reads_one_native_resident_column() {
    let mut engine = engine();
    let mut output = ResidencyOutput::default();
    let Some((relation_request, relation_data)) = generic_fixtures().into_iter().nth(3) else {
        panic!("relation fixture must exist")
    };
    let relation_ticket =
        request_deliver_upload(&mut engine, &mut output, relation_request, relation_data);
    let Some((point_request, point_data)) = generic_fixtures().into_iter().next() else {
        panic!("point fixture must exist")
    };
    let point_ticket = request_deliver_upload(&mut engine, &mut output, point_request, point_data);
    let point = PointChunkPlacement::new(
        ChunkPlacementId::new(91),
        point_ticket,
        Mat4::IDENTITY,
        2.0,
        Rgba8::WHITE,
    )
    .unwrap_or_else(|error| panic!("point placement validates: {error}"));
    engine
        .set_point_chunk_placements(&[point])
        .unwrap_or_else(|error| panic!("point placement must fit: {error}"));
    let target = ChunkDomainRef::new(
        relation_ticket.key.dataset,
        relation_ticket.key.chunk,
        ChunkDomainKind::Relation,
    );
    let attribute =
        AttributeChunkPayload::new(target, AttributeValues::Scalar(Arc::from([1.0_f32, 2.0])))
            .unwrap_or_else(|error| panic!("scalar column validates: {error}"));
    let (request, data) = fixture(
        relation_ticket.key.dataset,
        15,
        PayloadKind::Attribute,
        ChunkPayload::Attribute(attribute),
    );
    let attribute_ticket = engine
        .request_chunk_into(request, &mut output)
        .unwrap_or_else(|error| panic!("attribute request must fit: {error}"));
    engine
        .deliver_chunk_into(attribute_ticket, data, &mut output)
        .unwrap_or_else(|error| panic!("attribute delivery must fit: {error}"));
    let start = scalar_attribute(&mut engine, &mut output, target, 16, [2.0, 4.0]);
    let end = scalar_attribute(&mut engine, &mut output, target, 17, [6.0, 8.0]);

    let descriptor = animated_relation_visual(attribute_ticket);
    let relation = RelationChunkPlacement::new(
        ChunkPlacementId::new(93),
        relation_ticket,
        RelationStyle::default(),
    )
    .and_then(|placement| placement.with_visual(descriptor))
    .unwrap_or_else(|error| panic!("visual relation placement validates: {error}"));
    engine
        .set_relation_chunk_placements(std::slice::from_ref(&relation))
        .unwrap_or_else(|error| panic!("relation placement must fit: {error}"));
    assert_eq!(
        engine.relation_chunk_placement_status(relation.id()),
        ChunkPlacementStatus::NotResident
    );

    upload(&mut engine, attribute_ticket, &mut output);
    complete(&mut engine, &mut output);
    engine
        .set_attribute_chunk_windows(&[AttributeChunkWindow::new(
            attribute_ticket,
            start,
            end,
            0.25,
        )
        .unwrap_or_else(|error| panic!("attribute window validates: {error}"))])
        .unwrap_or_else(|error| panic!("attribute window fits: {error}"));
    assert_eq!(
        engine.relation_chunk_placement_status(relation.id()),
        ChunkPlacementStatus::Resident
    );
    let mut scene = molgfx_core::Scene::new();
    engine
        .render(&scene, &super::tests::camera())
        .unwrap_or_else(|error| panic!("paged visual relation must render: {error}"));
    assert_materialized_visual(&engine);

    let buffers = buffer_count(&engine);
    let writes = relation_write_count(&engine);
    engine
        .render(&scene, &super::tests::camera())
        .unwrap_or_else(|error| panic!("stable visual relation must render: {error}"));
    assert_eq!(buffer_count(&engine), buffers);
    assert_eq!(relation_write_count(&engine), writes);

    let geometry_writes = relation_geometry_write_count(&engine);
    let visual_writes = relation_write_count(&engine);
    scene
        .set_presentation_time(0.5)
        .unwrap_or_else(|error| panic!("presentation time validates: {error}"));
    engine
        .render(&scene, &super::tests::camera())
        .unwrap_or_else(|error| panic!("animated visual relation must render: {error}"));
    assert_eq!(relation_geometry_write_count(&engine), geometry_writes);
    assert_eq!(relation_write_count(&engine), visual_writes + 1);
    assert_eq!(buffer_count(&engine), buffers);

    assert_attribute_advance(&mut engine, &scene, attribute_ticket, start, end, buffers);
}

fn assert_materialized_visual(engine: &super::Engine<crate::testing::MockDevice>) {
    let labels = buffer_labels(engine);
    assert!(labels.contains(&"paged visual instructions"));
    assert!(labels.contains(&"visual entity results"));
    assert!(labels.contains(&"paged attribute timeline materialization configuration"));
    assert_eq!(engine.derived_cache_usage().gpu_bytes, 8);
}

fn assert_attribute_advance(
    engine: &mut super::Engine<crate::testing::MockDevice>,
    scene: &molgfx_core::Scene,
    attribute: molgfx_core::ResidencyTicket,
    start: molgfx_core::ResidencyTicket,
    end: molgfx_core::ResidencyTicket,
    buffers: usize,
) {
    let geometry_writes = relation_geometry_write_count(engine);
    let visual_writes = relation_write_count(engine);
    let timeline_writes = paged_attribute_timeline_writes(engine);
    let window = AttributeChunkWindow::new(attribute, start, end, 0.75)
        .unwrap_or_else(|error| panic!("advanced attribute window validates: {error}"));
    engine
        .set_attribute_chunk_windows(&[window])
        .unwrap_or_else(|error| panic!("advanced attribute window fits: {error}"));
    engine
        .render(scene, &super::tests::camera())
        .unwrap_or_else(|error| panic!("advanced attribute timeline renders: {error}"));
    assert_eq!(relation_geometry_write_count(engine), geometry_writes);
    assert_eq!(relation_write_count(engine), visual_writes);
    assert_eq!(paged_attribute_timeline_writes(engine), timeline_writes + 1);
    assert_eq!(buffer_count(engine), buffers);
}

fn animated_relation_visual(
    attribute_ticket: molgfx_core::ResidencyTicket,
) -> Arc<ChunkVisualDescriptor> {
    let key = VisualColumnKey(71);
    let second_key = VisualColumnKey(72);
    let mut builder = VisualProgramBuilder::new();
    let width = builder
        .scalar_column(key)
        .unwrap_or_else(|error| panic!("scalar expression builds: {error}"));
    let second_width = builder
        .scalar_column(second_key)
        .unwrap_or_else(|error| panic!("second scalar expression builds: {error}"));
    let width = builder
        .add(width, second_width)
        .unwrap_or_else(|error| panic!("scalar expressions combine: {error}"));
    let time = builder
        .time()
        .unwrap_or_else(|error| panic!("time expression builds: {error}"));
    let width = builder
        .multiply(width, time)
        .unwrap_or_else(|error| panic!("animated width builds: {error}"));
    builder
        .set_width_scale(width)
        .unwrap_or_else(|error| panic!("relation width output builds: {error}"));
    Arc::new(
        ChunkVisualDescriptor::new(
            VisualStyle::new(
                builder
                    .finish()
                    .unwrap_or_else(|error| panic!("visual program builds: {error}")),
            ),
            Arc::from([
                ChunkVisualBinding::new(key, attribute_ticket),
                ChunkVisualBinding::new(second_key, attribute_ticket),
            ]),
        )
        .unwrap_or_else(|error| panic!("chunk visual validates: {error}")),
    )
}

fn scalar_attribute(
    engine: &mut super::Engine<crate::testing::MockDevice>,
    output: &mut ResidencyOutput,
    target: ChunkDomainRef,
    chunk: u64,
    values: [f32; 2],
) -> molgfx_core::ResidencyTicket {
    let payload = AttributeChunkPayload::new(target, AttributeValues::Scalar(Arc::from(values)))
        .unwrap_or_else(|error| panic!("timeline scalar column validates: {error}"));
    let (request, data) = fixture(
        target.dataset(),
        chunk,
        PayloadKind::Attribute,
        ChunkPayload::Attribute(payload),
    );
    request_deliver_upload(engine, output, request, data)
}

fn buffer_labels(engine: &super::Engine<crate::testing::MockDevice>) -> Vec<&'static str> {
    engine.device.log.buffers.lock().map_or_else(
        |_| Vec::new(),
        |buffers| buffers.iter().map(|(_, label, _)| *label).collect(),
    )
}

fn paged_attribute_timeline_writes(engine: &super::Engine<crate::testing::MockDevice>) -> usize {
    let id = engine.device.log.buffers.lock().ok().and_then(|buffers| {
        buffers
            .iter()
            .find(|(_, label, _)| {
                *label == "paged attribute timeline materialization configuration"
            })
            .map(|(id, _, _)| *id)
    });
    engine.device.log.writes.lock().map_or(0, |writes| {
        writes
            .iter()
            .filter(|(buffer, _, _, _)| Some(*buffer) == id)
            .count()
    })
}