drmkit_fmt/probe.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Memoized atomic `TEST_ONLY` verdicts.
5
6use alloc::vec::Vec;
7
8use crate::modifier::Modifier;
9
10/// The cached outcome of probing a `(crtc, plane, fourcc, modifier)` tuple with
11/// an atomic `TEST_ONLY` commit.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13#[non_exhaustive]
14pub enum Verdict {
15 /// Never probed.
16 #[default]
17 Unknown,
18 /// The commit accepted it.
19 Ok,
20 /// The commit rejected it.
21 Rejected,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25struct Key {
26 crtc: u32,
27 plane: u32,
28 fourcc: u32,
29 modifier: Modifier,
30}
31
32/// Memoizes atomic `TEST_ONLY` verdicts.
33///
34/// An `IN_FORMATS` that advertises a modifier the hardware cannot actually
35/// program — historically some pre-6.16 `MediaTek` OVL instances — then costs one
36/// probe rather than one glitch per frame.
37///
38/// Backed by a linear scan: the entry count is bounded by
39/// `crtcs * planes * formats` actually exercised, which is small, and a scan
40/// beats hashing at this size.
41///
42/// ```
43/// use drmkit_fmt::{Modifier, ModifierProbeCache, Verdict, fourcc};
44///
45/// let mut cache = ModifierProbeCache::new();
46/// assert_eq!(cache.lookup(1, 31, fourcc::XRGB8888, Modifier::LINEAR), Verdict::Unknown);
47///
48/// cache.record(1, 31, fourcc::XRGB8888, Modifier::LINEAR, true);
49/// assert_eq!(cache.lookup(1, 31, fourcc::XRGB8888, Modifier::LINEAR), Verdict::Ok);
50///
51/// // A modeset or hotplug invalidates everything cached for that plane.
52/// cache.invalidate_plane(31);
53/// assert_eq!(cache.lookup(1, 31, fourcc::XRGB8888, Modifier::LINEAR), Verdict::Unknown);
54/// ```
55#[derive(Debug, Clone, Default)]
56pub struct ModifierProbeCache {
57 entries: Vec<(Key, bool)>,
58}
59
60impl ModifierProbeCache {
61 /// An empty cache.
62 #[must_use]
63 pub const fn new() -> Self {
64 Self {
65 entries: Vec::new(),
66 }
67 }
68
69 /// The cached verdict, or [`Verdict::Unknown`].
70 #[must_use]
71 pub fn lookup(&self, crtc: u32, plane: u32, fourcc: u32, modifier: Modifier) -> Verdict {
72 let key = Key {
73 crtc,
74 plane,
75 fourcc,
76 modifier,
77 };
78 self.entries
79 .iter()
80 .find(|(k, _)| *k == key)
81 .map_or(Verdict::Unknown, |(_, ok)| {
82 if *ok { Verdict::Ok } else { Verdict::Rejected }
83 })
84 }
85
86 /// Record a probe outcome, replacing any previous verdict for the tuple.
87 pub fn record(&mut self, crtc: u32, plane: u32, fourcc: u32, modifier: Modifier, ok: bool) {
88 let key = Key {
89 crtc,
90 plane,
91 fourcc,
92 modifier,
93 };
94 if let Some(entry) = self.entries.iter_mut().find(|(k, _)| *k == key) {
95 entry.1 = ok;
96 } else {
97 self.entries.push((key, ok));
98 }
99 }
100
101 /// Drop every verdict for a plane. Call on hotplug or modeset, after which
102 /// a previously rejected modifier may well be accepted.
103 pub fn invalidate_plane(&mut self, plane: u32) {
104 self.entries.retain(|(k, _)| k.plane != plane);
105 }
106
107 /// Number of cached verdicts.
108 #[must_use]
109 pub fn len(&self) -> usize {
110 self.entries.len()
111 }
112
113 /// Whether nothing is cached.
114 #[must_use]
115 pub fn is_empty(&self) -> bool {
116 self.entries.is_empty()
117 }
118}