Skip to main content

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}