1use drmkit_core::{Device, Result};
13use drmkit_core::{ObjectType, PropertyStore};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[non_exhaustive]
22pub enum Colorspace {
23 Default,
25 Smpte170mYcc,
27 Bt709Ycc,
29 XvYcc601,
31 XvYcc709,
33 SYcc601,
35 OpYcc601,
37 OpRgb,
39 Bt2020Cycc,
41 Bt2020Rgb,
43 Bt2020Ycc,
45 DciP3RgbD65,
47 DciP3RgbTheater,
49 RgbWideFixed,
51 RgbWideFloat,
53}
54
55impl Colorspace {
56 pub const ALL: [Self; 15] = [
58 Self::Default,
59 Self::Smpte170mYcc,
60 Self::Bt709Ycc,
61 Self::XvYcc601,
62 Self::XvYcc709,
63 Self::SYcc601,
64 Self::OpYcc601,
65 Self::OpRgb,
66 Self::Bt2020Cycc,
67 Self::Bt2020Rgb,
68 Self::Bt2020Ycc,
69 Self::DciP3RgbD65,
70 Self::DciP3RgbTheater,
71 Self::RgbWideFixed,
72 Self::RgbWideFloat,
73 ];
74
75 #[must_use]
77 pub const fn name(self) -> &'static str {
78 match self {
79 Self::Default => "Default",
80 Self::Smpte170mYcc => "SMPTE_170M_YCC",
81 Self::Bt709Ycc => "BT709_YCC",
82 Self::XvYcc601 => "XVYCC_601",
83 Self::XvYcc709 => "XVYCC_709",
84 Self::SYcc601 => "SYCC_601",
85 Self::OpYcc601 => "opYCC_601",
86 Self::OpRgb => "opRGB",
87 Self::Bt2020Cycc => "BT2020_CYCC",
88 Self::Bt2020Rgb => "BT2020_RGB",
89 Self::Bt2020Ycc => "BT2020_YCC",
90 Self::DciP3RgbD65 => "DCI-P3_RGB_D65",
91 Self::DciP3RgbTheater => "DCI-P3_RGB_Theater",
92 Self::RgbWideFixed => "RGB_Wide_Gamut_Fixed_Point",
93 Self::RgbWideFloat => "RGB_Wide_Gamut_Floating_Point",
94 }
95 }
96
97 const fn index(self) -> usize {
98 self as usize
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
108#[non_exhaustive]
109pub enum BroadcastRgb {
110 Automatic,
112 Full,
114 Limited,
116}
117
118impl BroadcastRgb {
119 pub const ALL: [Self; 3] = [Self::Automatic, Self::Full, Self::Limited];
121
122 #[must_use]
124 pub const fn name(self) -> &'static str {
125 match self {
126 Self::Automatic => "Automatic",
127 Self::Full => "Full",
128 Self::Limited => "Limited 16:235",
129 }
130 }
131
132 const fn index(self) -> usize {
133 self as usize
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139#[allow(
140 clippy::exhaustive_structs,
141 reason = "a value defined by its fields; there is nothing to add to it"
142)]
143pub struct MaxBpc {
144 pub min: u64,
146 pub max: u64,
148 pub current: u64,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct ConnectorCapabilities {
155 pub connector_id: u32,
157 colorspace: [Option<u64>; Colorspace::ALL.len()],
165 broadcast_rgb: [Option<u64>; BroadcastRgb::ALL.len()],
167 pub max_bpc: Option<MaxBpc>,
169 pub has_hdr_output_metadata: bool,
171}
172
173impl ConnectorCapabilities {
174 #[must_use]
179 pub const fn new(connector_id: u32) -> Self {
180 Self {
181 connector_id,
182 colorspace: [None; Colorspace::ALL.len()],
183 broadcast_rgb: [None; BroadcastRgb::ALL.len()],
184 max_bpc: None,
185 has_hdr_output_metadata: false,
186 }
187 }
188
189 #[must_use]
191 pub const fn with_colorspace(mut self, entry: Colorspace, value: u64) -> Self {
192 self.colorspace[entry.index()] = Some(value);
193 self
194 }
195
196 #[must_use]
198 pub const fn with_broadcast_rgb(mut self, entry: BroadcastRgb, value: u64) -> Self {
199 self.broadcast_rgb[entry.index()] = Some(value);
200 self
201 }
202
203 #[must_use]
205 pub const fn with_max_bpc(mut self, max_bpc: MaxBpc) -> Self {
206 self.max_bpc = Some(max_bpc);
207 self
208 }
209
210 #[must_use]
212 pub const fn with_hdr_output_metadata(mut self, present: bool) -> Self {
213 self.has_hdr_output_metadata = present;
214 self
215 }
216
217 pub fn probe(device: &Device, connector_id: u32) -> Result<Self> {
227 let mut properties = PropertyStore::new();
228 properties.cache_properties(device, connector_id, ObjectType::Connector)?;
229 Ok(Self::from_properties(device, &properties, connector_id))
230 }
231
232 #[must_use]
235 pub fn from_properties(device: &Device, properties: &PropertyStore, connector_id: u32) -> Self {
236 let mut colorspace = [None; Colorspace::ALL.len()];
237 for entry in Colorspace::ALL {
238 colorspace[entry.index()] = properties
239 .enum_value(device, connector_id, "Colorspace", entry.name())
240 .ok();
241 }
242 let mut broadcast_rgb = [None; BroadcastRgb::ALL.len()];
243 for entry in BroadcastRgb::ALL {
244 broadcast_rgb[entry.index()] = properties
245 .enum_value(device, connector_id, "Broadcast RGB", entry.name())
246 .ok();
247 }
248
249 let max_bpc = properties
250 .range(connector_id, "max bpc")
251 .ok()
252 .flatten()
253 .and_then(|(min, max)| {
254 Some(MaxBpc {
255 min,
256 max,
257 current: properties.property_value(connector_id, "max bpc").ok()?,
258 })
259 });
260
261 Self {
262 connector_id,
263 colorspace,
264 broadcast_rgb,
265 max_bpc,
266 has_hdr_output_metadata: properties
267 .property_id(connector_id, "HDR_OUTPUT_METADATA")
268 .is_ok(),
269 }
270 }
271
272 #[must_use]
274 pub const fn colorspace(&self, entry: Colorspace) -> Option<u64> {
275 self.colorspace[entry.index()]
276 }
277
278 #[must_use]
280 pub const fn broadcast_rgb(&self, entry: BroadcastRgb) -> Option<u64> {
281 self.broadcast_rgb[entry.index()]
282 }
283
284 #[must_use]
286 pub fn has_colorspace(&self) -> bool {
287 self.colorspace.iter().any(Option::is_some)
288 }
289
290 #[must_use]
292 pub fn has_broadcast_rgb(&self) -> bool {
293 self.broadcast_rgb.iter().any(Option::is_some)
294 }
295
296 #[must_use]
304 pub fn can_signal_hdr(&self) -> bool {
305 self.has_hdr_output_metadata && self.max_bpc.is_some_and(|bpc| bpc.max >= 10)
306 }
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
311#[non_exhaustive]
312pub struct CrtcCapabilities {
313 pub crtc_id: u32,
315 pub degamma_lut_size: u32,
317 pub gamma_lut_size: u32,
319 pub has_degamma_lut: bool,
321 pub has_ctm: bool,
323 pub has_gamma_lut: bool,
325}
326
327impl CrtcCapabilities {
328 pub fn probe(device: &Device, crtc_id: u32) -> Result<Self> {
334 let mut properties = PropertyStore::new();
335 properties.cache_properties(device, crtc_id, ObjectType::Crtc)?;
336 Ok(Self::from_properties(&properties, crtc_id))
337 }
338
339 #[must_use]
342 pub fn from_properties(properties: &PropertyStore, crtc_id: u32) -> Self {
343 let size = |name: &str| {
349 properties
350 .property_value(crtc_id, name)
351 .ok()
352 .and_then(|value| u32::try_from(value).ok())
353 .unwrap_or(0)
354 };
355 let has = |name: &str| properties.property_id(crtc_id, name).is_ok();
356
357 Self {
358 crtc_id,
359 degamma_lut_size: size("DEGAMMA_LUT_SIZE"),
360 gamma_lut_size: size("GAMMA_LUT_SIZE"),
361 has_degamma_lut: has("DEGAMMA_LUT"),
362 has_ctm: has("CTM"),
363 has_gamma_lut: has("GAMMA_LUT"),
364 }
365 }
366
367 #[must_use]
372 pub const fn has_full_pipeline(&self) -> bool {
373 self.has_degamma_lut && self.has_ctm && self.has_gamma_lut
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::{BroadcastRgb, Colorspace, ConnectorCapabilities, CrtcCapabilities, MaxBpc};
380
381 fn amdgpu() -> ConnectorCapabilities {
383 ConnectorCapabilities::new(383)
384 .with_colorspace(Colorspace::Default, 0)
385 .with_colorspace(Colorspace::Bt709Ycc, 1)
386 .with_colorspace(Colorspace::OpRgb, 2)
387 .with_colorspace(Colorspace::Bt2020Rgb, 3)
388 .with_colorspace(Colorspace::Bt2020Ycc, 4)
389 .with_max_bpc(MaxBpc {
390 min: 8,
391 max: 16,
392 current: 16,
393 })
394 .with_hdr_output_metadata(true)
395 }
396
397 #[test]
398 fn an_entry_the_driver_does_not_have_is_absent_rather_than_zero() {
399 let connector = amdgpu();
403 assert_eq!(connector.colorspace(Colorspace::Default), Some(0));
404 assert_eq!(connector.colorspace(Colorspace::DciP3RgbD65), None);
405 }
406
407 #[test]
408 fn every_entry_is_stored_under_its_own_name() {
409 let mut connector = ConnectorCapabilities::new(1);
412 for (value, entry) in Colorspace::ALL.into_iter().enumerate() {
413 connector = connector.with_colorspace(entry, value as u64);
414 }
415 for (value, entry) in Colorspace::ALL.into_iter().enumerate() {
416 assert_eq!(
417 connector.colorspace(entry),
418 Some(value as u64),
419 "{}",
420 entry.name()
421 );
422 }
423 }
424
425 #[test]
426 fn broadcast_rgb_entries_are_stored_the_same_way() {
427 let mut connector = ConnectorCapabilities::new(1);
428 for (value, entry) in BroadcastRgb::ALL.into_iter().enumerate() {
429 connector = connector.with_broadcast_rgb(entry, 10 + value as u64);
430 }
431 for (value, entry) in BroadcastRgb::ALL.into_iter().enumerate() {
432 assert_eq!(connector.broadcast_rgb(entry), Some(10 + value as u64));
433 }
434 assert!(connector.has_broadcast_rgb());
435 }
436
437 #[test]
438 fn hdr_needs_the_metadata_and_the_bit_depth() {
439 assert!(amdgpu().can_signal_hdr());
440
441 assert!(
444 !ConnectorCapabilities::new(1)
445 .with_hdr_output_metadata(true)
446 .with_max_bpc(MaxBpc {
447 min: 6,
448 max: 8,
449 current: 8,
450 })
451 .can_signal_hdr()
452 );
453
454 assert!(
456 !ConnectorCapabilities::new(1)
457 .with_max_bpc(MaxBpc {
458 min: 8,
459 max: 10,
460 current: 8,
461 })
462 .can_signal_hdr()
463 );
464
465 assert!(
468 !ConnectorCapabilities::new(1)
469 .with_hdr_output_metadata(true)
470 .can_signal_hdr()
471 );
472 }
473
474 #[test]
475 fn the_current_bpc_is_what_is_set_not_what_is_possible() {
476 let connector = ConnectorCapabilities::new(1)
480 .with_hdr_output_metadata(true)
481 .with_max_bpc(MaxBpc {
482 min: 8,
483 max: 16,
484 current: 8,
485 });
486 assert!(connector.can_signal_hdr());
487 assert_eq!(connector.max_bpc.map(|bpc| bpc.current), Some(8));
488 }
489
490 #[test]
491 fn a_connector_with_nothing_on_it_says_so() {
492 let connector = ConnectorCapabilities::new(7);
493 assert!(!connector.has_colorspace());
494 assert!(!connector.has_broadcast_rgb());
495 assert!(!connector.can_signal_hdr());
496 assert_eq!(connector.connector_id, 7);
497 }
498
499 #[test]
500 fn a_partial_colour_pipeline_is_not_a_full_one() {
501 let vkms = CrtcCapabilities {
505 crtc_id: 34,
506 gamma_lut_size: 256,
507 has_gamma_lut: true,
508 ..CrtcCapabilities::default()
509 };
510 assert!(!vkms.has_full_pipeline());
511
512 let amdgpu = CrtcCapabilities {
513 crtc_id: 369,
514 degamma_lut_size: 4096,
515 gamma_lut_size: 4096,
516 has_degamma_lut: true,
517 has_ctm: true,
518 has_gamma_lut: true,
519 };
520 assert!(amdgpu.has_full_pipeline());
521 }
522}