Skip to main content

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}