use super::acceleration_cache::AccelerationCache;
use super::asset::GpuAssetIdentity;
use super::buffers::{count, upload_grow};
use super::slot_types::{RecordState, SelectionIdentity, SelectionKey};
use crate::error::RenderError;
use molgfx_core::{AtomGpu, BondGpu, PlacedStructure, Representation, Scene};
use molgfx_gpu::Device;
use std::collections::BTreeMap;
#[cfg(test)]
#[path = "record_cache_tests.rs"]
mod tests;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) struct RecordKey {
pub(crate) selection: SelectionKey,
pub(crate) asset: GpuAssetIdentity,
pub(crate) records: RecordState,
pub(crate) properties: [u64; 2],
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) struct RecordGeometry {
pub(crate) selection: SelectionKey,
pub(crate) asset: GpuAssetIdentity,
pub(crate) properties: [u64; 2],
pub(crate) kind: u8,
pub(crate) radius_scale: u32,
}
impl RecordKey {
#[must_use]
pub(crate) const fn geometry(self) -> RecordGeometry {
RecordGeometry {
selection: self.selection,
asset: self.asset,
properties: self.properties,
kind: self.records.kind(),
radius_scale: self.records.radius_scale_bits(),
}
}
}
pub(super) struct RecordPrepare<'a, D: Device> {
pub(super) device: &'a D,
pub(super) queue: &'a D::Queue,
pub(super) placed: &'a PlacedStructure,
pub(super) representation: &'a Representation,
pub(super) selection: &'a molgfx_core::AtomSelection,
pub(super) color_property: Option<&'a molgfx_core::AtomProperty>,
pub(super) appearance_property: Option<&'a molgfx_core::AtomProperty>,
}
pub(super) struct RecordBudget<'a, D: Device> {
pub(super) ledger: &'a mut crate::DerivedCache,
pub(super) frame: u64,
pub(super) _device: std::marker::PhantomData<D>,
}
pub(super) struct RecordScratch<'a> {
pub(super) atoms: &'a mut Vec<AtomGpu>,
pub(super) bonds: &'a mut Vec<BondGpu>,
pub(super) compaction: &'a mut Vec<u32>,
}
#[derive(Debug)]
pub(super) struct RecordSet<D: Device> {
atoms: Option<D::Buffer>,
bonds: Option<D::Buffer>,
compaction: Option<D::Buffer>,
pub(super) atom_count: u32,
pub(super) bond_count: u32,
atoms_capacity: u64,
bonds_capacity: u64,
compaction_capacity: u64,
}
impl<D: Device> Default for RecordSet<D> {
fn default() -> Self {
Self {
atoms: None,
bonds: None,
compaction: None,
atom_count: 0,
bond_count: 0,
atoms_capacity: 0,
bonds_capacity: 0,
compaction_capacity: 0,
}
}
}
#[derive(Debug)]
pub(super) struct RecordSetRef<'a, D: Device> {
pub(super) atoms: &'a D::Buffer,
pub(super) bonds: &'a D::Buffer,
pub(super) compaction: &'a D::Buffer,
pub(super) atom_count: u32,
pub(super) bond_count: u32,
}
impl<D: Device> Clone for RecordSetRef<'_, D> {
fn clone(&self) -> Self {
*self
}
}
impl<D: Device> Copy for RecordSetRef<'_, D> {}
#[derive(Debug)]
pub(crate) struct RecordCache<D: Device> {
entries: Vec<(RecordKey, RecordSet<D>)>,
}
impl<D: Device> RecordCache<D> {
pub(super) const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[must_use]
pub(super) fn key(
scene: &Scene,
placed: &PlacedStructure,
structure: molgfx_core::StructureHandle,
representation: &Representation,
selection_handle: molgfx_core::SelectionHandle,
asset: GpuAssetIdentity,
property_revisions: [u64; 2],
) -> RecordKey {
RecordKey {
selection: SelectionKey {
structure,
identity: match scene.selection_fingerprint(selection_handle) {
Some(fingerprint) => SelectionIdentity::Query(fingerprint),
None => SelectionIdentity::Mask(selection_handle),
},
topology: placed.bond_topology_revision(),
},
asset,
records: RecordState::new(representation),
properties: property_revisions,
}
}
pub(super) fn sync<'a>(
&mut self,
acceleration: &mut AccelerationCache<D>,
budget: &mut RecordBudget<'_, D>,
needed: &BTreeMap<RecordKey, usize>,
requires_bvh: bool,
scratch: &mut RecordScratch<'_>,
mut prepare: impl FnMut(usize, RecordKey) -> Result<Option<RecordPrepare<'a, D>>, RenderError>,
) -> Result<(), RenderError> {
let (ledger, frame) = (&mut *budget.ledger, budget.frame);
self.entries.retain(|(key, _)| needed.contains_key(key));
let mut fresh = Vec::new();
let mut hierarchy_inputs = Vec::new();
for (key, slot_index) in needed {
if self
.entries
.binary_search_by_key(key, |(key, _)| *key)
.is_ok()
{
continue;
}
let Some(input) = prepare(*slot_index, *key)? else {
continue;
};
let set = RecordSet::sync(&input, scratch)?;
if requires_bvh {
acceleration.build(
*key,
input.device,
input.queue,
scratch.atoms,
scratch.bonds,
input.placed,
)?;
hierarchy_inputs.push((*key, input.placed));
}
fresh.push((*key, set));
}
for (key, set) in fresh {
if !ledger.retain(
key,
crate::DerivedCacheClass::RecordSet,
crate::DerivedFootprint {
cpu_bytes: 0,
gpu_bytes: set.resident_bytes(),
},
frame,
) {
continue;
}
let position = match self.entries.binary_search_by_key(&key, |(key, _)| *key) {
Ok(position) | Err(position) => position,
};
self.entries.insert(position, (key, set));
}
self.entries.retain(|(key, _)| needed.contains_key(key));
acceleration.retain(needed, requires_bvh, ledger, frame);
let _ = hierarchy_inputs;
Ok(())
}
#[must_use]
pub(super) fn get(&self, key: RecordKey) -> Option<&RecordSet<D>> {
self.entries
.binary_search_by_key(&key, |(key, _)| *key)
.ok()
.and_then(|index| self.entries.get(index))
.map(|(_, set)| set)
}
}
impl<D: Device> RecordSet<D> {
fn sync(
input: &RecordPrepare<'_, D>,
scratch: &mut RecordScratch<'_>,
) -> Result<Self, RenderError> {
let mut set = Self::default();
super::record_pack::pack_records(input, scratch)?;
set.atom_count = count(scratch.atoms.len());
set.bond_count = count(scratch.bonds.len());
upload_grow(
input.device,
input.queue,
"atom instances",
scratch.atoms,
&mut set.atoms,
&mut set.atoms_capacity,
)?;
upload_grow(
input.device,
input.queue,
"bond instances",
scratch.bonds,
&mut set.bonds,
&mut set.bonds_capacity,
)?;
upload_grow(
input.device,
input.queue,
"source-to-compacted atom indices",
scratch.compaction,
&mut set.compaction,
&mut set.compaction_capacity,
)?;
Ok(set)
}
#[must_use]
pub(super) fn as_ref(&self) -> Option<RecordSetRef<'_, D>> {
Some(RecordSetRef {
atoms: self.atoms.as_ref()?,
bonds: self.bonds.as_ref()?,
compaction: self.compaction.as_ref()?,
atom_count: self.atom_count,
bond_count: self.bond_count,
})
}
fn resident_bytes(&self) -> u64 {
self.atoms_capacity
.saturating_add(self.bonds_capacity)
.saturating_add(self.compaction_capacity)
}
}