drmkit_scene/display.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! How the scene displays a layer's buffer.
5//!
6//! Port of the geometry core of `src/scene/display_params.hpp`.
7//!
8//! Separated from [`SourceFormat`](crate::SourceFormat) along the KMS concept
9//! boundary: the source describes what the buffer **is**, these describe how the
10//! plane scanning it out should be **configured**. The same buffer can be shown
11//! several ways, and the same plane configuration can scan different buffers.
12//!
13//! # Scope
14//!
15//! The geometry every commit needs, the container colorimetry and transfer
16//! function the connector's signaling is derived from, and the YCbCr
17//! encoding and range a plane converts with. The C++'s per-plane
18//! color-pipeline fields (`degamma_tf`, `hdr_mult`, the shaper and blend
19//! stages) are not ported.
20
21pub use drmkit_planes::{ColorEncoding, ColorRange, Rect};
22
23/// A source rectangle in 16.16 fixed point: the kernel's own `SRC_*`
24/// encoding, for a crop at sub-pixel precision. A Wayland viewport's source
25/// rectangle, for one, is in 1/256ths of a pixel, and rounding it moves the
26/// image by up to half a pixel and changes its scale. 1.0 is `0x10000`.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28#[allow(
29 clippy::exhaustive_structs,
30 reason = "a value defined by its fields; there is nothing to add to it"
31)]
32pub struct FixedRect {
33 /// Left edge.
34 pub x: u32,
35 /// Top edge.
36 pub y: u32,
37 /// Width; zero means the buffer's full width.
38 pub w: u32,
39 /// Height; zero means the buffer's full height.
40 pub h: u32,
41}
42
43/// How a layer should be displayed.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
45#[allow(
46 clippy::exhaustive_structs,
47 reason = "built by callers with `..Default::default()`, which a new field does not break"
48)]
49pub struct DisplayParams {
50 /// Region of the **buffer** to show.
51 ///
52 /// A zero width or height means "the buffer's full extent" — see
53 /// [`lower_layer`](crate::lower_layer), where that resolution happens.
54 pub src_rect: Rect,
55
56 /// The source crop at sub-pixel precision.
57 ///
58 /// When set it takes precedence over [`src_rect`](Self::src_rect) and is
59 /// written to `SRC_X`/`Y`/`W`/`H` as given; a zero width or height still
60 /// means the buffer's full extent. The composition fallback, which
61 /// samples whole pixels, rounds it. `None`: `src_rect` applies.
62 pub src_rect_fixed: Option<FixedRect>,
63
64 /// Region of the **CRTC** to show it on.
65 ///
66 /// `x`/`y` are signed: a plane may extend off-screen.
67 pub dst_rect: Rect,
68
69 /// Rotate and reflect bits, as a `DRM_MODE_ROTATE_*` /
70 /// `DRM_MODE_REFLECT_*` mask. Zero means no rotation, and nothing is
71 /// written.
72 pub rotation: u64,
73
74 /// Per-plane alpha, if the caller set one.
75 pub alpha: Option<u16>,
76
77 /// Stacking position, if the caller set one.
78 ///
79 /// Left unset deliberately by most callers — see
80 /// [`lower_layer`](crate::lower_layer) for why writing a default here
81 /// rather than leaving it alone is actively harmful.
82 pub zpos: Option<u64>,
83
84 /// The container colorimetry this layer's content is in.
85 ///
86 /// Feeds the scene's choice of connector `Colorspace` -- the widest-gamut
87 /// layer wins -- and seeds the mastering primaries when the frame is HDR.
88 /// `None` means the producer did not say, and the scene leaves the
89 /// connector property alone rather than guessing.
90 pub color_primaries: Option<crate::signaling::ColorPrimaries>,
91
92 /// The transfer function the content is encoded with.
93 ///
94 /// `None` is the SDR transfer. When *any* layer in a frame reports PQ or
95 /// HLG the scene declares HDR on the connector.
96 pub source_eotf: Option<drmkit_display::TransferFunction>,
97
98 /// The YCbCr-to-RGB matrix this layer's pixels were encoded with: the
99 /// plane's `COLOR_ENCODING`.
100 ///
101 /// `None` asks for BT.709. Only YUV formats are converted, so an RGB
102 /// layer is unaffected either way; the scene still writes the value, to
103 /// displace whatever the previous client left on the plane. A plane
104 /// without the encoding asked for gets BT.709, or BT.601 where that is
105 /// all it has.
106 pub color_encoding: Option<ColorEncoding>,
107
108 /// Whether this layer's YCbCr is limited or full range: the plane's
109 /// `COLOR_RANGE`. `None` asks for limited, which most camera and
110 /// broadcast video is.
111 pub color_range: Option<ColorRange>,
112}
113
114impl DisplayParams {
115 /// The YCbCr interpretation this layer asks its plane for, defaults
116 /// applied.
117 #[must_use]
118 pub fn ycbcr(&self) -> Ycbcr {
119 Ycbcr {
120 encoding: self.color_encoding.unwrap_or(ColorEncoding::Bt709),
121 range: self.color_range.unwrap_or(ColorRange::Limited),
122 }
123 }
124}
125
126/// How a plane converts a layer's YCbCr to RGB: its `COLOR_ENCODING` and
127/// `COLOR_RANGE`.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
129#[allow(
130 clippy::exhaustive_structs,
131 reason = "a value defined by its fields; there is nothing to add to it"
132)]
133pub struct Ycbcr {
134 /// The matrix.
135 pub encoding: ColorEncoding,
136 /// The range.
137 pub range: ColorRange,
138}
139
140/// Convert whole pixels to the 16.16 fixed point KMS expects for `SRC_*`.
141///
142/// The kernel encodes source rectangles this way so a compositor can express
143/// subpixel positioning; the destination rectangle stays in whole pixels.
144#[must_use]
145pub const fn to_16_16(pixels: u32) -> u64 {
146 (pixels as u64) << 16
147}