1use super::{
12 Engine, FrameCompleteness, FrameDegradation, FrameMetrics, FrameReport, FrameStatus,
13 MotionBlur, QualityTier, RenderMode, TemporalOptions,
14};
15use crate::error::RenderError;
16use crate::graph::{DisplayEncoding, PassContext, ResourceTable, TransientPool, plan_aliases};
17use crate::passes::FrameBindings;
18use molgfx_core::Scene;
19use molgfx_gpu::Queue as _;
20use molgfx_gpu::{Device, Surface as _, SurfaceError, SurfaceFrame as _};
21use molgfx_math::Camera;
22use web_time::Instant;
25
26impl<D: Device> Engine<D> {
27 fn sync_scene(&mut self, scene: &Scene) -> Result<bool, RenderError> {
32 self.ensure_occupancy(scene)?;
33 let scene_changed = self.scene_gpu.sync(crate::scene_gpu::SceneSync {
34 device: &self.device,
35 queue: &self.queue,
36 scene,
37 quality: self.tier() >= QualityTier::Standard,
38 extent: [self.width, self.height],
39 ray_query_layout: self.passes.ambient_occlusion.ray_query_layout(),
40 derived_cache: &mut self.derived_cache,
41 derived_frame: self.derived_frame,
42 })?;
43 self.chunk_residency.sync_scene(
44 &mut self.scene_gpu,
45 &self.device,
46 &self.queue,
47 &mut self.derived_cache,
48 self.derived_frame,
49 )?;
50 self.derived_frame = self.derived_frame.wrapping_add(1);
51 if scene_changed || self.chunk_residency.metrics().uploads.active_tickets != 0 {
52 self.temporal.invalidate_convergence();
53 }
54 Ok(scene_changed)
55 }
56
57 pub fn render(&mut self, scene: &Scene, camera: &Camera) -> Result<FrameReport, RenderError> {
68 let frame_start = Instant::now();
69 self.sync_quality_tier();
70 self.device.check_errors()?;
71 self.chunk_residency.begin_epoch();
72 self.scene_gpu.begin_frame();
73 let scene_changed = self.sync_scene(scene)?;
75
76 let rebuild = self.rebuild_pool_if_needed()?;
78 self.scene_gpu
82 .settle_specializations(&self.device, scene, &self.passes);
83 let camera_changed = self.temporal.camera_changed(camera);
84 let identity = scene.cache_identity();
85 let scene_reset = self.temporal_scene_identity.replace(identity) != Some(identity);
86 let cinematic = self.tier() >= QualityTier::Standard;
87 let optics = self.resolve_optics(scene, camera)?;
88 let shadow =
89 self.shadow_bound
90 .fit(scene, camera, self.resolved_plan.lighting(), scene_changed);
91 let uniforms = self.temporal.prepare(
92 camera,
93 &TemporalOptions {
94 extent: [self.width, self.height],
95 reset: scene_reset
96 || rebuild
97 || (self.mode == RenderMode::Cinematic && camera_changed),
98 quality: cinematic,
99 publication: false,
100 illustration: self.resolved_plan.illustration(),
101 optics,
102 motion_blur: self
103 .resolved_plan
104 .motion_blur()
105 .map_or([0.0; 4], MotionBlur::packed),
106 atmosphere: self
107 .resolved_plan
108 .packed_presentation(self.scene_gpu.has_translucency()),
109 lighting: self.resolved_plan.packed_lighting(),
110 shadow_view: shadow.view,
111 shadow_projection: shadow.projection,
112 shadow_view_proj: shadow.view_projection,
113 },
114 );
115 self.scene_gpu
116 .write_frame_uniforms(&self.queue, &uniforms)?;
117 let frame = self.acquire_surface_frame()?;
118 let Some(frame) = frame else {
119 return Ok(self.frame_report(FrameStatus::Skipped));
121 };
122 let temporal_write = self.temporal.write_index();
123
124 let mut encoder = self.device.create_command_encoder();
126 self.scene_gpu
127 .settle_specializations(&self.device, scene, &self.passes);
128 self.record_scene_compute(&mut encoder, cinematic);
129 let Some(pool) = &self.pool else {
130 return Ok(self.frame_report(FrameStatus::Skipped));
131 };
132 {
133 let table = ResourceTable {
134 pool,
135 swapchain: frame.view(),
136 };
137 for &index in &self.order {
138 let Some(node) = self.pass_nodes.get(index) else {
139 continue;
140 };
141 let mut ctx = PassContext {
142 encoder: &mut encoder,
143 resources: &table,
144 passes: &self.passes,
145 bindings: self.bindings.as_ref(),
146 scene: &self.scene_gpu,
147 timestamps: None,
148 temporal_write,
149 quality: cinematic,
150 display_encoding: self.display_encoding(),
151 };
152 (node.record)(&mut ctx);
153 }
154 }
155
156 self.queue.submit(encoder);
158 self.device.check_errors()?;
159 frame.present();
160 let report = self.frame_report(FrameStatus::Presented);
163 let frame_time = frame_start.elapsed();
169 let elapsed = frame_time
170 .as_secs()
171 .saturating_mul(1_000_000_000)
172 .saturating_add(u64::from(frame_time.subsec_nanos()));
173 self.adaptive.observe(elapsed);
174 Ok(report)
175 }
176
177 fn record_scene_compute(&mut self, encoder: &mut D::CommandEncoder, cinematic: bool) {
178 self.passes
179 .cull
180 .record_attribute_timelines(&self.scene_gpu, encoder);
181 self.passes
182 .cull
183 .record_instance_timelines(&self.scene_gpu, encoder);
184 let point_coordinates_changed = self
185 .passes
186 .cull
187 .record_point_timelines(&self.scene_gpu, encoder);
188 self.scene_gpu
189 .record_particle_motion(encoder, &self.passes.particle_motion);
190 let structure_coordinates_changed =
191 self.scene_gpu
192 .record_trajectories(encoder, &self.passes.trajectory, None);
193 let paged_coordinates_changed = self
194 .passes
195 .cull
196 .record_paged_trajectories(&self.scene_gpu, encoder);
197 self.scene_gpu.record_dynamic_relations(
198 encoder,
199 &self.passes.relation_resolve,
200 structure_coordinates_changed || paged_coordinates_changed || point_coordinates_changed,
201 );
202 self.scene_gpu
203 .record_occupancies(encoder, self.passes.occupancy.as_ref());
204 self.scene_gpu.record_surface_fields(
205 encoder,
206 &self.passes.surface_field,
207 &self.passes.surface_components,
208 );
209 self.scene_gpu.record_quality_hardware(encoder, cinematic);
210 }
211
212 fn frame_report(&self, status: FrameStatus) -> FrameReport {
213 let residency = self.chunk_residency.metrics();
214 let derived = self.derived_cache.usage();
215 let physical = self.device.resource_memory();
216 let pending = residency.uploads.active_tickets;
217 FrameReport {
218 status,
219 completeness: if pending == 0 {
220 FrameCompleteness::Complete
221 } else {
222 FrameCompleteness::Progressive {
223 pending_chunks: pending,
224 }
225 },
226 degradation: FrameDegradation::streaming_proxy(
227 self.mode == RenderMode::Realtime && pending > 0,
228 ),
229 metrics: FrameMetrics {
230 tracked_chunks: residency.tracked_chunks,
231 upload_in_flight_bytes: residency.uploads.in_flight_bytes,
232 derived_cache_gpu_bytes: derived.gpu_bytes,
233 derived_cache_peak_gpu_bytes: derived.peak_gpu_bytes,
234 physical_buffer_bytes: physical.buffer_bytes,
235 physical_texture_bytes: physical.texture_bytes,
236 physical_total_bytes: physical.total_bytes(),
237 physical_peak_bytes: physical.peak_bytes,
238 },
239 needs_another_frame: status == FrameStatus::Skipped
240 || pending != 0
241 || self
242 .temporal
243 .needs_another_frame(self.tier().temporal_samples()),
244 quality_tier: self.tier(),
245 }
246 }
247
248 pub(super) fn display_encoding(&self) -> DisplayEncoding {
253 let display = self.resolved_plan.display();
254 DisplayEncoding {
255 gamut: display.gamut,
256 transfer: display.transfer,
257 }
258 }
259
260 pub(super) fn rebuild_pool_if_needed(&mut self) -> Result<bool, RenderError> {
261 let rebuild = self
262 .pool
263 .as_ref()
264 .is_none_or(|pool| !pool.matches(self.width, self.height));
265 if !rebuild {
266 return Ok(false);
267 }
268 let plan = plan_aliases(&self.resources, &self.pass_nodes, &self.order);
269 self.bindings = None;
272 self.pool = None;
273 self.temporal.reset();
274 self.pool = Some(TransientPool::build(
275 &self.device,
276 &self.resources,
277 plan,
278 self.width,
279 self.height,
280 )?);
281 self.bindings = self
282 .pool
283 .as_ref()
284 .and_then(|pool| FrameBindings::new(&self.device, pool, &self.passes));
285 Ok(true)
286 }
287
288 fn acquire_surface_frame(
289 &mut self,
290 ) -> Result<Option<<D::Surface as molgfx_gpu::Surface<D>>::Frame>, RenderError> {
291 let Some(surface) = &mut self.surface else {
292 return Ok(None);
293 };
294 match surface.acquire() {
295 Ok(frame) => Ok(Some(frame)),
296 Err(SurfaceError::Lost | SurfaceError::Outdated) => {
297 surface.configure(
298 &self.device,
299 &molgfx_gpu::SurfaceConfig {
300 width: self.width,
301 height: self.height,
302 format: self.target_format,
303 },
304 );
305 Ok(None)
306 }
307 Err(SurfaceError::Timeout) => Ok(None),
308 Err(error) => Err(RenderError::Gpu(error.into())),
309 }
310 }
311}