1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Presentation resources: the display encoding, the transient pool sized to
//! the surface, and acquiring the next surface frame.
use super::Engine;
use crate::error::RenderError;
use crate::graph::{DisplayEncoding, TransientPool, plan_aliases};
use crate::passes::FrameBindings;
use molgfx_gpu::{Device, Surface as _, SurfaceError};
impl<D: Device> Engine<D> {
/// Whether the tonemap pass smooths edges this frame.
///
/// A profile that states a choice wins. Otherwise the choice follows
/// convergence, not quality: only a path that accumulates sub-pixel
/// coverage is already smooth, and the realtime path is not, so it smooths
/// whatever tier it holds. Keying this to the tier instead left small
/// interactive scenes — which reach the highest tier — with hard,
/// stair-stepped silhouettes at every zoom.
#[inline]
pub(super) fn edge_smoothing(&self) -> bool {
match self.resolved_plan.antialias() {
Some(style) => style.edge_smoothing,
None => !self.adaptive.converged(),
}
}
/// The display encoding the tonemap pass writes for this frame.
///
/// Selects a pre-built tonemap pipeline rather than a per-pixel branch, so
/// the encoding is fixed for the whole frame by construction.
#[inline]
pub(super) fn display_encoding(&self) -> DisplayEncoding {
let display = self.resolved_plan.display();
DisplayEncoding {
gamut: display.gamut,
transfer: display.transfer,
}
}
/// Rebuilds transient render resources when the presentation extent changes.
///
/// Existing bindings and the old pool are released before allocating the
/// replacement pool, minimizing peak RSS during resize.
///
/// Returns `true` when a rebuild occurred.
///
/// # Errors
///
/// Returns an error when the transient pool cannot be built.
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);
// Old views keep their textures alive. Release bindings first so a
// resize only reserves the new pool, including a large-to-small resize.
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)
}
/// Acquires the next presentation frame and handles recoverable surfaces.
///
/// Lost and outdated surfaces are reconfigured and reported as `None`.
/// Timeouts are also reported as `None`, allowing the caller to skip the
/// current frame rather than failing or blocking.
///
/// # Errors
///
/// Returns an error for surface failures other than lost, outdated, or
/// timeout conditions.
pub(super) 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())),
}
}
}