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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
// SPDX-License-Identifier: MIT
//! What one scene commit did.
//!
//! Port of `src/scene/commit_report.hpp`.
use drmkit_planes::LayerId;
/// How a layer reached scanout.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[non_exhaustive]
pub enum Placement {
/// On its own hardware plane.
AssignedToPlane,
/// Composited into the canvas plane.
Composited,
/// Dropped this frame — no plane available, no way to composite it, or the
/// canvas could not be allocated.
#[default]
Unassigned,
}
/// One layer's outcome.
///
/// The default is the safe one: unassigned, on no plane. A placement nothing
/// filled in describes a layer that did not reach the screen, which is a
/// smaller claim than naming a plane it does not have.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct LayerPlacement {
/// Which layer.
pub layer: LayerId,
/// How it reached scanout.
pub placement: Placement,
/// The plane its content scanned out on, or `None` when unassigned.
///
/// For a composited layer this is the **canvas** plane, not a plane of its
/// own.
pub plane_id: Option<u32>,
/// The assigned plane's supported rotate/reflect mask, for an
/// [`AssignedToPlane`](Placement::AssignedToPlane) layer; 0 otherwise.
///
/// Lets a producer decide whether its requested angle can land on the plane
/// it was given or needs pre-rotation.
pub plane_rotation_bits: u64,
}
impl Default for LayerPlacement {
/// Unassigned, on no plane, for no layer.
///
/// Written by hand rather than derived so `LayerId` does not have to gain
/// a `Default` it has no use for elsewhere -- an id is something the scene
/// hands out, and one a caller could conjure would be a way to address a
/// layer that does not exist.
fn default() -> Self {
Self {
layer: LayerId(0),
placement: Placement::Unassigned,
plane_id: None,
plane_rotation_bits: 0,
}
}
}
/// A snapshot of a single commit.
///
/// Every field is a count that starts at zero. `#[non_exhaustive]` because the
/// C++ has grown counters additively — `layers_skipped_no_frame`,
/// `fb_delta_fast_path`, the fence counters — and keeping that freedom means a
/// new counter is not a breaking change.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct CommitReport {
/// Layers in the scene at commit time.
///
/// # Accounting invariant
///
/// ```text
/// layers_total == layers_assigned
/// + layers_composited
/// + layers_unassigned
/// + layers_skipped_no_frame
/// ```
///
/// Checked by [`accounting_balances`](Self::accounting_balances). This is
/// why a layer with no format is never placed: counting it as both assigned
/// and skipped would break this.
pub layers_total: usize,
/// Layers the allocator put directly on a hardware plane.
pub layers_assigned: usize,
/// Layers rescued by composition into the canvas. They do reach hardware —
/// via the canvas plane rather than one of their own.
pub layers_composited: usize,
/// Layers neither placed nor composited: dropped this frame.
pub layers_unassigned: usize,
/// Layers whose source returned [`WouldBlock`](crate::SourceError::WouldBlock)
/// and were skipped.
///
/// Flow control, not failure — a live source with nothing to contribute
/// this vblank. Graphable as a per-layer frame-stall signal.
pub layers_skipped_no_frame: usize,
/// Composition buckets emitted. 0 or 1 with a single full-screen canvas.
pub composition_buckets: usize,
/// Layers that asked to be pinned to a plane but could not be.
///
/// The pin target was not on this CRTC, did not support the format, or was
/// already claimed. **The layer is not dropped** — it falls back to normal
/// allocation and is still counted in one of the placement tallies, so this
/// does not affect the accounting invariant. Nonzero is a loud signal that
/// a caller's deterministic-plane assumption was violated, as opposed to a
/// silent drop.
pub pin_requests_unhonored: usize,
/// Properties enqueued on the atomic request — plane, CRTC, and connector
/// state together.
///
/// A regression signal for property minimization: it should drop
/// meaningfully on a commit whose layers did not change.
pub properties_written: usize,
/// Subset of `properties_written` that are `FB_ID` attachments.
pub fbs_attached: usize,
/// Subset of `properties_written`: layers that got an `FB_DAMAGE_CLIPS`
/// blob.
///
/// Zero on a full-frame commit — no damage reported, over budget,
/// degenerate clips, or a plane without the property. Tells a
/// bandwidth-sensitive consumer whether partial updates actually reach the
/// kernel.
pub damaged_layers: usize,
/// Layers whose acquire fence was handed to the kernel as `IN_FENCE_FD` —
/// the real explicit-sync path, with no CPU stall.
///
/// The honest signal that explicit sync is live on this driver: a source
/// may always produce a fence and the plane still not accept it, which
/// shows up in [`in_fence_cpu_waits`](Self::in_fence_cpu_waits) instead.
pub in_fences_armed: usize,
/// Layers that carried a fence the plane could not take, so the scene
/// blocked on a CPU wait before committing.
///
/// Real commits only; a test never waits. Both counters zero means no
/// source produced a fence at all.
pub in_fence_cpu_waits: usize,
/// Internal test commits the allocator issued while searching.
///
/// A cold start can spend the whole budget; a warm start is 1; the FB-only
/// fast path is 0.
pub test_commits_issued: usize,
/// Whether this commit took the FB-only fast path — only content changed on
/// already-placed layers, so the cached assignment was reused and the
/// `TEST_ONLY` skipped entirely.
///
/// **Invariant 4's observable signal.** Implies
/// `test_commits_issued == 0`, which a steady-state consumer asserts to
/// confirm it is paying one atomic commit per frame rather than two.
pub fb_delta_fast_path: bool,
/// Names this commit for
/// [`LayerScene::release_displaced`](crate::LayerScene::release_displaced):
/// real commits that the kernel took count from 1. 0 for a test commit
/// and for a rejected one.
pub commit_seq: u64,
/// Whether the per-frame test-commit budget, not the hardware, is what
/// stopped more layers reaching planes.
///
/// When this is set, some of `layers_composited` is a capacity decision
/// rather than a plane shortage: there were compatible planes free and the
/// allocator ran out of permitted round trips to the kernel before it
/// could offer them. Composited layers still reach the screen, so this is
/// not an error -- but a caller tuning a scene needs to tell "this device
/// has no more planes" from "raise the budget", and `layers_composited`
/// alone cannot. Raise it with `Allocator::set_max_test_commits`.
pub budget_exhausted: bool,
/// Whether the frame was suppressed as idle -- no commit was issued at all.
///
/// Set only by a caller that gates on content changing, such as
/// `ScanoutBackend::present_if_changed`. **When it is set every other field
/// is zero**, because there was no commit to count: no layers were walked,
/// no properties written, no placements decided. A consumer reading
/// `layers_assigned == 0` without checking this would conclude the frame
/// failed to reach a plane, when in fact the previous frame is still on
/// screen and correct.
///
/// The first frame is never suppressed, whatever the caller says about
/// content: nothing is on screen yet, so "unchanged" describes nothing.
pub skipped_idle: bool,
/// Per-layer outcomes.
pub placements: Vec<LayerPlacement>,
}
impl CommitReport {
/// The report for a frame that was suppressed as idle.
///
/// Every count is zero because there was no commit to count, and
/// [`skipped_idle`](Self::skipped_idle) says why. A constructor rather
/// than a struct literal because this type is `#[non_exhaustive]`: a
/// caller outside this crate cannot build one, and the suppressed frame is
/// the one shape it legitimately needs to.
#[must_use]
pub fn suppressed() -> Self {
Self {
skipped_idle: true,
..Self::default()
}
}
/// What became of one layer this commit.
///
/// `None` when the layer was not in the frame at all -- it was never
/// added, it was removed, or the handle is stale. A stale one cannot
/// match by accident: [`LayerHandle::layer_id`](crate::LayerHandle::layer_id)
/// packs the slot's generation, so a recycled slot yields a different id
/// and a handle to the layer that used to live there resolves to nothing.
#[must_use]
pub fn placement_of(&self, layer: impl Into<LayerId>) -> Option<&LayerPlacement> {
let id = layer.into();
self.placements.iter().find(|entry| entry.layer == id)
}
/// Whether the layer went into the canvas rather than onto a plane of its
/// own.
///
/// False for a layer that was dropped as well as for one that got a
/// plane: composited means it reached the screen the slow way, and
/// unassigned means it did not reach the screen at all. A caller
/// throttling its repaints on this distinction wants them separated.
#[must_use]
pub fn was_composited(&self, layer: impl Into<LayerId>) -> bool {
self.placement_of(layer)
.is_some_and(|entry| entry.placement == Placement::Composited)
}
/// Whether the layer tallies add up to [`layers_total`](Self::layers_total).
///
/// The C++ states this as a comment on the field. Making it checkable means
/// the scene's own tests can assert it every commit, which is how a layer
/// counted twice — or not at all — gets caught at the point it happens.
#[must_use]
pub const fn accounting_balances(&self) -> bool {
self.layers_total
== self.layers_assigned
+ self.layers_composited
+ self.layers_unassigned
+ self.layers_skipped_no_frame
}
/// Whether the fast path's implied contract holds: taking it means issuing
/// no test commits.
#[must_use]
pub const fn fast_path_consistent(&self) -> bool {
!self.fb_delta_fast_path || self.test_commits_issued == 0
}
}
#[cfg(test)]
mod tests {
use super::{CommitReport, LayerPlacement, Placement};
use drmkit_planes::LayerId;
/// A report with one layer of each outcome.
fn report() -> CommitReport {
CommitReport {
placements: vec![
LayerPlacement {
layer: LayerId(1),
placement: Placement::AssignedToPlane,
plane_id: Some(31),
plane_rotation_bits: 0,
},
LayerPlacement {
layer: LayerId(2),
placement: Placement::Composited,
plane_id: Some(42),
plane_rotation_bits: 0,
},
LayerPlacement {
layer: LayerId(3),
placement: Placement::Unassigned,
plane_id: None,
plane_rotation_bits: 0,
},
],
..CommitReport::default()
}
}
#[test]
fn each_outcome_is_found_with_the_plane_it_landed_on() {
let report = report();
let assigned = report.placement_of(LayerId(1)).expect("assigned");
assert_eq!(assigned.placement, Placement::AssignedToPlane);
assert_eq!(assigned.plane_id, Some(31));
// A composited layer names the *canvas* plane, not one of its own --
// a caller reading this as "the layer got plane 42" would think it had
// a plane it does not have.
let composited = report.placement_of(LayerId(2)).expect("composited");
assert_eq!(composited.placement, Placement::Composited);
assert_eq!(composited.plane_id, Some(42));
let dropped = report.placement_of(LayerId(3)).expect("unassigned");
assert_eq!(dropped.placement, Placement::Unassigned);
assert_eq!(dropped.plane_id, None, "it reached no plane at all");
}
#[test]
fn a_layer_that_was_not_in_the_frame_has_no_placement() {
assert!(report().placement_of(LayerId(4)).is_none());
assert!(CommitReport::default().placement_of(LayerId(1)).is_none());
}
#[test]
fn a_recycled_slot_does_not_answer_for_the_layer_that_left_it() {
// The identity a handle resolves to packs the slot's generation, so
// the layer that took a freed slot has a different id and a stale
// handle finds nothing. Without that, a caller holding a handle to a
// removed layer would read the new occupant's outcome as its own.
let slot_1_generation_1 = LayerId((1 << 32) | 1);
let slot_1_generation_2 = LayerId((1 << 32) | 2);
assert_ne!(slot_1_generation_1, slot_1_generation_2);
let report = CommitReport {
placements: vec![LayerPlacement {
layer: slot_1_generation_2,
placement: Placement::AssignedToPlane,
plane_id: Some(31),
plane_rotation_bits: 0,
}],
..CommitReport::default()
};
assert!(report.placement_of(slot_1_generation_2).is_some());
assert!(report.placement_of(slot_1_generation_1).is_none());
}
#[test]
fn only_a_composited_layer_was_composited() {
let report = report();
assert!(!report.was_composited(LayerId(1)), "it got a plane");
assert!(report.was_composited(LayerId(2)));
// Dropped is not composited: it never reached the screen, and a
// caller treating the two alike would stop repainting a layer nobody
// can see.
assert!(!report.was_composited(LayerId(3)));
assert!(!report.was_composited(LayerId(9)), "absent");
}
}