1
2#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct CvtTimings {
7 pub pixel_clock: f64,
9
10 pub h_total: u32,
12 pub h_active: u32,
14 pub h_blank: u32,
16 pub h_front_porch: u32,
18 pub h_sync: u32,
20 pub h_back_porch: u32,
22 pub h_sync_polarity: bool,
24 pub h_freq: f64,
28 pub h_period: f64,
32
33 pub v_total: u32,
35 pub v_active: u32,
37 pub v_blank: u32,
39 pub v_front_porch: u32,
41 pub v_sync: u32,
43 pub v_back_porch: u32,
45 pub v_sync_polarity: bool,
47 pub v_freq: f64,
51 pub v_period: f64,
55
56 pub interlaced: bool,
58}
59
60#[derive(Copy, Clone, Debug, PartialEq, Eq)]
64pub enum BlankingMode {
65 Normal,
71 Reduced,
77 ReducedV2,
83}
84
85#[derive(Copy, Clone, Debug, PartialEq, Eq)]
86enum AspectRatio {
87 Aspect4by3,
88 Aspect16by9,
89 Aspect16by10,
90 Aspect5by4,
91 Aspect15by9,
92
93 Aspect43by18,
95 Aspect64by27,
96 Aspect12by5,
97 AspectUnknown,
98}
99
100impl CvtTimings {
101 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; h_pol = true;
154 v_pol = false;
155 }
156 }
157
158 let cell_gran_rnd = cell_gran.floor();
159
160 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 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; 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 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}