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