molgfx-render 0.3.3

The render graph, passes and the engine that drives a frame.
Documentation
//! Shared visual-program lowering for generic homogeneous row domains.

use super::slot_types::SlotShading;
use super::visual::{VisualBase, VisualCullEntries, VisualSlot, VisualSync};
use super::visual_parameters::VisualParameterTable;
use super::visual_programs::VisualProgramTable;
use super::visual_properties::VisualPropertyTable;
use crate::error::RenderError;
use molgfx_core::{RowDomain, Scene, VisualOutput};
use molgfx_gpu::Device;
use molgfx_math::Rgba8;

pub(super) struct GenericVisualResources<'a, D: Device> {
    pub(super) programs: &'a VisualProgramTable<D>,
    pub(super) parameters: &'a VisualParameterTable<D>,
    pub(super) properties: &'a VisualPropertyTable<D>,
    pub(super) fallback: VisualCullEntries<'a, D>,
    pub(super) parameter_slot_base: usize,
    pub(super) time_seconds: f32,
}

pub(super) struct GenericVisualTarget<'a> {
    pub(super) scene: &'a Scene,
    pub(super) domain: RowDomain,
    pub(super) color: Rgba8,
    pub(super) opacity: f32,
    pub(super) row_count: usize,
}

impl<D: Device> Copy for GenericVisualResources<'_, D> {}

impl<D: Device> Clone for GenericVisualResources<'_, D> {
    fn clone(&self) -> Self {
        *self
    }
}

#[derive(Debug)]
pub(super) struct GenericVisualState<D: Device> {
    slot: Option<VisualSlot<D>>,
    shading: SlotShading,
    translucent: bool,
}

impl<D: Device> GenericVisualState<D> {
    pub(super) fn new() -> Self {
        Self {
            slot: None,
            shading: SlotShading::default(),
            translucent: false,
        }
    }

    pub(super) fn sync(
        &mut self,
        device: &D,
        queue: &D::Queue,
        target: &GenericVisualTarget<'_>,
        resources: GenericVisualResources<'_, D>,
    ) -> Result<bool, RenderError> {
        let Some(descriptor) = target.scene.domain_visual(target.domain) else {
            return self.sync_uniform(
                device,
                queue,
                target.color,
                target.opacity,
                target.row_count,
                resources,
            );
        };
        let style = descriptor.style();
        let program_offset = resources
            .programs
            .offset(style.program())
            .ok_or_else(missing_visual_buffer)?;
        let parameter_buffer = resources
            .parameters
            .buffer()
            .ok_or_else(missing_visual_buffer)?;
        let descriptor_index = target
            .scene
            .domain_visuals()
            .position(|(candidate, _)| candidate == target.domain)
            .ok_or_else(missing_visual_buffer)?;
        let parameter_slot = resources
            .parameter_slot_base
            .checked_add(descriptor_index)
            .ok_or_else(missing_visual_buffer)?;
        let attributes =
            resources
                .properties
                .offsets_for_domain(target.scene, target.domain, Some(style));
        let slot = self.slot.get_or_insert_with(VisualSlot::new);
        let changed = slot.sync(&VisualSync {
            device,
            queue,
            style: Some(style),
            program_offset,
            parameter_buffer,
            parameter_offset: VisualParameterTable::<D>::offset(parameter_slot),
            parameters_preloaded: false,
            property_offsets: attributes.offsets,
            attribute_layouts: attributes.layouts,
            property_end_offsets: [0; 4],
            property_alphas: [0.0; 4],
            state_offset: 1,
            time_seconds: resources.time_seconds,
            entity_count: target.row_count,
            result_count: target.row_count,
            base: VisualBase::rgba8_opacity(target.color, target.opacity),
        })?;
        self.shading = SlotShading::default()
            .with_visual(true)
            .with_fragment_visual(style.program().fragment_instruction_count() != 0);
        self.translucent = target.color.a < u8::MAX
            || style
                .program()
                .output_register(VisualOutput::Opacity)
                .is_some();
        Ok(changed)
    }

    pub(super) fn sync_uniform(
        &mut self,
        device: &D,
        queue: &D::Queue,
        color: Rgba8,
        opacity: f32,
        row_count: usize,
        resources: GenericVisualResources<'_, D>,
    ) -> Result<bool, RenderError> {
        let parameter_buffer = resources
            .parameters
            .buffer()
            .ok_or_else(missing_visual_buffer)?;
        let slot = self.slot.get_or_insert_with(VisualSlot::new);
        let changed = slot.sync(&VisualSync {
            device,
            queue,
            style: None,
            program_offset: 0,
            parameter_buffer,
            parameter_offset: 0,
            parameters_preloaded: false,
            property_offsets: [0; 4],
            attribute_layouts: [0; 4],
            property_end_offsets: [0; 4],
            property_alphas: [0.0; 4],
            state_offset: 1,
            time_seconds: resources.time_seconds,
            entity_count: row_count,
            result_count: row_count,
            base: VisualBase::rgba8_opacity(color, opacity),
        })?;
        self.shading = SlotShading::default();
        self.translucent = color.a < u8::MAX;
        Ok(changed)
    }

    pub(super) fn entries<'a>(
        &'a self,
        resources: GenericVisualResources<'a, D>,
    ) -> VisualCullEntries<'a, D> {
        let Some(slot) = &self.slot else {
            return resources.fallback;
        };
        let (Some(instructions), Some(parameters), Some(properties)) = (
            resources.programs.buffer(),
            resources.parameters.buffer(),
            resources.properties.buffer(),
        ) else {
            return resources.fallback;
        };
        crate::fallback(
            slot.cull_entries(instructions, parameters, properties),
            resources.fallback,
        )
    }

    pub(super) const fn shading(&self) -> SlotShading {
        self.shading
    }

    pub(super) const fn is_translucent(&self) -> bool {
        self.translucent
    }

    pub(super) fn has_cull_results(&self) -> bool {
        self.slot.as_ref().is_some_and(VisualSlot::has_cull_results)
    }

    pub(super) fn has_shading_results(&self) -> bool {
        self.slot
            .as_ref()
            .is_some_and(VisualSlot::has_shading_results)
    }

    pub(super) fn binding_revision(&self) -> u64 {
        self.slot.as_ref().map_or(0, VisualSlot::binding_revision)
    }
}

impl<D: Device> GenericVisualResources<'_, D> {
    pub(super) const fn binding_revision(self) -> [u64; 3] {
        [
            self.programs.binding_revision(),
            self.parameters.binding_revision(),
            self.properties.binding_revision(),
        ]
    }
}

fn missing_visual_buffer() -> molgfx_gpu::GpuError {
    molgfx_gpu::GpuError::LimitExceeded {
        resource: "generic visual arena",
        limit: 0,
    }
}