Skip to main content

molgfx_render/engine/
init.rs

1//! Engine construction: device open, pass creation, graph declaration.
2
3use super::graph_setup::{realtime_nodes, realtime_resources};
4use super::{
5    AdaptiveQuality, DerivedCache, EngineConfig, FocusTracker, GpuProfiler, PassRegistry, Picker,
6    RenderMode, RenderProfile, ResolvedRenderPlan, ShadowBoundCache, TemporalState,
7    chunk_residency::ChunkGpuResidency,
8};
9use crate::error::RenderError;
10use crate::graph::{self, PassNode, ResourceDesc, TransientPool};
11use crate::passes::{
12    AmbientOcclusionPass, AoDenoisePass, BloomPass, BondPass, CartoonPass, CullPass,
13    DepthOfFieldPass, FrameBindings, InteractionPass, LabelPass, LightingPass, MotionBlurPass,
14    OccupancyBoundsFormat, OccupancyPass, OitCompositePass, OitPass, OverlayPass,
15    ParticleMotionPass, PointPass, PrimitivePass, RelationResolvePass, ShadowPass, SpherePass,
16    SurfaceComponentPass, SurfaceFieldPass, SurfacePass, TemporalPass, TonemapPass, TrajectoryPass,
17};
18use crate::scene_gpu::GpuScene;
19use molgfx_gpu::{Device, DeviceDesc, Opened, TextureFormat, WindowTarget};
20
21/// The rendering engine, generic over the device with no dynamic dispatch
22/// on the frame path.
23#[derive(Debug)]
24pub struct Engine<D: Device> {
25    pub(crate) device: D,
26    pub(crate) queue: D::Queue,
27    pub(crate) surface: Option<D::Surface>,
28    pub(crate) passes: PassRegistry<D>,
29    pub(crate) resources: Vec<ResourceDesc>,
30    pub(crate) pass_nodes: Vec<PassNode<D>>,
31    pub(crate) order: Vec<usize>,
32    pub(crate) pool: Option<TransientPool<D>>,
33    pub(crate) bindings: Option<FrameBindings<D>>,
34    pub(crate) scene_gpu: GpuScene<D>,
35    pub(crate) width: u32,
36    pub(crate) height: u32,
37    pub(crate) target_format: TextureFormat,
38    pub(crate) profiler: Option<GpuProfiler<D>>,
39    pub(crate) picker: Picker<D>,
40    pub(crate) temporal: TemporalState,
41    pub(crate) temporal_scene_identity: Option<u64>,
42    pub(crate) mode: RenderMode,
43    /// The closed adaptive-quality loop: frame time in, quality tier out.
44    pub(crate) adaptive: AdaptiveQuality,
45    pub(crate) profile: RenderProfile,
46    pub(crate) resolved_plan: ResolvedRenderPlan,
47    pub(crate) focus_tracker: FocusTracker,
48    pub(crate) shadow_bound: ShadowBoundCache,
49    pub(crate) derived_cache: DerivedCache,
50    pub(super) derived_frame: u64,
51    pub(crate) host_working_set: molgfx_core::HostWorkingSet,
52    pub(super) chunk_residency: ChunkGpuResidency<D>,
53}
54
55fn cull_pass<D: Device>(device: &D, scene: &GpuScene<D>) -> Result<CullPass<D>, RenderError> {
56    let layouts = crate::passes::CullLayouts {
57        atoms: &scene.atom_cull_layout,
58        bonds: &scene.bond_cull_layout,
59        visuals: &scene.visual_cull_layout,
60        frame: &scene.group0_layout,
61        paged: scene.paged_chunk_layout(),
62        paged_bonds: scene.paged_bond_layout(),
63        points: &scene.generic_point_cull_layout,
64        instances: &scene.generic_instance_cull_layout,
65        instance_timeline: &scene.instance_timeline_layout,
66        attribute_timeline: &scene.attribute_timeline_layout,
67        relations: &scene.relation_cull_layout,
68    };
69    CullPass::new(device, &layouts)
70}
71
72fn realtime_passes<D: Device>(
73    device: &D,
74    target_format: TextureFormat,
75    scene: &GpuScene<D>,
76    plan: &ResolvedRenderPlan,
77) -> Result<PassRegistry<D>, RenderError> {
78    Ok(PassRegistry {
79        sphere: SpherePass::new(
80            device,
81            target_format,
82            &scene.group0_layout,
83            &scene.group2_layout,
84            scene.paged_chunk_layout(),
85        )?,
86        point: PointPass::new(
87            device,
88            &scene.group0_layout,
89            &scene.group2_layout,
90            scene.paged_chunk_layout(),
91            &scene.generic_point_render_layout,
92        )?,
93        primitive: PrimitivePass::new(
94            device,
95            &scene.group0_layout,
96            &scene.primitive_layout,
97            &scene.ligand_pose_layout,
98            &scene.generic_instance_render_layout,
99        )?,
100        surface: SurfacePass::new(device, &scene.group0_layout, &scene.group2_layout)?,
101        surface_field: SurfaceFieldPass::new(
102            device,
103            &scene.surface_field_output_layout,
104            &scene.surface_field_erosion_layout,
105            &scene.surface_field_normal_layout,
106            &scene.surface_field_input_layout,
107        )?,
108        surface_components: SurfaceComponentPass::new(device, &scene.surface_component_layout)?,
109        bond: BondPass::new(
110            device,
111            target_format,
112            &scene.group0_layout,
113            &scene.group2_layout,
114            scene.paged_bond_layout(),
115        )?,
116        cartoon: CartoonPass::new(device, &scene.group0_layout, &scene.ribbon_layout)?,
117        cull: cull_pass(device, scene)?,
118        depth_of_field: plan
119            .depth_of_field()
120            .map(|_| DepthOfFieldPass::new(device, &scene.group0_layout))
121            .transpose()?,
122        ambient_occlusion: AmbientOcclusionPass::new(
123            device,
124            &scene.group0_layout,
125            &scene.quality_layout,
126        )?,
127        ao_denoise: AoDenoisePass::new(device, &scene.group0_layout)?,
128        lighting: LightingPass::new(device, &scene.group0_layout)?,
129        shadow: ShadowPass::new(
130            device,
131            &scene.group0_layout,
132            &scene.group2_layout,
133            &scene.ribbon_layout,
134            &scene.primitive_shadow_layout,
135            &scene.ligand_pose_layout,
136        )?,
137        oit: OitPass::new(
138            device,
139            &scene.group0_layout,
140            &scene.group2_layout,
141            &scene.ribbon_layout,
142            (&scene.primitive_layout, &scene.ligand_pose_layout),
143            (
144                &scene.generic_point_render_layout,
145                &scene.generic_instance_render_layout,
146            ),
147            (&scene.volume_layout, &scene.segmentation_layout),
148        )?,
149        interaction: InteractionPass::new(device, &scene.group0_layout, &scene.interaction_layout)?,
150        relation_resolve: RelationResolvePass::new(device, &scene.relation_resolve_layout)?,
151        label: LabelPass::new(
152            device,
153            &scene.group0_layout,
154            &scene.label_declutter_layout,
155            &scene.label_render_layout,
156        )?,
157        oit_composite: OitCompositePass::new(device)?,
158        temporal: TemporalPass::new(device, &scene.group0_layout)?,
159        bloom: plan
160            .bloom()
161            .map(|_| BloomPass::new(device, &scene.group0_layout))
162            .transpose()?,
163        motion_blur: plan
164            .motion_blur()
165            .map(|_| MotionBlurPass::new(device, &scene.group0_layout))
166            .transpose()?,
167        tonemap: TonemapPass::new(device, target_format, &scene.group0_layout)?,
168        overlay: OverlayPass::new(
169            device,
170            target_format,
171            &scene.group0_layout,
172            &scene.overlay_layout,
173        )?,
174        trajectory: TrajectoryPass::new(device, &scene.trajectory_layout)?,
175        occupancy: None,
176        particle_motion: ParticleMotionPass::new(device, &scene.primitive_motion_layout)?,
177    })
178}
179
180impl<D: Device> Engine<D> {
181    pub(super) fn ensure_occupancy(
182        &mut self,
183        scene: &molgfx_core::Scene,
184    ) -> Result<(), RenderError> {
185        if !scene.has_occupancy_stream() || self.passes.occupancy.is_some() {
186            return Ok(());
187        }
188        let format = OccupancyBoundsFormat::resolve(self.device.capabilities())?;
189        let layout = self
190            .scene_gpu
191            .ensure_occupancy_layout(&self.device, format.texture_format());
192        self.passes.occupancy = Some(OccupancyPass::new(&self.device, layout, format)?);
193        Ok(())
194    }
195    /// Asynchronously opens a device and builds the realtime graph. Pass a
196    /// window to render to screen; none for off-screen rendering.
197    ///
198    /// # Errors
199    ///
200    /// No compatible adapter, or pipeline construction failed.
201    pub async fn new_async(
202        config: &EngineConfig,
203        window: Option<WindowTarget>,
204    ) -> Result<Self, RenderError> {
205        let opened = D::open_async(
206            &DeviceDesc {
207                power: config.power,
208                resource_memory_limit_bytes: config.resource_memory_limit_bytes,
209            },
210            window,
211        )
212        .await?;
213        Self::from_opened(config, opened)
214    }
215
216    /// Opens a native device synchronously for callers without an async
217    /// executor. Browser callers use [`Self::new_async`].
218    ///
219    /// # Errors
220    ///
221    /// No compatible adapter, or pipeline construction failed.
222    #[cfg(not(target_arch = "wasm32"))]
223    pub fn new(config: &EngineConfig, window: Option<WindowTarget>) -> Result<Self, RenderError> {
224        let opened = D::open_blocking(
225            &DeviceDesc {
226                power: config.power,
227                resource_memory_limit_bytes: config.resource_memory_limit_bytes,
228            },
229            window,
230        )?;
231        Self::from_opened(config, opened)
232    }
233
234    fn from_opened(config: &EngineConfig, opened: Opened<D>) -> Result<Self, RenderError> {
235        let mut surface = opened.surface;
236        let device = opened.device;
237
238        let target_format = super::target::configure(&device, &mut surface, config);
239
240        let profile = config.profile.clone();
241        let resolved_plan = profile.resolve();
242        let scene_gpu = GpuScene::new(&device, config.residency, config.picking_page_capacity)?;
243        let passes = realtime_passes(&device, target_format, &scene_gpu, &resolved_plan)?;
244        let pass_nodes = realtime_nodes(
245            resolved_plan.depth_of_field().is_some(),
246            resolved_plan.bloom().is_some(),
247            resolved_plan.motion_blur().is_some(),
248        );
249        let resources = realtime_resources();
250        let order = graph::schedule(&pass_nodes)?;
251
252        let profiler = GpuProfiler::new(&device, target_format)?;
253        let picker = Picker::new(&device, config.picking_page_capacity)?;
254        let chunk_residency = ChunkGpuResidency::new(&device, config.residency)?;
255        Ok(Self {
256            device,
257            queue: opened.queue,
258            surface,
259            passes,
260            resources,
261            pass_nodes,
262            order,
263            pool: None,
264            bindings: None,
265            scene_gpu,
266            width: config.width,
267            height: config.height,
268            target_format,
269            profiler,
270            picker,
271            temporal: TemporalState::default(),
272            temporal_scene_identity: None,
273            mode: config.mode,
274            adaptive: AdaptiveQuality::new(config.adaptive, config.mode == RenderMode::Cinematic),
275            profile,
276            resolved_plan,
277            focus_tracker: FocusTracker::default(),
278            shadow_bound: ShadowBoundCache::default(),
279            derived_cache: DerivedCache::new(config.derived_cache),
280            derived_frame: 0,
281            host_working_set: molgfx_core::HostWorkingSet::new(config.source_budget),
282            chunk_residency,
283        })
284    }
285}