cvt_utils/
lib.rs

1
2/// Represents CVT timings.
3/// 
4/// To better understand CVT timings, read the README of this crate
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct CvtTimings {
7    /// The pixel clock (MHz)
8    pub pixel_clock: f64,
9
10    /// Total width to scan (Pixels)
11    pub h_total: u32,
12    /// Active width section to scan (Pixels)
13    pub h_active: u32,
14    /// Blanking width section to scan (Pixels)
15    pub h_blank: u32,
16    /// Front Porch width section to scan (Pixels)
17    pub h_front_porch: u32,
18    /// Horizontal Sync (Pixels)
19    pub h_sync: u32,
20    /// Back Porch width section to scan (Pixels)
21    pub h_back_porch: u32,
22    /// Polarity of the horizontal sync scan (+/-)
23    pub h_sync_polarity: bool,
24    /// Horizontal scan frequency (KHz)
25    /// 
26    /// This represents how many times per second a horizontal scan is performed
27    pub h_freq: f64,
28    /// Horizontal scan period (us)
29    /// 
30    /// This represents the amount of time a horizontal scan takes
31    pub h_period: f64,
32
33    /// Total height to scan (Pixels)
34    pub v_total: u32,
35    /// Active height section to scan (Pixels)
36    pub v_active: u32,
37    /// Blanking height section to scan (Pixels)
38    pub v_blank: u32,
39    /// Front Porch height section to scan (Pixels)
40    pub v_front_porch: u32,
41    /// Vertical Sync (Pixels)
42    pub v_sync: u32,
43    /// Back Porch height section to scan (Pixels)
44    pub v_back_porch: u32,
45    /// Polarity of the vertical sync scan (+/-)
46    pub v_sync_polarity: bool,
47    /// Vertical scan frequency (KHz)
48    /// 
49    /// This represents how many times per second a vertical scan is performed
50    pub v_freq: f64,
51    /// Vertical scan period (us)
52    /// 
53    /// This represents the amount of time a vertical scan takes
54    pub v_period: f64,
55
56    /// Whether the video stream is interlaced or not
57    pub interlaced: bool,
58}
59
60/// Monitor blanking mode.
61/// 
62/// Determines the way a monitor should draw frames to the screen and the amount of bandwidth they use on the wire.
63#[derive(Copy, Clone, Debug, PartialEq, Eq)]
64pub enum BlankingMode {
65    /// Normal blanking.
66    /// 
67    /// does not cut down any time, works on all monitor types.
68    /// 
69    /// If you want to overdrive a non-CRT monitor, you should check out [BlankingMode::ReducedV2] or [BlankingMode::Reduced].
70    Normal,
71    /// Reduced blanking.
72    /// 
73    /// Does not work on CRT displays, but cuts down significantly on the bandwidth required.
74    /// 
75    /// You should use [BlankingMode::ReducedV2] instead, it being more efficient, unless it causes issues with your monitor.
76    Reduced,
77    /// Reduced blanking V2.
78    /// 
79    /// Does not work on CRT displays, but cuts down significantly on the bandwidth required, even more than [BlankingMode::Reduced].
80    /// 
81    /// If it causes issues with your monitor, switch to [BlankingMode::Reduced] or [BlankingMode::Normal].
82    ReducedV2,
83}
84
85#[derive(Copy, Clone, Debug, PartialEq, Eq)]
86enum AspectRatio {
87    Aspect4by3,
88    Aspect16by9,
89    Aspect16by10,
90    Aspect5by4,
91    Aspect15by9,
92
93    /// NOTE: NOT DEFINED BY THE SPEC
94    Aspect43by18,
95    Aspect64by27,
96    Aspect12by5,
97    AspectUnknown,
98}
99
100impl CvtTimings {
101    // generates  video timings according to the VESA CVT standard
102    // look into GTF for the future
103    // https://glenwing.github.io/docs/VESA-GTF-1.1.pdf
104
105    /// Generates CVT timings according to the input given.
106    pub fn generate(
107        h_pixels: u32,
108        v_pixels: u32,
109        refresh_rate: f64,
110        blanking_mode: BlankingMode,
111        margins: bool,
112        interlaced: bool,
113    ) -> Self {
114        let clock_step: f64;
115        let min_v_bporch: u32 = 6;
116        let rb_h_blank: u32;
117        let _rb_h_sync: u32 = 32;
118        let rb_min_v_blank: u32 = 460;
119        let rb_v_fporch: u32;
120        let refresh_multiplier: f64;
121        let h_pol: bool;
122        let v_pol: bool;
123        let cell_gran: f64 = 8.0;
124        let margin_per: f64 = 1.8;
125        let min_vsync_bp: f64 = 550.0;
126        let min_v_porch_rnd: f64 = 3.0;
127        let c_prime: u32 = 30;
128        let m_prime: u32 = 300;
129        let h_sync_per: f64 = 0.08;
130
131        match blanking_mode {
132            BlankingMode::Normal => {
133                clock_step = 0.25;
134                rb_h_blank = 160;
135                rb_v_fporch = 3;
136                refresh_multiplier = 1.0;
137                h_pol = false;
138                v_pol = true;
139            }
140            BlankingMode::Reduced => {
141                clock_step = 0.25;
142                rb_h_blank = 160;
143                rb_v_fporch = 3;
144                refresh_multiplier = 1.0;
145                h_pol = true;
146                v_pol = false;
147            }
148            BlankingMode::ReducedV2 => {
149                clock_step = 0.001;
150                rb_h_blank = 80;
151                rb_v_fporch = 1;
152                refresh_multiplier = 1.0; // video optimized shit idk
153                h_pol = true;
154                v_pol = false;
155            }
156        }
157
158        let cell_gran_rnd = cell_gran.floor();
159
160        // 5.2 Computation of Common Parameters
161        // if interlacing, set it to 2x the refresh rate
162        let v_field_rate_rqd = if interlaced {
163            refresh_rate * 2.0
164        } else {
165            refresh_rate
166        };
167
168        let h_pixels_rnd = (h_pixels as f64 / cell_gran_rnd).floor() * cell_gran_rnd;
169
170        // if using margins, set the margin
171        let left_margin = if margins {
172            ((h_pixels_rnd * margin_per / 100.0) / cell_gran_rnd).floor() * cell_gran_rnd
173        } else {
174            0.0
175        };
176        let right_margin = left_margin;
177
178        let total_active_pixels = (h_pixels_rnd + left_margin + right_margin).floor() as u32; // floor?
179
180        let v_lines_rnd = if interlaced {
181            ((v_pixels as f64) / 2.0).floor()
182        } else {
183            (v_pixels as f64).floor()
184        };
185
186        let top_margin = if margins {
187            (v_lines_rnd * margin_per / 100.0).floor()
188        } else {
189            0.0
190        };
191        let bot_margin = top_margin;
192
193        let interlace = if interlaced { 0.5 } else { 0.0 };
194
195        let v_sync_rnd: f64;
196
197        if blanking_mode == BlankingMode::ReducedV2 {
198            v_sync_rnd = 8.0;
199        } else {
200            // calculate aspect ratio
201            let aspect_ratio =
202                get_aspect_ratio(interlaced, v_lines_rnd, h_pixels_rnd, cell_gran_rnd);
203
204            match aspect_ratio {
205                AspectRatio::Aspect4by3 => {
206                    v_sync_rnd = 4.0;
207                }
208                AspectRatio::Aspect16by9 => {
209                    v_sync_rnd = 5.0;
210                }
211                AspectRatio::Aspect16by10 => {
212                    v_sync_rnd = 6.0;
213                }
214                AspectRatio::Aspect5by4 => {
215                    v_sync_rnd = 7.0;
216                }
217                AspectRatio::Aspect15by9 => {
218                    v_sync_rnd = 7.0;
219                }
220                _ => {
221                    v_sync_rnd = 10.0;
222                }
223            }
224        }
225
226        let h_period_est: f64;
227        let mut v_sync_bp: f64;
228        let v_blank: f64;
229        let v_front_porch: f64;
230        let v_back_porch: f64;
231        let total_v_lines: f64;
232        let ideal_duty_cycle: f64;
233        let h_blank: f64;
234        let total_pixels: f64;
235        let h_sync: f64;
236        let h_back_porch: f64;
237        let h_front_porch: f64;
238        let act_pix_freq: f64;
239        if blanking_mode == BlankingMode::Normal {
240            h_period_est = ((1.0 / v_field_rate_rqd) - min_vsync_bp / 1000000.0)
241                / (v_lines_rnd + (2.0 * top_margin) + min_v_porch_rnd + interlace)
242                * 1000000.0;
243            v_sync_bp = (min_vsync_bp / h_period_est).floor() + 1.0;
244
245            if v_sync_bp < (v_sync_rnd + min_v_bporch as f64) {
246                v_sync_bp = v_sync_rnd + min_v_bporch as f64;
247            }
248            v_blank = v_sync_bp + min_v_porch_rnd;
249            v_front_porch = min_v_porch_rnd;
250            v_back_porch = v_sync_bp - v_sync_rnd;
251            total_v_lines =
252                v_lines_rnd + top_margin + bot_margin + v_sync_bp + interlace + min_v_porch_rnd;
253            ideal_duty_cycle = c_prime as f64 - (m_prime as f64 * h_period_est / 1000.0);
254
255            if ideal_duty_cycle < 20.0 {
256                h_blank =
257                    (total_active_pixels as f64 * 20.0 / (100.0 - 20.0) / (2.0 * cell_gran_rnd))
258                        * (2.0 * cell_gran_rnd);
259            } else {
260                h_blank = (total_active_pixels as f64 * ideal_duty_cycle
261                    / (100.0 - ideal_duty_cycle)
262                    / (2.0 * cell_gran_rnd))
263                    .floor()
264                    * (2.0 * cell_gran_rnd);
265            }
266            total_pixels = total_active_pixels as f64 + h_blank;
267
268            h_sync = (h_sync_per * total_pixels / cell_gran_rnd).floor() * cell_gran_rnd;
269            h_back_porch = h_blank / 2.0;
270            h_front_porch = h_blank - h_sync - h_back_porch;
271            act_pix_freq = clock_step * (total_pixels / h_period_est / clock_step).floor();
272        } else {
273            h_period_est = ((1000000.0 / v_field_rate_rqd) - rb_min_v_blank as f64)
274                / (v_lines_rnd + top_margin + bot_margin);
275            h_blank = rb_h_blank as f64;
276            let vbi_lines = (rb_min_v_blank as f64 / h_period_est) + 1.0;
277            let rb_min_vbi = rb_v_fporch as f64 + v_sync_rnd + min_v_bporch as f64;
278            let act_vbi_lines = if vbi_lines < rb_min_vbi {
279                rb_min_vbi
280            } else {
281                vbi_lines
282            };
283            total_v_lines = act_vbi_lines + v_lines_rnd + top_margin + bot_margin + interlace;
284            total_pixels = (rb_h_blank + total_active_pixels) as f64;
285            act_pix_freq = clock_step
286                * ((v_field_rate_rqd * total_v_lines * total_pixels / 1000000.0
287                    * refresh_multiplier)
288                    / clock_step)
289                    .floor();
290
291            if blanking_mode == BlankingMode::ReducedV2 {
292                v_blank = act_vbi_lines;
293                v_front_porch = act_vbi_lines - v_sync_rnd - 6.0;
294                v_back_porch = 6.0;
295                h_sync = 32.0;
296                h_back_porch = 40.0;
297                h_front_porch = h_blank - h_sync - h_back_porch;
298            } else {
299                v_blank = act_vbi_lines;
300                v_front_porch = 3.0;
301                v_back_porch = act_vbi_lines - v_front_porch - v_sync_rnd;
302
303                h_sync = 32.0;
304                h_back_porch = 80.0;
305                h_front_porch = h_blank - h_sync - h_back_porch;
306            }
307        }
308
309        let pclock = act_pix_freq * 1000000.0;
310        let h_freq = pclock / total_pixels;
311        let v_freq = pclock / (total_v_lines * total_pixels);
312        Self {
313            pixel_clock: pclock,
314            h_active: total_active_pixels,
315            h_blank: h_blank as u32,
316            h_total: total_pixels as u32,
317            v_active: v_lines_rnd as u32,
318            v_blank: v_blank as u32,
319            v_total: total_v_lines as u32,
320            h_freq: (h_freq * 100.0).round() / 100.0,
321            v_freq: (v_freq * 100.0).round() / 100.0,
322            h_period: 1.0 / h_freq,
323            v_period: 1.0 / v_freq,
324            h_front_porch: h_front_porch as u32,
325            h_sync: h_sync as u32,
326            h_back_porch: h_back_porch as u32,
327            h_sync_polarity: h_pol,
328            v_front_porch: v_front_porch as u32,
329            v_sync: v_sync_rnd as u32,
330            v_back_porch: v_back_porch as u32,
331            v_sync_polarity: v_pol,
332            interlaced,
333        }
334    }
335
336    pub fn generate_modeline(&self) -> String {
337        format!(
338            "Modeline \"{}x{}_{:.2}{}\" {} {} {} {} {} {} {} {} {} {} {} {}",
339            self.h_active,
340            self.v_active,
341            self.v_freq,
342            if self.interlaced { "i" } else { "" },
343            (self.pixel_clock / 1000.0).round() / 1000.0,
344            self.h_active,
345            self.h_active + self.h_front_porch,
346            self.h_active + self.h_front_porch + self.h_sync,
347            self.h_total,
348            self.v_active,
349            self.v_active + self.v_front_porch,
350            self.v_active + self.v_front_porch + self.v_sync,
351            self.v_total,
352            if self.h_sync_polarity {
353                "+HSync"
354            } else {
355                "-HSync"
356            },
357            if self.v_sync_polarity {
358                "+Vsync"
359            } else {
360                "-VSync"
361            },
362            if self.interlaced { "Interlace" } else { "" }
363        )
364    }
365}
366
367fn get_aspect_ratio(
368    interlaced: bool,
369    v_lines_rnd: f64,
370    h_pixels_rnd: f64,
371    cell_gran_rnd: f64,
372) -> AspectRatio {
373    let ver_pixels = if interlaced {
374        2.0 * v_lines_rnd
375    } else {
376        v_lines_rnd
377    };
378    let hor_pixels_4_3 = cell_gran_rnd * (ver_pixels * 4.0 / 3.0).floor() / cell_gran_rnd;
379    let hor_pixels_16_9 = cell_gran_rnd * (ver_pixels * 16.0 / 9.0).floor() / cell_gran_rnd;
380    let hor_pixels_16_10 = cell_gran_rnd * (ver_pixels * 16.0 / 10.0).floor() / cell_gran_rnd;
381    let hor_pixels_5_4 = cell_gran_rnd * (ver_pixels * 5.0 / 4.0).floor() / cell_gran_rnd;
382    let hor_pixels_15_9 = cell_gran_rnd * (ver_pixels * 15.0 / 9.0).floor() / cell_gran_rnd;
383    let hor_pixels_43_18 = cell_gran_rnd * (ver_pixels * 43.0 / 18.0).floor() / cell_gran_rnd;
384    let hor_pixels_64_27 = cell_gran_rnd * (ver_pixels * 64.0 / 27.0).floor() / cell_gran_rnd;
385    let hor_pixels_12_5 = cell_gran_rnd * (ver_pixels * 12.0 / 5.0).floor() / cell_gran_rnd;
386
387    if hor_pixels_4_3 == h_pixels_rnd {
388        AspectRatio::Aspect4by3
389    } else if hor_pixels_16_9 == h_pixels_rnd {
390        return AspectRatio::Aspect16by9;
391    } else if hor_pixels_16_10 == h_pixels_rnd {
392        return AspectRatio::Aspect16by10;
393    } else if hor_pixels_5_4 == h_pixels_rnd {
394        return AspectRatio::Aspect5by4;
395    } else if hor_pixels_15_9 == h_pixels_rnd {
396        return AspectRatio::Aspect15by9;
397    } else if hor_pixels_43_18 == h_pixels_rnd {
398        return AspectRatio::Aspect43by18;
399    } else if hor_pixels_64_27 == h_pixels_rnd {
400        return AspectRatio::Aspect64by27;
401    } else if hor_pixels_12_5 == h_pixels_rnd {
402        return AspectRatio::Aspect12by5;
403    } else {
404        return AspectRatio::AspectUnknown;
405    }
406}