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