Skip to main content

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}