1use super::{
12 Engine, FrameCompleteness, FrameDegradation, FrameMetrics, FrameReport, FrameStatus,
13 MotionBlur, QualityTier, RenderMode, TemporalOptions,
14};
15use crate::error::RenderError;
16use crate::graph::{PassContext, ResourceTable};
17use molgfx_core::Scene;
18use molgfx_gpu::Queue as _;
19use molgfx_gpu::{Device, SurfaceFrame as _};
20use molgfx_math::Camera;
21
22#[inline]
24fn pending_frame_submissions<D: Device>(engine: &Engine<D>) -> u32 {
25 u32::from(engine.frame_submission_pending)
26}
27
28#[inline]
31fn duration_ns(duration: std::time::Duration) -> u64 {
32 duration
33 .as_secs()
34 .saturating_mul(1_000_000_000)
35 .saturating_add(u64::from(duration.subsec_nanos()))
36}
37
38impl<D: Device> Engine<D> {
39 fn sync_scene(&mut self, scene: &Scene) -> Result<bool, RenderError> {
49 self.ensure_occupancy(scene)?;
50 self.chunk_residency.poll(&self.device, &self.queue)?;
51
52 let scene_changed = self.scene_gpu.sync(crate::scene_gpu::SceneSync {
53 device: &self.device,
54 queue: &self.queue,
55 scene,
56 quality: self.tier() >= QualityTier::Standard,
57 detail: self.tier().detail(),
58 extent: [self.width, self.height],
59 ray_query_layout: self.passes.ambient_occlusion.ray_query_layout(),
60 derived_cache: &mut self.derived_cache,
61 derived_frame: self.derived_frame,
62 })?;
63
64 self.chunk_residency.sync_scene(
65 &mut self.scene_gpu,
66 &self.device,
67 &self.queue,
68 &mut self.derived_cache,
69 self.derived_frame,
70 )?;
71 self.chunk_residency.flush(&self.device, &self.queue)?;
72
73 self.derived_frame = self.derived_frame.wrapping_add(1);
74
75 if scene_changed {
76 self.temporal.invalidate_convergence();
77 }
78
79 Ok(scene_changed)
80 }
81
82 #[inline]
87 fn update_temporal_scene_identity(&mut self, scene: &Scene) -> bool {
88 let identity = scene.cache_identity();
89 let changed = self.temporal_scene_identity != Some(identity);
90
91 if changed {
92 self.temporal_scene_identity = Some(identity);
93 }
94
95 changed
96 }
97
98 #[inline]
100 fn temporal_reset_required(
101 &self,
102 scene_reset: bool,
103 pool_rebuilt: bool,
104 camera_changed: bool,
105 ) -> bool {
106 scene_reset || pool_rebuilt || (self.mode == RenderMode::Cinematic && camera_changed)
107 }
108
109 pub fn render(&mut self, scene: &Scene, camera: &Camera) -> Result<FrameReport, RenderError> {
120 #[cfg(not(target_arch = "wasm32"))]
130 let render_started_at = self.clock_origin.elapsed();
131
132 if self.poll_pending_submission()? {
133 #[cfg(not(test))]
138 return Ok(self.frame_report(FrameStatus::Skipped, true));
139 }
140
141 self.prepare_frame(scene, camera)?;
142
143 let Some(frame) = self.acquire_surface_frame()? else {
144 return Ok(self.frame_report(FrameStatus::Skipped, false));
145 };
146
147 let mut encoder = self.device.create_command_encoder();
148
149 if !self.record_frame(&mut encoder, scene, frame.view()) {
150 return Ok(self.frame_report(FrameStatus::Skipped, false));
151 }
152
153 self.submit_frame(encoder);
154 self.device.check_errors()?;
155 frame.present();
156
157 #[cfg(not(target_arch = "wasm32"))]
160 self.adaptive.observe(duration_ns(
161 self.clock_origin
162 .elapsed()
163 .saturating_sub(render_started_at),
164 ));
165
166 Ok(self.frame_report(FrameStatus::Presented, false))
167 }
168
169 fn prepare_frame(&mut self, scene: &Scene, camera: &Camera) -> Result<(), RenderError> {
180 self.adaptive.set_atom_count(scene.atom_count());
181 self.sync_quality_tier();
182 self.device.check_errors()?;
183
184 self.chunk_residency.begin_epoch();
185 self.scene_gpu.begin_frame();
186
187 let scene_changed = self.sync_scene(scene)?;
188 let pool_rebuilt = self.rebuild_pool_if_needed()?;
189
190 self.scene_gpu
191 .settle_specializations(&self.device, scene, &self.passes);
192
193 let camera_changed = self.temporal.camera_changed(camera);
194 let scene_reset = self.update_temporal_scene_identity(scene);
195 let cinematic = self.tier() >= QualityTier::Standard;
196
197 let optics = self.resolve_optics(scene, camera)?;
198 let shadow =
199 self.shadow_bound
200 .fit(scene, camera, self.resolved_plan.lighting(), scene_changed);
201
202 let uniforms = self.temporal.prepare(
203 camera,
204 &TemporalOptions {
205 extent: [self.width, self.height],
206 reset: self.temporal_reset_required(scene_reset, pool_rebuilt, camera_changed),
207 quality: cinematic,
208 publication: false,
209 illustration: self.resolved_plan.illustration(),
210 depth_cue: self.resolved_plan.packed_depth_cue(),
211 optics,
212 motion_blur: self
213 .resolved_plan
214 .motion_blur()
215 .map_or([0.0; 4], MotionBlur::packed),
216 atmosphere: self
217 .resolved_plan
218 .packed_presentation(self.scene_gpu.has_translucency()),
219 lighting: self.resolved_plan.packed_lighting(),
220 shadow_view: shadow.view,
221 shadow_projection: shadow.projection,
222 shadow_view_proj: shadow.view_projection,
223 },
224 );
225
226 self.scene_gpu.write_frame_uniforms(&self.queue, &uniforms)
227 }
228
229 fn record_frame(
237 &mut self,
238 encoder: &mut D::CommandEncoder,
239 scene: &Scene,
240 swapchain: &D::TextureView,
241 ) -> bool {
242 let _ = scene;
244
245 let cinematic = self.tier() >= QualityTier::Standard;
246
247 self.record_scene_compute(encoder, cinematic);
248
249 let Some(pool) = &self.pool else {
250 return false;
251 };
252
253 let table = ResourceTable { pool, swapchain };
254
255 for &index in &self.order {
256 let Some(node) = self.pass_nodes.get(index) else {
257 continue;
258 };
259
260 let mut ctx = PassContext {
261 encoder: &mut *encoder,
262 resources: &table,
263 passes: &self.passes,
264 bindings: self.bindings.as_ref(),
265 scene: &self.scene_gpu,
266 timestamps: None,
267 temporal_write: self.temporal.write_index(),
268 quality: cinematic,
269 edge_smoothing: self.edge_smoothing(),
270 display_encoding: self.display_encoding(),
271 };
272
273 (node.record)(&mut ctx);
274 }
275
276 true
277 }
278
279 fn submit_frame(&mut self, encoder: D::CommandEncoder) {
287 self.last_submission_id = self.last_submission_id.wrapping_add(1);
288 self.last_submission_timestamp_ns = duration_ns(self.clock_origin.elapsed());
289 self.last_completion_timestamp_ns = None;
290
291 self.last_frame_submission = self.queue.submit_tracked(encoder);
292 self.frame_submission_pending = true;
293
294 #[cfg(target_arch = "wasm32")]
297 {
298 self.frame_submitted_at = Some(self.clock_origin.elapsed());
299 }
300 }
301
302 fn poll_pending_submission(&mut self) -> Result<bool, RenderError> {
325 let was_pending = self.frame_submission_pending;
326 let completed = self.queue.completed_fence(&self.device)?;
327
328 self.frame_submission_pending = completed < self.last_frame_submission;
329
330 if was_pending && !self.frame_submission_pending {
331 self.last_completion_timestamp_ns = Some(duration_ns(self.clock_origin.elapsed()));
332
333 #[cfg(target_arch = "wasm32")]
337 if let Some(submitted_at) = self.frame_submitted_at.take() {
338 self.adaptive.observe(duration_ns(
339 self.clock_origin.elapsed().saturating_sub(submitted_at),
340 ));
341 }
342 }
343
344 Ok(self.frame_submission_pending)
345 }
346
347 fn record_scene_compute(&mut self, encoder: &mut D::CommandEncoder, cinematic: bool) {
352 self.passes
353 .cull
354 .record_attribute_timelines(&self.scene_gpu, encoder);
355
356 self.passes
357 .cull
358 .record_instance_timelines(&self.scene_gpu, encoder);
359
360 let point_coordinates_changed = self
361 .passes
362 .cull
363 .record_point_timelines(&self.scene_gpu, encoder);
364
365 self.scene_gpu
366 .record_particle_motion(encoder, &self.passes.particle_motion);
367
368 let structure_coordinates_changed =
369 self.scene_gpu
370 .record_trajectories(encoder, &self.passes.trajectory, None);
371
372 let paged_coordinates_changed = self
373 .passes
374 .cull
375 .record_paged_trajectories(&self.scene_gpu, encoder);
376
377 let coordinates_changed =
378 structure_coordinates_changed || paged_coordinates_changed || point_coordinates_changed;
379
380 self.scene_gpu.record_dynamic_relations(
381 encoder,
382 &self.passes.relation_resolve,
383 coordinates_changed,
384 );
385
386 self.scene_gpu
387 .record_occupancies(encoder, self.passes.occupancy.as_ref());
388
389 self.scene_gpu.record_surface_fields(
390 encoder,
391 &self.passes.surface_field,
392 &self.passes.surface_components,
393 );
394
395 self.scene_gpu.record_quality_hardware(encoder, cinematic);
396 }
397
398 fn frame_report(&self, status: FrameStatus, fence_pending: bool) -> FrameReport {
410 let residency = self.chunk_residency.metrics();
411 let derived = self.derived_cache.usage();
412 let physical = self.device.resource_memory();
413 let pending = residency.uploads.active_tickets;
414
415 FrameReport {
416 status,
417 completeness: if pending == 0 {
418 FrameCompleteness::Complete
419 } else {
420 FrameCompleteness::Progressive {
421 pending_chunks: pending,
422 }
423 },
424 degradation: FrameDegradation::streaming_proxy(
425 self.mode == RenderMode::Realtime && pending != 0,
426 ),
427 metrics: FrameMetrics {
428 tracked_chunks: residency.tracked_chunks,
429 upload_in_flight_bytes: residency.uploads.in_flight_bytes,
430 pending_frame_submissions: pending_frame_submissions(self),
431 last_submission_id: self.last_submission_id,
432 submission_timestamp_ns: self.last_submission_timestamp_ns,
433 completion_timestamp_ns: self.last_completion_timestamp_ns,
434 derived_cache_gpu_bytes: derived.gpu_bytes,
435 derived_cache_peak_gpu_bytes: derived.peak_gpu_bytes,
436 physical_buffer_bytes: physical.buffer_bytes,
437 physical_texture_bytes: physical.texture_bytes,
438 physical_total_bytes: physical.total_bytes(),
439 physical_peak_bytes: physical.peak_bytes,
440 },
441 needs_another_frame: (status == FrameStatus::Skipped && !fence_pending) || pending != 0,
442 quality_tier: self.tier(),
443 }
444 }
445}