drmkit_planes/zpos_order.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! The stacking rules for `zpos`: which planes can be written, where a layer
5//! actually stacks, and the dense numbering the allocator writes.
6//!
7//! Port of `src/planes/zpos_order.hpp` (drm-cxx `8bf20e6`, `1384526`).
8//!
9//! A mutable-zpos plane takes a written value, so it stacks where its layer
10//! asked relative to the others. A fixed-slot plane (`zpos_min == zpos_max`:
11//! i.MX LCDIF and vc4 primaries at 0, amdgpu's at 2) is never written and
12//! stacks at its slot whatever the layer asked for. Requiring the layer's zpos
13//! to equal the slot is stricter than needed -- on a single-plane controller
14//! it shuts every layer above 0 out of the only plane. What matters is relative
15//! order, and the kernel accepts an inverted stack without complaint, so a
16//! `TEST_ONLY` never catches one: [`stacking_consistent`] has to.
17
18use crate::registry::{PlaneCapabilities, PlaneRegistry};
19
20/// Whether the plane's zpos is a single fixed slot that cannot be written.
21#[must_use]
22pub fn zpos_fixed(plane: &PlaneCapabilities) -> bool {
23 matches!((plane.zpos_min, plane.zpos_max), (Some(min), Some(max)) if min == max)
24}
25
26/// Where a layer requesting `layer_zpos` stacks on `plane`: the fixed slot,
27/// else the layer's own written zpos. `None` when neither is known.
28#[must_use]
29pub fn effective_zpos(plane: &PlaneCapabilities, layer_zpos: Option<u64>) -> Option<u64> {
30 if zpos_fixed(plane) {
31 plane.zpos_min
32 } else {
33 layer_zpos
34 }
35}
36
37/// Whether layer A (zpos `za`) on `pa` and layer B (zpos `zb`) on `pb` stack in
38/// the order their zpos values request.
39///
40/// Layers without a zpos, or with equal ones, request no order. Different
41/// requests landing on the same effective slot are ambiguous -- the kernel
42/// breaks the tie by plane id -- and rejected. On mutable planes the effective
43/// zpos is the requested one, so this only ever rules on fixed-slot planes.
44#[must_use]
45pub fn stacking_consistent(
46 pa: &PlaneCapabilities,
47 za: Option<u64>,
48 pb: &PlaneCapabilities,
49 zb: Option<u64>,
50) -> bool {
51 let (Some(za), Some(zb)) = (za, zb) else {
52 return true;
53 };
54 if za == zb {
55 return true;
56 }
57 let (Some(ea), Some(eb)) = (effective_zpos(pa, Some(za)), effective_zpos(pb, Some(zb))) else {
58 // A plane without a zpos property: nothing to check against.
59 return true;
60 };
61 ea != eb && (za < zb) == (ea < eb)
62}
63
64/// One armed plane in a frame's stack.
65#[derive(Debug, Clone, Copy)]
66#[non_exhaustive]
67pub struct StackEntry<'a> {
68 /// The plane.
69 pub plane: &'a PlaneCapabilities,
70 /// The zpos its layer requests.
71 pub requested: Option<u64>,
72 /// The zpos to write, filled by [`stack_zpos`].
73 pub written: Option<u64>,
74 /// Stack under the other entries requesting the same zpos, whatever the
75 /// plane ids: the composition canvas's slot under the placed layer that
76 /// must cover it (drm-cxx `07226e4`).
77 pub below_ties: bool,
78}
79
80impl<'a> StackEntry<'a> {
81 /// An entry with nothing written yet.
82 #[must_use]
83 pub const fn new(plane: &'a PlaneCapabilities, requested: Option<u64>) -> Self {
84 Self {
85 plane,
86 requested,
87 written: None,
88 below_ties: false,
89 }
90 }
91}
92
93/// Number the armed planes' zpos densely, in requested order.
94///
95/// A mutable plane gets `max(previous + 1, zpos_min)`; a fixed-slot plane keeps
96/// its slot. Only the planes actually armed take values, so layers that end up
97/// composited do not use up the range, and the stack stays clear of its top,
98/// which some controllers advertise but refuse (the SA8155P advertises
99/// `[0, 10]` and rejects 9 and 10 even on a lone plane). Ties are broken by
100/// plane id, except that a `below_ties` entry goes under the others. Order is
101/// preserved, so [`stacking_consistent`]'s rulings stand.
102///
103/// Greedy-lowest is optimal, so when it overflows a plane's `zpos_max`, or meets
104/// a fixed slot at or below the previous value, no dense numbering exists:
105/// every `written` is left equal to `requested` and this returns `false`.
106/// Entries with no requested zpos or no zpos range are not numbered.
107pub fn stack_zpos(entries: &mut [StackEntry<'_>]) -> bool {
108 struct Rankable {
109 index: usize,
110 requested: u64,
111 after_ties: bool,
112 plane_id: u32,
113 min: u64,
114 max: u64,
115 fixed: bool,
116 }
117
118 let mut order = Vec::with_capacity(entries.len());
119 for (index, entry) in entries.iter_mut().enumerate() {
120 entry.written = entry.requested;
121 if let (Some(requested), Some(min), Some(max)) =
122 (entry.requested, entry.plane.zpos_min, entry.plane.zpos_max)
123 {
124 order.push(Rankable {
125 index,
126 requested,
127 after_ties: !entry.below_ties,
128 plane_id: entry.plane.id,
129 min,
130 max,
131 fixed: min == max,
132 });
133 }
134 }
135 order.sort_by_key(|rank| (rank.requested, rank.after_ties, rank.plane_id));
136
137 let mut values = Vec::with_capacity(order.len());
138 let mut previous: Option<u64> = None;
139 for rank in &order {
140 let value = match previous {
141 Some(previous) if rank.fixed && rank.min <= previous => return false,
142 Some(previous) if !rank.fixed => previous.saturating_add(1).max(rank.min),
143 _ => rank.min,
144 };
145 if value > rank.max {
146 return false;
147 }
148 values.push(value);
149 previous = Some(value);
150 }
151 for (rank, value) in order.iter().zip(values) {
152 entries[rank.index].written = Some(value);
153 }
154 true
155}
156
157/// The zpos to write on each armed plane, as `(plane_id, value)`.
158///
159/// `armed` is every plane the frame programs -- allocated, pinned and the
160/// composition canvas alike -- with the zpos its layer requests. One function
161/// for the frame that is committed and the assignments that are tested, so the
162/// kernel is never asked about one stack and handed another. Planes with no
163/// requested zpos are left out, and one with no zpos range keeps its requested
164/// value; when no dense numbering fits, every rankable plane gets its
165/// requested value and the `TEST_ONLY` decides.
166#[must_use]
167pub fn stacked_zpos(registry: &PlaneRegistry, armed: &[(u32, Option<u64>)]) -> Vec<(u32, u64)> {
168 let mut entries = Vec::with_capacity(armed.len());
169 let mut ids = Vec::with_capacity(armed.len());
170 for &(plane_id, requested) in armed {
171 if let Some(plane) = registry.by_id(plane_id) {
172 entries.push(StackEntry::new(plane, requested));
173 ids.push(plane_id);
174 }
175 }
176 // On overflow every `written` is the requested value, which is what goes
177 // out; the return value only says the numbering could not be dense.
178 let _ = stack_zpos(&mut entries);
179 ids.into_iter()
180 .zip(entries)
181 .filter_map(|(id, entry)| entry.written.map(|value| (id, value)))
182 .collect()
183}
184
185/// [`stacked_zpos`] with `beneath` stacked under the planes requesting the
186/// same zpos: the composition canvas in the slot
187/// [`Allocator::canvas_zpos`](crate::Allocator::canvas_zpos) gives it.
188///
189/// `None` when no dense numbering fits. Unlike [`stacked_zpos`], which then
190/// writes the requested values, the slot asks for a placed layer's own zpos,
191/// so the requested values tie, and the caller stacks the canvas on top
192/// instead (upstream drops the slot the same way).
193#[must_use]
194pub fn stacked_zpos_beneath(
195 registry: &PlaneRegistry,
196 armed: &[(u32, Option<u64>)],
197 beneath: u32,
198) -> Option<Vec<(u32, u64)>> {
199 let mut entries = Vec::with_capacity(armed.len());
200 let mut ids = Vec::with_capacity(armed.len());
201 for &(plane_id, requested) in armed {
202 if let Some(plane) = registry.by_id(plane_id) {
203 let mut entry = StackEntry::new(plane, requested);
204 entry.below_ties = plane_id == beneath;
205 entries.push(entry);
206 ids.push(plane_id);
207 }
208 }
209 stack_zpos(&mut entries).then(|| {
210 ids.into_iter()
211 .zip(entries)
212 .filter_map(|(id, entry)| entry.written.map(|value| (id, value)))
213 .collect()
214 })
215}