smudge_platform/present.rs
1//! Copying the renderer's surface into the window buffer, and choosing how
2//! much of the surface a present has to carry.
3
4use std::cell::RefCell;
5use std::rc::Rc;
6use std::time::Duration;
7
8use smudge_core::geometry::PixelRect;
9
10/// How many past frames the damage ring keeps.
11///
12/// Softbuffer reports ages of at most 3 for the double and triple buffered
13/// backends this library targets, so a longer ring could never be read.
14const HISTORY: usize = 3;
15
16/// What the last present did.
17///
18/// The platform records one of these after every frame it presents and, when
19/// the window was opened with a [`PresentStatsHandle`], publishes it there for
20/// the application to read.
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
22pub struct PresentStats {
23 /// The buffer age the platform reported, in presents.
24 ///
25 /// An age of 0 means the buffer contents were unknown and the present
26 /// copied the whole surface.
27 pub buffer_age: u8,
28 /// Whether the present carried only the damaged region.
29 ///
30 /// A present without damage copied and presented the whole surface.
31 pub with_damage: bool,
32 /// How many surface bytes the present copied from the renderer's pixmap.
33 ///
34 /// Four bytes per pixel, so a full present of a 100 by 100 surface copies
35 /// 40,000 bytes. The GPU path reports none: it draws straight onto the
36 /// window surface and copies no pixmap.
37 pub bytes_copied: u64,
38 /// The width of the presented surface in physical pixels.
39 pub surface_width: u32,
40 /// The height of the presented surface in physical pixels.
41 pub surface_height: u32,
42 /// How long the renderer took to paint this frame's damage into its
43 /// surface.
44 ///
45 /// On the GPU path this is the whole draw, whose submission reaches the
46 /// GPU asynchronously.
47 pub raster: Duration,
48 /// How long the platform waited for a free window buffer before the
49 /// present could start.
50 ///
51 /// A compositor that still holds the previous buffers makes this wait,
52 /// often for up to one display refresh. It is time spent blocked, not
53 /// work.
54 pub acquire: Duration,
55 /// How long the present took, from holding a free window buffer to handing
56 /// it back to the platform: the copy and the submission, without the wait
57 /// that [`PresentStats::acquire`] holds.
58 pub present: Duration,
59 /// Which renderer presented the frame, as its name: `cpu` or `gpu`.
60 ///
61 /// Empty until the first present.
62 pub renderer: &'static str,
63}
64
65/// A shared slot the platform writes the latest [`PresentStats`] into.
66///
67/// Create one, clone it into the `stats` field of the window options, and keep
68/// the original to read the statistics of the most recent present while the
69/// window runs. The handle reports zeros until the first present.
70///
71/// # Examples
72///
73/// ```
74/// use smudge_platform::PresentStatsHandle;
75///
76/// let handle = PresentStatsHandle::new();
77/// let reader = handle.clone();
78/// assert_eq!(reader.get().buffer_age, 0);
79/// ```
80#[derive(Clone, Debug, Default)]
81pub struct PresentStatsHandle {
82 /// The statistics of the most recent present.
83 slot: Rc<RefCell<PresentStats>>,
84 /// The GPU adapter the window presents through, reported once when the
85 /// GPU path is created.
86 #[cfg(feature = "gpu")]
87 adapter: Rc<RefCell<Option<crate::AdapterSummary>>>,
88}
89
90impl PresentStatsHandle {
91 /// Creates a handle that reports zeros until the first present.
92 pub fn new() -> PresentStatsHandle {
93 PresentStatsHandle::default()
94 }
95
96 /// Returns the statistics of the most recent present.
97 pub fn get(&self) -> PresentStats {
98 *self.slot.borrow()
99 }
100
101 /// Publishes the statistics of the most recent present.
102 pub(crate) fn store(&self, stats: PresentStats) {
103 *self.slot.borrow_mut() = stats;
104 }
105
106 /// Returns the GPU adapter the window presents through, when it knows
107 /// one.
108 ///
109 /// The platform reports the adapter once, when it sets the GPU
110 /// presentation path up, and only when the window was opened with a
111 /// handle in its options. A window on the CPU renderer reports `None`,
112 /// which is how a reader tells a GPU window from a CPU one.
113 #[cfg(feature = "gpu")]
114 pub fn adapter(&self) -> Option<crate::AdapterSummary> {
115 self.adapter.borrow().clone()
116 }
117
118 /// Reports the adapter of the GPU presentation path.
119 #[cfg(feature = "gpu")]
120 pub(crate) fn set_adapter(&self, adapter: crate::AdapterSummary) {
121 *self.adapter.borrow_mut() = Some(adapter);
122 }
123}
124
125/// Copies one rectangle of the surface into the window buffer.
126///
127/// `source` is the renderer's pixmap as premultiplied RGBA8, row-major with no
128/// padding between rows, `source_width` pixels per row. `destination` is the
129/// window buffer, one `u32` per pixel, with the same width as the pixmap. Each
130/// destination entry becomes `0x00RRGGBB`: the alpha channel is dropped, so a
131/// non-opaque clear color is unsupported and the copy would lose it. Pixels
132/// outside `rect` are left alone.
133///
134/// # Panics
135///
136/// Panics when `rect` reaches outside the surface, which means the frame was
137/// produced for a surface of a different size. That is a broken invariant of
138/// the caller, not input from outside the library.
139pub(crate) fn copy_rect(
140 source: &[u8],
141 source_width: u32,
142 destination: &mut [u32],
143 rect: PixelRect,
144) {
145 let stride = source_width as usize;
146 let width = rect.width as usize;
147 for y in rect.y..rect.y + rect.height {
148 let source_row = &source[(y as usize * stride + rect.x as usize) * 4..][..width * 4];
149 let destination_row = &mut destination[y as usize * stride + rect.x as usize..][..width];
150 for (pixel, rgba) in destination_row
151 .iter_mut()
152 .zip(source_row.as_chunks::<4>().0)
153 {
154 *pixel = u32::from(rgba[0]) << 16 | u32::from(rgba[1]) << 8 | u32::from(rgba[2]);
155 }
156 }
157}
158
159/// Blends the damage overlay's tint over the frame's damage rectangles.
160///
161/// `buffer` holds one `0x00RRGGBB` entry per pixel with `surface_width` entries
162/// per row, as [`copy_rect`] writes it. Only the pixels inside `rects` change.
163/// The tint lives in the window buffer alone: the next present copies the whole
164/// pixmap again, so no tint reaches the renderer's pixmap and none survives in
165/// a reused window buffer.
166///
167/// # Panics
168///
169/// Panics when a rect reaches outside the surface, which means the frame was
170/// produced for a surface of a different size. That is a broken invariant of
171/// the caller, not input from outside the library.
172pub(crate) fn tint_rects(buffer: &mut [u32], surface_width: u32, rects: &[PixelRect]) {
173 for rect in rects {
174 let row_width = rect.width as usize;
175 for y in rect.y..rect.y + rect.height {
176 let row =
177 &mut buffer[y as usize * surface_width as usize + rect.x as usize..][..row_width];
178 for pixel in row {
179 let red = tinted(*pixel >> 16 & 0xff, TINT_RGB[0]);
180 let green = tinted(*pixel >> 8 & 0xff, TINT_RGB[1]);
181 let blue = tinted(*pixel & 0xff, TINT_RGB[2]);
182 *pixel = red << 16 | green << 8 | blue;
183 }
184 }
185 }
186}
187
188/// The color the damage overlay tints with, as 8-bit sRGB channels.
189const TINT_RGB: [u8; 3] = [255, 64, 64];
190
191/// How strongly the damage overlay tints, out of 255.
192const TINT_ALPHA: u32 = 96;
193
194/// Blends one color channel toward the tint by the tint's opacity.
195fn tinted(channel: u32, tint: u8) -> u32 {
196 (channel * (255 - TINT_ALPHA) + u32::from(tint) * TINT_ALPHA + 127) / 255
197}
198
199/// The damage of the last few frames.
200///
201/// A buffer age of `N` means the window buffer holds the frame from `N`
202/// presents ago. The pixels that differ between that frame and the current one
203/// are exactly the damage of the current frame together with the damage of the
204/// previous `N - 1` frames, so a present that copies that union updates only
205/// the pixels the buffer is missing. The ring holds the frames an age of 1 to 3
206/// can ask about and reuses the vectors between frames.
207pub(crate) struct DamageHistory {
208 /// The recorded frames, most recent first.
209 entries: [Entry; HISTORY],
210 /// How many entries hold a presented frame.
211 filled: usize,
212}
213
214/// The recorded damage of one past frame.
215struct Entry {
216 /// The rectangles the frame changed.
217 damage: Vec<PixelRect>,
218 /// Whether the frame repainted the whole surface.
219 full: bool,
220}
221
222impl Default for DamageHistory {
223 fn default() -> DamageHistory {
224 DamageHistory {
225 entries: [const {
226 Entry {
227 damage: Vec::new(),
228 full: false,
229 }
230 }; HISTORY],
231 filled: 0,
232 }
233 }
234}
235
236impl DamageHistory {
237 /// Records a presented frame and drops the oldest entry.
238 pub(crate) fn push(&mut self, damage: &[PixelRect], full: bool) {
239 self.entries.rotate_right(1);
240 let entry = &mut self.entries[0];
241 entry.damage.clear();
242 entry.damage.extend_from_slice(damage);
243 entry.full = full;
244 self.filled = (self.filled + 1).min(HISTORY);
245 }
246}
247
248/// Whether the frame the last redraw rendered must skip its present.
249///
250/// A frame with no damage while the app runs a tick changed nothing, and the
251/// window still shows what the last present left, so a present would copy the
252/// whole surface only to write the same pixels again. A frame with no damage
253/// without a tick is how the window system asks for a redraw, for example
254/// after an X11 window was uncovered, and the full present it gets restores
255/// the window's contents. While the damage overlay is on, no present is
256/// skipped, so the previous frame's tint never outlives that frame.
257pub(crate) fn present_skipped(damage: &[PixelRect], has_tick: bool, show_damage: bool) -> bool {
258 damage.is_empty() && has_tick && !show_damage
259}
260
261/// Chooses the region a present of `age` has to copy, writing it to `out`.
262///
263/// `current` is the damage of the frame being presented and `current_full`
264/// says whether it repainted the whole surface. Returns `true` when `out`
265/// holds the rectangles to copy and to present with damage, and `false` when
266/// the whole pixmap must be copied and presented instead. The whole-pixmap
267/// cases are an age outside 1 to 3, a full repaint of the current frame, a
268/// current frame with no damage, a full repaint within the frames the region
269/// covers, or history that does not reach back far enough.
270///
271/// A past rectangle that a rectangle already in `out` contains is skipped, so a
272/// region that changes on every frame is copied once rather than once per
273/// frame the buffer missed. Measured on a 1270 by 1388 window at age 2, a frame
274/// whose damage covered the whole surface copied 14.1 MB and took 1525
275/// microseconds to present, against 716 microseconds for a full present.
276/// Rectangles that only partly overlap are both kept, and writing a pixel twice
277/// changes nothing.
278///
279/// A frame with no damage is mostly a redraw the window system asked for,
280/// such as an X11 window being shown again after it was minimized or covered.
281/// Without a compositor such a window has lost its contents, while the
282/// buffer age still reports the buffer as current, so only a full present
283/// restores it.
284pub(crate) fn damage_region(
285 age: u8,
286 current: &[PixelRect],
287 current_full: bool,
288 history: &DamageHistory,
289 out: &mut Vec<PixelRect>,
290) -> bool {
291 out.clear();
292 if !(1..=HISTORY as u8).contains(&age) || current_full || current.is_empty() {
293 return false;
294 }
295 let previous = age as usize - 1;
296 if previous > history.filled || history.entries[..previous].iter().any(|entry| entry.full) {
297 return false;
298 }
299 out.extend_from_slice(current);
300 for entry in &history.entries[..previous] {
301 for &rect in &entry.damage {
302 if !out.iter().any(|kept| kept.contains_rect(rect)) {
303 out.push(rect);
304 }
305 }
306 }
307 true
308}
309
310#[cfg(test)]
311#[path = "present_tests.rs"]
312mod tests;