Skip to main content

display_types/
timing.rs

1use crate::VideoMode;
2
3/// Estimates the pixel clock in kHz for a [`VideoMode`] using the CVT Reduced Blanking (CVT-RB)
4/// overhead model.
5///
6/// When `mode.pixel_clock_khz` is `Some`, returns that exact value directly — no estimation
7/// is performed. When it is `None` (modes decoded from standard timings, established timings,
8/// or SVD entries that lack a Detailed Timing Descriptor), applies the CVT-RB fixed blanking
9/// parameters:
10///
11/// - **Horizontal blanking:** 160 pixels (CVT-RB fixed blank, VESA CVT 1.2 §2.2).
12/// - **Vertical blanking:** 8 lines (minimum RB frame-height adjustment).
13///
14/// The resulting estimate is:
15///
16/// ```text
17/// pixel_clock_khz ≈ (width + 160) × (height + 8) × refresh_rate_hz / 1000
18/// ```
19///
20/// # Accuracy
21///
22/// CVT-RB is the dominant timing standard for modern display modes. For typical consumer
23/// resolutions the estimate is within ~2% of the actual clock. Interlaced modes and
24/// non-CVT timings (e.g. legacy CTA-861 formats with larger blanking) may diverge further.
25///
26/// For bandwidth ceiling checks this function is conservative in the direction of
27/// *under*-estimating — use it to detect obvious violations, not as a substitute for an
28/// exact clock when one is available.
29pub fn pixel_clock_khz_cvt_rb_estimate(mode: &VideoMode) -> u32 {
30    if let Some(clk) = mode.pixel_clock_khz {
31        return clk;
32    }
33    let h_total = mode.width as u64 + 160;
34    let v_total = mode.height as u64 + 8;
35    (h_total * v_total * mode.refresh_rate as u64 / 1000) as u32
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use crate::VideoMode;
42
43    #[test]
44    fn exact_clock_returned_unchanged() {
45        let mode = VideoMode::new(1920, 1080, 60, false).with_detailed_timing(
46            148_500,
47            88,
48            44,
49            4,
50            5,
51            0,
52            0,
53            Default::default(),
54            None,
55        );
56        assert_eq!(pixel_clock_khz_cvt_rb_estimate(&mode), 148_500);
57    }
58
59    #[test]
60    fn non_dtd_mode_uses_cvt_rb_formula() {
61        // 1920×1080@60: (1920+160) × (1080+8) × 60 / 1000 = 135_782
62        let mode = VideoMode::new(1920, 1080, 60, false);
63        assert_eq!(pixel_clock_khz_cvt_rb_estimate(&mode), 135_782);
64    }
65
66    #[test]
67    fn zero_refresh_rate_returns_zero() {
68        let mode = VideoMode::new(1920, 1080, 0, false);
69        assert_eq!(pixel_clock_khz_cvt_rb_estimate(&mode), 0);
70    }
71}
72
73/// Video timing support reported in the display range limits descriptor (`0xFD`), byte 10.
74///
75/// Indicates which timing generation formula (if any) the display supports beyond the
76/// explicitly listed modes.
77#[non_exhaustive]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum TimingFormula {
81    /// Default GTF supported (byte 10 = `0x00`).
82    ///
83    /// The display accepts any timing within its range limits that satisfies the
84    /// default GTF parameters. Requires bit 0 of the Feature Support byte (`0x18`) to be set.
85    DefaultGtf,
86    /// Range limits only; no secondary timing formula (byte 10 = `0x01`).
87    ///
88    /// The display supports only the video timing modes explicitly listed in the EDID.
89    RangeLimitsOnly,
90    /// Secondary GTF curve supported (byte 10 = `0x02`).
91    ///
92    /// The display accepts timings using either the default GTF or the secondary GTF curve
93    /// whose parameters are stored in bytes 12–17.
94    SecondaryGtf(GtfSecondaryParams),
95    /// CVT timing supported (byte 10 = `0x04`), with parameters from bytes 11–17.
96    ///
97    /// The display accepts Coordinated Video Timings within its range limits.
98    /// Requires bit 0 of the Feature Support byte (`0x18`) to be set.
99    Cvt(CvtSupportParams),
100}
101
102/// GTF secondary curve parameters decoded from a display range limits descriptor (`0xFD`).
103///
104/// Used when [`TimingFormula::SecondaryGtf`] is active (byte 10 = `0x02`).
105/// The GTF formula selects the secondary curve for horizontal frequencies at or above
106/// [`start_freq_khz`][Self::start_freq_khz] and the default curve below it.
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct GtfSecondaryParams {
110    /// Start break frequency in kHz (byte 12 × 2).
111    pub start_freq_khz: u16,
112    /// GTF `C` parameter (0–127); byte 13 ÷ 2.
113    pub c: u8,
114    /// GTF `M` parameter (0–65535); bytes 14–15, little-endian.
115    pub m: u16,
116    /// GTF `K` parameter (0–255); byte 16.
117    pub k: u8,
118    /// GTF `J` parameter (0–127); byte 17 ÷ 2.
119    pub j: u8,
120}
121
122/// CVT support parameters decoded from a display range limits descriptor (`0xFD`).
123///
124/// Used when [`TimingFormula::Cvt`] is active (byte 10 = `0x04`).
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct CvtSupportParams {
128    /// CVT standard version, encoded as two BCD nibbles (e.g., `0x11` = version 1.1).
129    pub version: u8,
130    /// Additional pixel clock precision: 6-bit value from byte 12 bits 7–2.
131    ///
132    /// The maximum pixel clock is: `(descriptor byte 9 × 10 MHz) − (pixel_clock_adjust × 0.25 MHz)`.
133    /// When all six bits are set (`63`), byte 9 was already rounded up to the nearest 10 MHz.
134    pub pixel_clock_adjust: u8,
135    /// Maximum number of horizontal active pixels, or `None` if there is no limit.
136    ///
137    /// Computed as `8 × (byte 13 + 256 × (byte 12 bits 1–0))`. `None` when the 10-bit
138    /// combined value is zero.
139    pub max_h_active_pixels: Option<u16>,
140    /// Aspect ratios the display supports for CVT-generated timings.
141    pub supported_aspect_ratios: CvtAspectRatios,
142    /// Preferred aspect ratio for CVT-generated timings, or `None` for a reserved value.
143    pub preferred_aspect_ratio: Option<CvtAspectRatio>,
144    /// Standard CVT blanking (normal blanking) is supported.
145    pub standard_blanking: bool,
146    /// Reduced CVT blanking is supported (preferred over standard blanking).
147    pub reduced_blanking: bool,
148    /// Display scaling capabilities.
149    pub scaling: CvtScaling,
150    /// Preferred vertical refresh rate in Hz, or `None` if byte 17 = `0x00` (reserved).
151    pub preferred_v_rate: Option<u8>,
152}
153
154bitflags::bitflags! {
155    /// Aspect ratios supported for CVT-generated timings (byte 14 of a `0xFD` descriptor).
156    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
157    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
158    pub struct CvtAspectRatios: u8 {
159        /// 4∶3 aspect ratio supported.
160        const R4_3   = 0x80;
161        /// 16∶9 aspect ratio supported.
162        const R16_9  = 0x40;
163        /// 16∶10 aspect ratio supported.
164        const R16_10 = 0x20;
165        /// 5∶4 aspect ratio supported.
166        const R5_4   = 0x10;
167        /// 15∶9 aspect ratio supported.
168        const R15_9  = 0x08;
169    }
170}
171
172bitflags::bitflags! {
173    /// Display scaling capabilities reported in byte 16 of a `0xFD` CVT descriptor.
174    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
175    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
176    pub struct CvtScaling: u8 {
177        /// Input horizontal active pixels can exceed the display's preferred horizontal count.
178        const HORIZONTAL_SHRINK  = 0x80;
179        /// Input horizontal active pixels can be fewer than the display's preferred horizontal count.
180        const HORIZONTAL_STRETCH = 0x40;
181        /// Input vertical active lines can exceed the display's preferred vertical count.
182        const VERTICAL_SHRINK    = 0x20;
183        /// Input vertical active lines can be fewer than the display's preferred vertical count.
184        const VERTICAL_STRETCH   = 0x10;
185    }
186}
187
188/// Preferred aspect ratio for CVT-generated timings, decoded from byte 15 bits 7–5.
189#[non_exhaustive]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum CvtAspectRatio {
193    /// 4∶3 preferred aspect ratio.
194    R4_3,
195    /// 16∶9 preferred aspect ratio.
196    R16_9,
197    /// 16∶10 preferred aspect ratio.
198    R16_10,
199    /// 5∶4 preferred aspect ratio.
200    R5_4,
201    /// 15∶9 preferred aspect ratio.
202    R15_9,
203}