drmkit_present/frame_economy.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Deciding whether this vblank is worth a commit at all.
5
6/// What the present loop should do with this frame.
7///
8/// One enum rather than the reference's `{ FrameAction action; bool full; }`,
9/// where `full` is meaningless unless `action` is `Commit` and is set to
10/// `false` on a skip — a value a caller can read and act on. Three variants
11/// make the nonsensical combination unrepresentable, and leave the caller one
12/// thing to match.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum FrameAction {
16 /// Nothing changed. Issue no commit: no page flip, no scanout reprogram.
17 Skip,
18 /// Commit the whole frame, omitting `FB_DAMAGE_CLIPS`.
19 CommitFull,
20 /// Commit with the producer's damage rectangles attached.
21 CommitDamaged,
22}
23
24impl FrameAction {
25 /// Whether this action puts a commit on the wire.
26 #[must_use]
27 pub const fn commits(self) -> bool {
28 !matches!(self, Self::Skip)
29 }
30}
31
32/// The per-frame present decision, and what it has decided so far.
33///
34/// # The point of it
35///
36/// The win is the skip. When nothing has changed since the last commit, the
37/// present loop issues no atomic commit at all — no flip, no reprogram. That
38/// is power saved on every panel, and on a panel capable of self-refresh it is
39/// what lets the panel stay there.
40///
41/// So the skip is **unconditional** and consults no PSR capability. Not
42/// flipping is a win whether or not the panel can self-refresh, and gating it
43/// on a capability would mean burning power on the panels that advertise
44/// nothing — which is most of them.
45///
46/// # Threading
47///
48/// Not synchronized: one present loop owns it, the same one that owns the
49/// [`BufferRing`](crate::BufferRing).
50#[derive(Debug)]
51pub struct FrameEconomy {
52 first: bool,
53 force_full: bool,
54 committed: u64,
55 skipped: u64,
56}
57
58impl Default for FrameEconomy {
59 /// The same as [`new`](Self::new): the first decision commits in full.
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl FrameEconomy {
66 /// A fresh economy, whose first decision is always a full commit.
67 #[must_use]
68 pub const fn new() -> Self {
69 Self {
70 first: true,
71 force_full: false,
72 committed: 0,
73 skipped: 0,
74 }
75 }
76
77 /// Decide this frame.
78 ///
79 /// `content_changed` is whether a layer produced a new frame or the scene
80 /// mutated since the last committed one. `damage_available` is whether the
81 /// producer supplied a bounded damage rectangle set, so a damaged commit is
82 /// possible at all.
83 ///
84 /// The first decision after construction or [`force_full`](Self::force_full)
85 /// is always [`FrameAction::CommitFull`]: the scanout buffer's contents are
86 /// otherwise undefined, or a modeset has just discarded them.
87 pub const fn decide(&mut self, content_changed: bool, damage_available: bool) -> FrameAction {
88 if self.first || self.force_full {
89 self.first = false;
90 self.force_full = false;
91 self.committed += 1;
92 return FrameAction::CommitFull;
93 }
94 if !content_changed {
95 self.skipped += 1;
96 return FrameAction::Skip;
97 }
98 self.committed += 1;
99 if damage_available {
100 FrameAction::CommitDamaged
101 } else {
102 FrameAction::CommitFull
103 }
104 }
105
106 /// Make the next decision a full commit.
107 ///
108 /// For after a modeset, a mode change or a rebind — anything that leaves
109 /// the previous scanout contents no longer applying, so damage against them
110 /// would repaint the wrong difference. One-shot: it applies to the next
111 /// decision and then clears.
112 pub const fn force_full(&mut self) {
113 self.force_full = true;
114 }
115
116 /// How many frames have been committed.
117 #[must_use]
118 pub const fn committed(&self) -> u64 {
119 self.committed
120 }
121
122 /// How many frames have been skipped — the ones that cost nothing.
123 #[must_use]
124 pub const fn skipped(&self) -> u64 {
125 self.skipped
126 }
127}