Skip to main content

drmkit_planes/
prop.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! The closed set of KMS plane properties a [`Layer`](crate::Layer) carries.
5
6/// Property tags a layer recognizes.
7///
8/// The set is **closed**: every KMS plane property a layer carries lives
9/// here, and an unrecognized name handed to
10/// [`Layer::set_property_by_name`](crate::Layer::set_property_by_name) is
11/// dropped — a kernel commit would reject it with `EINVAL` anyway, so
12/// accepting it would only hide the typo. The scene writes a few per-plane
13/// extras, such as `COLOR_ENCODING` and `FB_DAMAGE_CLIPS`, from plane
14/// capabilities rather than from the layer.
15///
16/// Adding a property means adding a variant here and extending
17/// [`PropTag::name`] and [`PropTag::class`]. Both are exhaustive `match`es, so
18/// the compiler names every place that needs updating.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[repr(u8)]
21#[non_exhaustive]
22pub enum PropTag {
23    /// `FB_ID` — the framebuffer to scan out.
24    FbId,
25    /// `FB_MODIFIER` — the layout of that framebuffer.
26    FbModifier,
27    /// `CRTC_ID` — which CRTC the plane feeds.
28    CrtcId,
29    /// `CRTC_X` — destination x, in pixels.
30    CrtcX,
31    /// `CRTC_Y` — destination y, in pixels.
32    CrtcY,
33    /// `CRTC_W` — destination width, in pixels.
34    CrtcW,
35    /// `CRTC_H` — destination height, in pixels.
36    CrtcH,
37    /// `SRC_X` — source x, 16.16 fixed point.
38    SrcX,
39    /// `SRC_Y` — source y, 16.16 fixed point.
40    SrcY,
41    /// `SRC_W` — source width, 16.16 fixed point.
42    SrcW,
43    /// `SRC_H` — source height, 16.16 fixed point.
44    SrcH,
45    /// `rotation` — rotate/reflect bitmask.
46    Rotation,
47    /// `alpha` — per-plane alpha, u16.
48    Alpha,
49    /// `zpos` — stacking order.
50    Zpos,
51    /// `pixel_format` — the `FourCC`.
52    PixelFormat,
53    /// `IN_FENCE_FD` — a fence the flip waits on.
54    InFenceFd,
55}
56
57/// What a property describes, for the FB-only fast path.
58///
59/// This is the **corollary of invariant 4** the C++ can only state in prose:
60///
61/// > `property_hash` isolates content from placement — every placement or
62/// > format property moves the hash; `FB_ID` and `IN_FENCE_FD` do not. A
63/// > property misclassified as content would let a real change skip the test.
64///
65/// Here it is data, consumed by [`PropTag::class`]'s exhaustive `match`. A new
66/// variant added without a classification does not compile, so the failure the
67/// C++ warns about — a property silently treated as content — cannot reach a
68/// running system.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
70#[non_exhaustive]
71pub enum PropClass {
72    /// Changes every frame and does not affect plane compatibility.
73    ///
74    /// Excluded from [`Layer::property_hash`](crate::Layer::property_hash), so
75    /// a frame that changes only these can reuse the cached plane assignment
76    /// and skip the redundant `TEST_ONLY` commit.
77    Content,
78    /// Describes where or how the plane scans out.
79    ///
80    /// Any change here must move the hash, forcing a re-test. Getting this
81    /// wrong in the permissive direction — calling a placement property
82    /// content — is what would let a real geometry change skip validation.
83    Placement,
84}
85
86/// Number of distinct property tags.
87pub const NUM_PROPS: usize = PropTag::ALL.len();
88
89impl PropTag {
90    /// Every tag, in enum order.
91    ///
92    /// Listing them explicitly rather than deriving a count keeps the
93    /// exhaustiveness of [`PropTag::class`] meaningful: a variant inserted
94    /// without being added here breaks the index-contiguity check in
95    /// `every_prop_tag_is_classified`. One appended at the end is not caught,
96    /// so keep the array's size in step.
97    pub const ALL: [Self; 16] = [
98        Self::FbId,
99        Self::FbModifier,
100        Self::CrtcId,
101        Self::CrtcX,
102        Self::CrtcY,
103        Self::CrtcW,
104        Self::CrtcH,
105        Self::SrcX,
106        Self::SrcY,
107        Self::SrcW,
108        Self::SrcH,
109        Self::Rotation,
110        Self::Alpha,
111        Self::Zpos,
112        Self::PixelFormat,
113        Self::InFenceFd,
114    ];
115
116    /// Index into a layer's property array.
117    #[must_use]
118    pub const fn index(self) -> usize {
119        self as usize
120    }
121
122    /// The canonical KMS property name.
123    #[must_use]
124    pub const fn name(self) -> &'static str {
125        match self {
126            Self::FbId => "FB_ID",
127            Self::FbModifier => "FB_MODIFIER",
128            Self::CrtcId => "CRTC_ID",
129            Self::CrtcX => "CRTC_X",
130            Self::CrtcY => "CRTC_Y",
131            Self::CrtcW => "CRTC_W",
132            Self::CrtcH => "CRTC_H",
133            Self::SrcX => "SRC_X",
134            Self::SrcY => "SRC_Y",
135            Self::SrcW => "SRC_W",
136            Self::SrcH => "SRC_H",
137            Self::Rotation => "rotation",
138            Self::Alpha => "alpha",
139            Self::Zpos => "zpos",
140            Self::PixelFormat => "pixel_format",
141            Self::InFenceFd => "IN_FENCE_FD",
142        }
143    }
144
145    /// Whether this property describes content or placement.
146    ///
147    /// See [`PropClass`]. The `match` is exhaustive by construction: adding a
148    /// variant without classifying it is a compile error, which is the whole
149    /// reason this is a function rather than a lookup table with a default.
150    #[must_use]
151    pub const fn class(self) -> PropClass {
152        match self {
153            // Changes every frame: a new buffer, a new fence descriptor.
154            // Neither says anything about whether the plane can scan it out.
155            Self::FbId | Self::InFenceFd => PropClass::Content,
156
157            // Everything else describes where or how the plane scans out, so
158            // a change must invalidate the cached placement. `FB_MODIFIER`
159            // and `PixelFormat` belong here because a layout or format change
160            // can make a previously valid plane invalid.
161            Self::FbModifier
162            | Self::CrtcId
163            | Self::CrtcX
164            | Self::CrtcY
165            | Self::CrtcW
166            | Self::CrtcH
167            | Self::SrcX
168            | Self::SrcY
169            | Self::SrcW
170            | Self::SrcH
171            | Self::Rotation
172            | Self::Alpha
173            | Self::Zpos
174            | Self::PixelFormat => PropClass::Placement,
175        }
176    }
177
178    /// Look up a tag by its canonical KMS name.
179    ///
180    /// A linear scan over 16 entries: branchless, cache-friendly, and faster
181    /// than a hash map at this size — the same reasoning the C++ records.
182    #[must_use]
183    pub fn from_name(name: &str) -> Option<Self> {
184        Self::ALL.into_iter().find(|tag| tag.name() == name)
185    }
186}