Skip to main content

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;