use super::{
Engine, FrameCompleteness, FrameDegradation, FrameMetrics, FrameReport, FrameStatus,
MotionBlur, QualityTier, RenderMode, TemporalOptions,
};
use crate::error::RenderError;
use crate::graph::{DisplayEncoding, PassContext, ResourceTable, TransientPool, plan_aliases};
use crate::passes::FrameBindings;
use molgfx_core::Scene;
use molgfx_gpu::Queue as _;
use molgfx_gpu::{Device, Surface as _, SurfaceError, SurfaceFrame as _};
use molgfx_math::Camera;
use web_time::Instant;
impl<D: Device> Engine<D> {
fn sync_scene(&mut self, scene: &Scene) -> Result<bool, RenderError> {
let scene_changed = self.scene_gpu.sync(crate::scene_gpu::SceneSync {
device: &self.device,
queue: &self.queue,
scene,
quality: self.tier() >= QualityTier::Standard,
extent: [self.width, self.height],
ray_query_layout: self.passes.ambient_occlusion.ray_query_layout(),
derived_cache: &mut self.derived_cache,
derived_frame: self.derived_frame,
})?;
self.chunk_residency.sync_scene(
&mut self.scene_gpu,
&self.device,
&self.queue,
&mut self.derived_cache,
self.derived_frame,
)?;
self.derived_frame = self.derived_frame.wrapping_add(1);
if scene_changed || self.chunk_residency.metrics().uploads.active_tickets != 0 {
self.temporal.invalidate_convergence();
}
Ok(scene_changed)
}
pub fn render(&mut self, scene: &Scene, camera: &Camera) -> Result<FrameReport, RenderError> {
let frame_start = Instant::now();
self.sync_quality_tier();
self.device.check_errors()?;
self.chunk_residency.begin_epoch();
self.scene_gpu.begin_frame();
let scene_changed = self.sync_scene(scene)?;
let rebuild = self.rebuild_pool_if_needed()?;
self.scene_gpu
.settle_specializations(&self.device, scene, &self.passes);
let camera_changed = self.temporal.camera_changed(camera);
let identity = scene.cache_identity();
let scene_reset = self.temporal_scene_identity.replace(identity) != Some(identity);
let cinematic = self.tier() >= QualityTier::Standard;
let optics = self.resolve_optics(scene, camera)?;
let shadow =
self.shadow_bound
.fit(scene, camera, self.resolved_plan.lighting(), scene_changed);
let uniforms = self.temporal.prepare(
camera,
&TemporalOptions {
extent: [self.width, self.height],
reset: scene_reset
|| rebuild
|| (self.mode == RenderMode::Cinematic && camera_changed),
quality: cinematic,
publication: false,
illustration: self.resolved_plan.illustration(),
optics,
motion_blur: self
.resolved_plan
.motion_blur()
.map_or([0.0; 4], MotionBlur::packed),
atmosphere: self
.resolved_plan
.packed_presentation(self.scene_gpu.has_translucency()),
lighting: self.resolved_plan.packed_lighting(),
shadow_view: shadow.view,
shadow_projection: shadow.projection,
shadow_view_proj: shadow.view_projection,
},
);
self.scene_gpu
.write_frame_uniforms(&self.queue, &uniforms)?;
let frame = self.acquire_surface_frame()?;
let Some(frame) = frame else {
return Ok(self.frame_report(FrameStatus::Skipped));
};
let temporal_write = self.temporal.write_index();
let mut encoder = self.device.create_command_encoder();
self.scene_gpu
.settle_specializations(&self.device, scene, &self.passes);
self.record_scene_compute(&mut encoder, cinematic);
let Some(pool) = &self.pool else {
return Ok(self.frame_report(FrameStatus::Skipped));
};
{
let table = ResourceTable {
pool,
swapchain: frame.view(),
};
for &index in &self.order {
let Some(node) = self.pass_nodes.get(index) else {
continue;
};
let mut ctx = PassContext {
encoder: &mut encoder,
resources: &table,
passes: &self.passes,
bindings: self.bindings.as_ref(),
scene: &self.scene_gpu,
timestamps: None,
temporal_write,
quality: cinematic,
display_encoding: self.display_encoding(),
};
(node.record)(&mut ctx);
}
}
self.queue.submit(encoder);
self.device.check_errors()?;
frame.present();
let report = self.frame_report(FrameStatus::Presented);
let frame_time = frame_start.elapsed();
let elapsed = frame_time
.as_secs()
.saturating_mul(1_000_000_000)
.saturating_add(u64::from(frame_time.subsec_nanos()));
self.adaptive.observe(elapsed);
Ok(report)
}
fn record_scene_compute(&mut self, encoder: &mut D::CommandEncoder, cinematic: bool) {
self.passes
.cull
.record_attribute_timelines(&self.scene_gpu, encoder);
self.passes
.cull
.record_instance_timelines(&self.scene_gpu, encoder);
let point_coordinates_changed = self
.passes
.cull
.record_point_timelines(&self.scene_gpu, encoder);
self.scene_gpu
.record_particle_motion(encoder, &self.passes.particle_motion);
let structure_coordinates_changed =
self.scene_gpu
.record_trajectories(encoder, &self.passes.trajectory, None);
let paged_coordinates_changed = self
.passes
.cull
.record_paged_trajectories(&self.scene_gpu, encoder);
self.scene_gpu.record_dynamic_relations(
encoder,
&self.passes.relation_resolve,
structure_coordinates_changed || paged_coordinates_changed || point_coordinates_changed,
);
self.scene_gpu
.record_occupancies(encoder, &self.passes.occupancy);
self.scene_gpu.record_surface_fields(
encoder,
&self.passes.surface_field,
&self.passes.surface_components,
);
self.scene_gpu.record_quality_hardware(encoder, cinematic);
}
fn frame_report(&self, status: FrameStatus) -> FrameReport {
let residency = self.chunk_residency.metrics();
let derived = self.derived_cache.usage();
let physical = self.device.resource_memory();
let pending = residency.uploads.active_tickets;
FrameReport {
status,
completeness: if pending == 0 {
FrameCompleteness::Complete
} else {
FrameCompleteness::Progressive {
pending_chunks: pending,
}
},
degradation: FrameDegradation::streaming_proxy(
self.mode == RenderMode::Realtime && pending > 0,
),
metrics: FrameMetrics {
tracked_chunks: residency.tracked_chunks,
upload_in_flight_bytes: residency.uploads.in_flight_bytes,
derived_cache_gpu_bytes: derived.gpu_bytes,
derived_cache_peak_gpu_bytes: derived.peak_gpu_bytes,
physical_buffer_bytes: physical.buffer_bytes,
physical_texture_bytes: physical.texture_bytes,
physical_total_bytes: physical.total_bytes(),
physical_peak_bytes: physical.peak_bytes,
},
needs_another_frame: status == FrameStatus::Skipped
|| pending != 0
|| self
.temporal
.needs_another_frame(self.tier().temporal_samples()),
quality_tier: self.tier(),
}
}
pub(super) fn display_encoding(&self) -> DisplayEncoding {
let display = self.resolved_plan.display();
DisplayEncoding {
gamut: display.gamut,
transfer: display.transfer,
}
}
pub(super) fn rebuild_pool_if_needed(&mut self) -> Result<bool, RenderError> {
let rebuild = self
.pool
.as_ref()
.is_none_or(|pool| !pool.matches(self.width, self.height));
if !rebuild {
return Ok(false);
}
let plan = plan_aliases(&self.resources, &self.pass_nodes, &self.order);
self.bindings = None;
self.pool = None;
self.temporal.reset();
self.pool = Some(TransientPool::build(
&self.device,
&self.resources,
plan,
self.width,
self.height,
)?);
self.bindings = self
.pool
.as_ref()
.and_then(|pool| FrameBindings::new(&self.device, pool, &self.passes));
Ok(true)
}
fn acquire_surface_frame(
&mut self,
) -> Result<Option<<D::Surface as molgfx_gpu::Surface<D>>::Frame>, RenderError> {
let Some(surface) = &mut self.surface else {
return Ok(None);
};
match surface.acquire() {
Ok(frame) => Ok(Some(frame)),
Err(SurfaceError::Lost | SurfaceError::Outdated) => {
surface.configure(
&self.device,
&molgfx_gpu::SurfaceConfig {
width: self.width,
height: self.height,
format: self.target_format,
},
);
Ok(None)
}
Err(SurfaceError::Timeout) => Ok(None),
Err(error) => Err(RenderError::Gpu(error.into())),
}
}
}