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}