1 /**************************************************************
2
3 modeline.cpp - Modeline generation and scoring routines
4
5 ---------------------------------------------------------
6
7 Switchres Modeline generation engine for emulation
8
9 License GPL-2.0+
10 Copyright 2010-2021 Chris Kennedy, Antonio Giner,
11 Alexandre Wodarczyk, Gil Delescluse
12
13 **************************************************************/
14
15 #include <stdio.h>
16 #include <string.h>
17 #include <cstddef>
18 #include "modeline.h"
19 #include "log.h"
20
21 #define max(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a > _b ? _a : _b; })
22 #define min(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a < _b ? _a : _b; })
23
24
25 //============================================================
26 // PROTOTYPES
27 //============================================================
28
29 int get_line_params(modeline *mode, monitor_range *range, int char_size);
30 int scale_into_range (int value, int lower_limit, int higher_limit);
31 int scale_into_range (double value, double lower_limit, double higher_limit);
32 int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff);
33 int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace);
34 int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace);
35 double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace);
36
37 //============================================================
38 // modeline_create
39 //============================================================
40
modeline_create(modeline * s_mode,modeline * t_mode,monitor_range * range,generator_settings * cs)41 int modeline_create(modeline *s_mode, modeline *t_mode, monitor_range *range, generator_settings *cs)
42 {
43 double vfreq_real = 0;
44 double interlace = 1;
45 double doublescan = 1;
46 double scan_factor = 1;
47 int x_scale = 0;
48 int y_scale = 0;
49 int v_scale = 0;
50 double x_diff = 0;
51 double y_diff = 0;
52 double v_diff = 0;
53 double y_ratio = 0;
54 double x_ratio = 0;
55 double borders = 0;
56 t_mode->result.weight = 0;
57
58 // ≈≈≈ Vertical refresh ≈≈≈
59 // try to fit vertical frequency into current range
60 v_scale = scale_into_range(t_mode->vfreq, range->vfreq_min, range->vfreq_max);
61
62 if (!v_scale && (t_mode->type & V_FREQ_EDITABLE))
63 {
64 t_mode->vfreq = t_mode->vfreq < range->vfreq_min? range->vfreq_min : range->vfreq_max;
65 v_scale = 1;
66 }
67 else if (v_scale != 1 && !(t_mode->type & V_FREQ_EDITABLE))
68 {
69 t_mode->result.weight |= R_OUT_OF_RANGE;
70 return -1;
71 }
72
73 // ≈≈≈ Vertical resolution ≈≈≈
74 // try to fit active lines in the progressive range first
75 if (range->progressive_lines_min && (!t_mode->interlace || (t_mode->type & SCAN_EDITABLE)))
76 y_scale = scale_into_range(t_mode->vactive, range->progressive_lines_min, range->progressive_lines_max);
77
78 // if not possible, try to fit in the interlaced range, if any
79 if (!y_scale && range->interlaced_lines_min && cs->interlace && (t_mode->interlace || (t_mode->type & SCAN_EDITABLE)))
80 {
81 y_scale = scale_into_range(t_mode->vactive, range->interlaced_lines_min, range->interlaced_lines_max);
82 interlace = 2;
83 }
84
85 // if we succeeded, let's see if we can apply integer scaling
86 if (y_scale == 1 || (y_scale > 1 && (t_mode->type & Y_RES_EDITABLE)))
87 {
88 // check if we should apply doublescan
89 if (cs->doublescan && y_scale % 2 == 0)
90 {
91 y_scale /= 2;
92 doublescan = 0.5;
93 }
94 scan_factor = interlace * doublescan;
95
96 // Calculate top border in case of multi-standard consumer TVs
97 if (cs->v_shift_correct)
98 borders = (range->progressive_lines_max - t_mode->vactive * y_scale / interlace) * (1.0 / range->hfreq_min) / 2;
99
100 // calculate expected achievable refresh for this height
101 vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive * y_scale, range, borders, scan_factor));
102 if (vfreq_real != t_mode->vfreq * v_scale && !(t_mode->type & V_FREQ_EDITABLE))
103 {
104 t_mode->result.weight |= R_OUT_OF_RANGE;
105 return -1;
106 }
107
108 // calculate the ratio that our scaled yres represents with respect to the original height
109 y_ratio = double(t_mode->vactive) * y_scale / s_mode->vactive;
110 int y_source_scaled = s_mode->vactive * floor(y_ratio);
111
112 // if our original height doesn't fit the target height, we're forced to stretch
113 if (!y_source_scaled)
114 t_mode->result.weight |= R_RES_STRETCH;
115
116 // otherwise we try to perform integer scaling
117 else
118 {
119 // exclude lcd ranges from raw border computation
120 if (t_mode->type & V_FREQ_EDITABLE && range->progressive_lines_max - range->progressive_lines_min > 0)
121 {
122 // calculate y borders considering physical lines (instead of logical resolution)
123 int tot_yres = total_lines_for_yres(t_mode->vactive * y_scale, vfreq_real, range, borders, scan_factor);
124 int tot_source = total_lines_for_yres(y_source_scaled, t_mode->vfreq * v_scale, range, borders, scan_factor);
125 y_diff = tot_yres > tot_source?double(tot_yres % tot_source) / tot_yres * 100:0;
126
127 // we penalize for the logical lines we need to add in order to meet the user's lower active lines limit
128 int y_min = interlace == 2?range->interlaced_lines_min:range->progressive_lines_min;
129 int tot_rest = (y_min >= y_source_scaled / doublescan)? y_min % int(y_source_scaled / doublescan):0;
130 y_diff += double(tot_rest) / tot_yres * 100;
131 }
132 else
133 y_diff = double((t_mode->vactive * y_scale) % y_source_scaled) / (t_mode->vactive * y_scale) * 100;
134
135 // we save the integer ratio between source and target resolutions, this will be used for prescaling
136 y_scale = floor(y_ratio);
137
138 // now if the borders obtained are low enough (< 10%) we'll finally apply integer scaling
139 // otherwise we'll stretch the original resolution over the target one
140 if (!(y_ratio >= 1.0 && y_ratio < 16.0 && y_diff < 10.0))
141 t_mode->result.weight |= R_RES_STRETCH;
142 }
143 }
144
145 // otherwise, check if we're allowed to apply fractional scaling
146 else if (t_mode->type & Y_RES_EDITABLE)
147 t_mode->result.weight |= R_RES_STRETCH;
148
149 // if there's nothing we can do, we're out of range
150 else
151 {
152 t_mode->result.weight |= R_OUT_OF_RANGE;
153 return -1;
154 }
155
156 // ≈≈≈ Horizontal resolution ≈≈≈
157 // make the best possible adjustment of xres depending on what happened in the previous steps
158 // let's start with the SCALED case
159 if (!(t_mode->result.weight & R_RES_STRETCH))
160 {
161 // apply integer scaling to yres
162 if (t_mode->type & Y_RES_EDITABLE) t_mode->vactive *= y_scale;
163
164 // if we can, let's apply the same scaling to both directions
165 if (t_mode->type & X_RES_EDITABLE)
166 {
167 x_scale = y_scale;
168 double aspect_corrector = max(1.0f, cs->monitor_aspect / (cs->rotation? (1.0/(STANDARD_CRT_ASPECT)) : (STANDARD_CRT_ASPECT)));
169 t_mode->hactive = normalize(double(t_mode->hactive) * double(x_scale) * aspect_corrector, 8);
170 }
171
172 // otherwise, try to get the best out of our current xres
173 else
174 {
175 x_scale = t_mode->hactive / s_mode->hactive;
176 // if the source width fits our xres, try applying integer scaling
177 if (x_scale)
178 {
179 x_scale = scale_into_aspect(s_mode->hactive, t_mode->hactive, cs->rotation?1.0/(STANDARD_CRT_ASPECT):STANDARD_CRT_ASPECT, cs->monitor_aspect, &x_diff);
180 if (x_diff > 15.0 && t_mode->hactive < cs->super_width)
181 t_mode->result.weight |= R_RES_STRETCH;
182 }
183 // otherwise apply fractional scaling
184 else
185 t_mode->result.weight |= R_RES_STRETCH;
186 }
187 }
188
189 // if the result was fractional scaling in any of the previous steps, deal with it
190 if (t_mode->result.weight & R_RES_STRETCH)
191 {
192 if (t_mode->type & Y_RES_EDITABLE)
193 {
194 // always try to use the interlaced range first if it exists, for better resolution
195 t_mode->vactive = stretch_into_range(t_mode->vfreq * v_scale, range, borders, cs->interlace, &interlace);
196
197 // check in case we couldn't achieve the desired refresh
198 vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive, range, borders, interlace));
199 }
200
201 // check if we can create a normal aspect resolution
202 if (t_mode->type & X_RES_EDITABLE)
203 t_mode->hactive = max(t_mode->hactive, normalize(STANDARD_CRT_ASPECT * t_mode->vactive, 8));
204
205 // calculate integer scale for prescaling
206 x_scale = max(1, scale_into_aspect(s_mode->hactive, t_mode->hactive, cs->rotation?1.0/(STANDARD_CRT_ASPECT):STANDARD_CRT_ASPECT, cs->monitor_aspect, &x_diff));
207 y_scale = max(1, floor(double(t_mode->vactive) / s_mode->vactive));
208
209 scan_factor = interlace;
210 doublescan = 1;
211 }
212
213 x_ratio = double(t_mode->hactive) / s_mode->hactive;
214 y_ratio = double(t_mode->vactive) / s_mode->vactive;
215 v_scale = max(round_near(vfreq_real / s_mode->vfreq), 1);
216 v_diff = (vfreq_real / v_scale) - s_mode->vfreq;
217 if (fabs(v_diff) > cs->refresh_tolerance)
218 t_mode->result.weight |= R_V_FREQ_OFF;
219
220 // ≈≈≈ Modeline generation ≈≈≈
221 // compute new modeline if we are allowed to
222 if (t_mode->type & V_FREQ_EDITABLE)
223 {
224 double margin = 0;
225 double vblank_lines = 0;
226 double vvt_ini = 0;
227
228 // Get resulting refresh
229 t_mode->vfreq = vfreq_real;
230
231 // Get total vertical lines
232 vvt_ini = total_lines_for_yres(t_mode->vactive, t_mode->vfreq, range, borders, scan_factor) + (interlace == 2?0.5:0);
233
234 // Calculate horizontal frequency
235 t_mode->hfreq = t_mode->vfreq * vvt_ini;
236
237 horizontal_values:
238
239 // Fill horizontal part of modeline
240 get_line_params(t_mode, range, cs->pixel_precision? 1 : 8);
241
242 // Calculate pixel clock
243 t_mode->pclock = t_mode->htotal * t_mode->hfreq;
244 if (t_mode->pclock <= cs->pclock_min)
245 {
246 if (t_mode->type & X_RES_EDITABLE)
247 {
248 x_scale *= 2;
249 t_mode->hactive *= 2;
250 goto horizontal_values;
251 }
252 else
253 {
254 t_mode->result.weight |= R_OUT_OF_RANGE;
255 return -1;
256 }
257 }
258
259 // Vertical blanking
260 t_mode->vtotal = vvt_ini * scan_factor;
261 vblank_lines = int(t_mode->hfreq * (range->vertical_blank + borders)) + (interlace == 2?0.5:0);
262 margin = (t_mode->vtotal - t_mode->vactive - vblank_lines * scan_factor) / (cs->v_shift_correct? 1 : 2);
263
264 t_mode->vbegin = t_mode->vactive + max(round_near(t_mode->hfreq * range->vfront_porch * scan_factor + margin), 1);
265 t_mode->vend = t_mode->vbegin + max(round_near(t_mode->hfreq * range->vsync_pulse * scan_factor), 1);
266
267 // Recalculate final vfreq
268 t_mode->vfreq = (t_mode->hfreq / t_mode->vtotal) * scan_factor;
269
270 t_mode->hsync = range->hsync_polarity;
271 t_mode->vsync = range->vsync_polarity;
272 t_mode->interlace = interlace == 2?1:0;
273 t_mode->doublescan = doublescan == 1?0:1;
274
275 // Apply interlace fixes
276 if (cs->interlace_force_even && interlace == 2)
277 {
278 t_mode->vbegin = (t_mode->vbegin / 2) * 2;
279 t_mode->vend = (t_mode->vend / 2) * 2;
280 t_mode->vtotal++;
281 }
282 }
283
284 // finally, store result
285 t_mode->result.scan_penalty = (s_mode->interlace != t_mode->interlace? 1:0) + (s_mode->doublescan != t_mode->doublescan? 1:0);
286 t_mode->result.x_scale = x_scale;
287 t_mode->result.y_scale = y_scale;
288 t_mode->result.v_scale = v_scale;
289 t_mode->result.x_diff = x_diff;
290 t_mode->result.y_diff = y_diff;
291 t_mode->result.v_diff = v_diff;
292 t_mode->result.x_ratio = x_ratio;
293 t_mode->result.y_ratio = y_ratio;
294 t_mode->result.v_ratio = 0;
295
296 return 0;
297 }
298
299 //============================================================
300 // get_line_params
301 //============================================================
302
get_line_params(modeline * mode,monitor_range * range,int char_size)303 int get_line_params(modeline *mode, monitor_range *range, int char_size)
304 {
305 int hhi, hhf, hht;
306 int hh, hs, he, ht;
307 double line_time, char_time, new_char_time;
308 double hfront_porch_min, hsync_pulse_min, hback_porch_min;
309
310 hfront_porch_min = range->hfront_porch * .90;
311 hsync_pulse_min = range->hsync_pulse * .90;
312 hback_porch_min = range->hback_porch * .90;
313
314 line_time = 1 / mode->hfreq * 1000000;
315
316 hh = round(mode->hactive / char_size);
317 hs = he = ht = 1;
318
319 do {
320 char_time = line_time / (hh + hs + he + ht);
321 if (hs * char_time < hfront_porch_min ||
322 fabs((hs + 1) * char_time - range->hfront_porch) < fabs(hs * char_time - range->hfront_porch))
323 hs++;
324
325 if (he * char_time < hsync_pulse_min ||
326 fabs((he + 1) * char_time - range->hsync_pulse) < fabs(he * char_time - range->hsync_pulse))
327 he++;
328
329 if (ht * char_time < hback_porch_min ||
330 fabs((ht + 1) * char_time - range->hback_porch) < fabs(ht * char_time - range->hback_porch))
331 ht++;
332
333 new_char_time = line_time / (hh + hs + he + ht);
334 } while (new_char_time != char_time);
335
336 hhi = (hh + hs) * char_size;
337 hhf = (hh + hs + he) * char_size;
338 hht = (hh + hs + he + ht) * char_size;
339
340 mode->hbegin = hhi;
341 mode->hend = hhf;
342 mode->htotal = hht;
343
344 return 0;
345 }
346
347 //============================================================
348 // scale_into_range
349 //============================================================
350
scale_into_range(int value,int lower_limit,int higher_limit)351 int scale_into_range (int value, int lower_limit, int higher_limit)
352 {
353 int scale = 1;
354 while (value * scale < lower_limit) scale ++;
355 if (value * scale <= higher_limit)
356 return scale;
357 else
358 return 0;
359 }
360
361 //============================================================
362 // scale_into_range
363 //============================================================
364
scale_into_range(double value,double lower_limit,double higher_limit)365 int scale_into_range (double value, double lower_limit, double higher_limit)
366 {
367 int scale = 1;
368 while (value * scale < lower_limit) scale ++;
369 if (value * scale <= higher_limit)
370 return scale;
371 else
372 return 0;
373 }
374
375 //============================================================
376 // scale_into_aspect
377 //============================================================
378
scale_into_aspect(int source_res,int tot_res,double original_monitor_aspect,double users_monitor_aspect,double * best_diff)379 int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff)
380 {
381 int scale = 1, best_scale = 1;
382 double diff = 0;
383 *best_diff = 0;
384
385 while (source_res * scale <= tot_res)
386 {
387 diff = fabs(1.0 - (users_monitor_aspect / (double(tot_res) / double(source_res * scale) * original_monitor_aspect))) * 100.0;
388 if (diff < *best_diff || *best_diff == 0)
389 {
390 *best_diff = diff;
391 best_scale = scale;
392 }
393 scale ++;
394 }
395 return best_scale;
396 }
397
398 //============================================================
399 // stretch_into_range
400 //============================================================
401
stretch_into_range(double vfreq,monitor_range * range,double borders,bool interlace_allowed,double * interlace)402 int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace)
403 {
404 int yres, lower_limit;
405
406 if (range->interlaced_lines_min && interlace_allowed)
407 {
408 yres = range->interlaced_lines_max;
409 lower_limit = range->interlaced_lines_min;
410 *interlace = 2;
411 }
412 else
413 {
414 yres = range->progressive_lines_max;
415 lower_limit = range->progressive_lines_min;
416 }
417
418 while (yres > lower_limit && max_vfreq_for_yres(yres, range, borders, *interlace) < vfreq)
419 yres -= 8;
420
421 return yres;
422 }
423
424
425 //============================================================
426 // total_lines_for_yres
427 //============================================================
428
total_lines_for_yres(int yres,double vfreq,monitor_range * range,double borders,double interlace)429 int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace)
430 {
431 int vvt = max(yres / interlace + round_near(vfreq * yres / (interlace * (1.0 - vfreq * (range->vertical_blank + borders))) * (range->vertical_blank + borders)), 1);
432 while ((vfreq * vvt < range->hfreq_min) && (vfreq * (vvt + 1) < range->hfreq_max)) vvt++;
433 return vvt;
434 }
435
436 //============================================================
437 // max_vfreq_for_yres
438 //============================================================
439
max_vfreq_for_yres(int yres,monitor_range * range,double borders,double interlace)440 double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace)
441 {
442 return range->hfreq_max / (yres / interlace + round_near(range->hfreq_max * (range->vertical_blank + borders)));
443 }
444
445 //============================================================
446 // modeline_print
447 //============================================================
448
modeline_print(modeline * mode,char * modeline,int flags)449 char * modeline_print(modeline *mode, char *modeline, int flags)
450 {
451 char label[48]={'\x00'};
452 char params[192]={'\x00'};
453
454 if (flags & MS_LABEL)
455 sprintf(label, "\"%dx%d_%d%s %.6fKHz %.6fHz\"", mode->hactive, mode->vactive, mode->refresh, mode->interlace?"i":"", mode->hfreq/1000, mode->vfreq);
456
457 if (flags & MS_LABEL_SDL)
458 sprintf(label, "\"%dx%d_%.6f\"", mode->hactive, mode->vactive, mode->vfreq);
459
460 if (flags & MS_PARAMS)
461 sprintf(params, " %.6f %d %d %d %d %d %d %d %d %s %s %s %s", double(mode->pclock)/1000000.0, mode->hactive, mode->hbegin, mode->hend, mode->htotal, mode->vactive, mode->vbegin, mode->vend, mode->vtotal,
462 mode->interlace?"interlace":"", mode->doublescan?"doublescan":"", mode->hsync?"+hsync":"-hsync", mode->vsync?"+vsync":"-vsync");
463
464 sprintf(modeline, "%s%s", label, params);
465
466 return modeline;
467 }
468
469 //============================================================
470 // modeline_result
471 //============================================================
472
modeline_result(modeline * mode,char * result)473 char * modeline_result(modeline *mode, char *result)
474 {
475 log_verbose(" rng(%d): ", mode->range);
476
477 if (mode->result.weight & R_OUT_OF_RANGE)
478 sprintf(result, " out of range");
479
480 else
481 sprintf(result, "%4d x%4d_%3.6f%s%s %3.6f [%s] scale(%d, %d, %d) diff(%.2f, %.2f, %.4f) ratio(%.3f, %.3f)",
482 mode->hactive, mode->vactive, mode->vfreq, mode->interlace?"i":"p", mode->doublescan?"d":"", mode->hfreq/1000, mode->result.weight & R_RES_STRETCH?"fract":"integ",
483 mode->result.x_scale, mode->result.y_scale, mode->result.v_scale, mode->result.x_diff, mode->result.y_diff, mode->result.v_diff, mode->result.x_ratio, mode->result.y_ratio);
484 return result;
485 }
486
487 //============================================================
488 // modeline_compare
489 //============================================================
490
modeline_compare(modeline * t,modeline * best)491 int modeline_compare(modeline *t, modeline *best)
492 {
493 bool vector = (t->hactive == (int)t->result.x_ratio);
494
495 if (t->result.weight < best->result.weight)
496 return 1;
497
498 else if (t->result.weight <= best->result.weight)
499 {
500 double t_v_diff = fabs(t->result.v_diff);
501 double b_v_diff = fabs(best->result.v_diff);
502
503 if (t->result.weight & R_RES_STRETCH || vector)
504 {
505 double t_y_score = t->result.y_ratio * (t->interlace?(2.0/3.0):1.0);
506 double b_y_score = best->result.y_ratio * (best->interlace?(2.0/3.0):1.0);
507
508 if ((t_v_diff < b_v_diff) ||
509 ((t_v_diff == b_v_diff) && (t_y_score > b_y_score)) ||
510 ((t_v_diff == b_v_diff) && (t_y_score == b_y_score) && (t->result.x_ratio > best->result.x_ratio)))
511 return 1;
512 }
513 else
514 {
515 int t_y_score = t->result.y_scale + t->result.scan_penalty;
516 int b_y_score = best->result.y_scale + best->result.scan_penalty;
517 double xy_diff = roundf((t->result.x_diff + t->result.y_diff) * 100) / 100;
518 double best_xy_diff = roundf((best->result.x_diff + best->result.y_diff) * 100) / 100;
519
520 if ((t_y_score < b_y_score) ||
521 ((t_y_score == b_y_score) && (xy_diff < best_xy_diff)) ||
522 ((t_y_score == b_y_score) && (xy_diff == best_xy_diff) && (t->result.x_scale < best->result.x_scale)) ||
523 ((t_y_score == b_y_score) && (xy_diff == best_xy_diff) && (t->result.x_scale == best->result.x_scale) && (t_v_diff < b_v_diff)))
524 return 1;
525 }
526 }
527 return 0;
528 }
529
530 //============================================================
531 // modeline_vesa_gtf
532 // Based on the VESA GTF spreadsheet by Andy Morrish 1/5/97
533 //============================================================
534
modeline_vesa_gtf(modeline * m)535 int modeline_vesa_gtf(modeline *m)
536 {
537 int C, M;
538 int v_sync_lines, v_porch_lines_min, v_front_porch_lines, v_back_porch_lines, v_sync_v_back_porch_lines, v_total_lines;
539 int h_sync_width_percent, h_sync_width_pixels, h_blanking_pixels, h_front_porch_pixels, h_total_pixels;
540 double v_freq, v_freq_est, v_freq_real, v_sync_v_back_porch;
541 double h_freq, h_period, h_period_real, h_ideal_blanking;
542 double pixel_freq, interlace;
543
544 // Check if there's a value defined for vfreq. We're assuming input vfreq is the total field vfreq regardless interlace
545 v_freq = m->vfreq? m->vfreq:double(m->refresh);
546
547 // These values are GTF defined defaults
548 v_sync_lines = 3;
549 v_porch_lines_min = 1;
550 v_front_porch_lines = v_porch_lines_min;
551 v_sync_v_back_porch = 550;
552 h_sync_width_percent = 8;
553 M = 128.0 / 256 * 600;
554 C = ((40 - 20) * 128.0 / 256) + 20;
555
556 // GTF calculation
557 interlace = m->interlace?0.5:0;
558 h_period = ((1.0 / v_freq) - (v_sync_v_back_porch / 1000000)) / ((double)m->height + v_front_porch_lines + interlace) * 1000000;
559 v_sync_v_back_porch_lines = round_near(v_sync_v_back_porch / h_period);
560 v_back_porch_lines = v_sync_v_back_porch_lines - v_sync_lines;
561 v_total_lines = m->height + v_front_porch_lines + v_sync_lines + v_back_porch_lines;
562 v_freq_est = (1.0 / h_period) / v_total_lines * 1000000;
563 h_period_real = h_period / (v_freq / v_freq_est);
564 v_freq_real = (1.0 / h_period_real) / v_total_lines * 1000000;
565 h_ideal_blanking = double(C - (M * h_period_real / 1000));
566 h_blanking_pixels = round_near(m->width * h_ideal_blanking /(100 - h_ideal_blanking) / (2 * 8)) * (2 * 8);
567 h_total_pixels = m->width + h_blanking_pixels;
568 pixel_freq = h_total_pixels / h_period_real * 1000000;
569 h_freq = 1000000 / h_period_real;
570 h_sync_width_pixels = round_near(h_sync_width_percent * h_total_pixels / 100 / 8) * 8;
571 h_front_porch_pixels = (h_blanking_pixels / 2) - h_sync_width_pixels;
572
573 // Results
574 m->hactive = m->width;
575 m->hbegin = m->hactive + h_front_porch_pixels;
576 m->hend = m->hbegin + h_sync_width_pixels;
577 m->htotal = h_total_pixels;
578 m->vactive = m->height;
579 m->vbegin = m->vactive + v_front_porch_lines;
580 m->vend = m->vbegin + v_sync_lines;
581 m->vtotal = v_total_lines;
582 m->hfreq = h_freq;
583 m->vfreq = v_freq_real;
584 m->pclock = pixel_freq;
585 m->hsync = 0;
586 m->vsync = 1;
587
588 return true;
589 }
590
591 //============================================================
592 // modeline_parse
593 //============================================================
594
modeline_parse(const char * user_modeline,modeline * mode)595 int modeline_parse(const char *user_modeline, modeline *mode)
596 {
597 char modeline_txt[256]={'\x00'};
598
599 if (!strcmp(user_modeline, "auto"))
600 return false;
601
602 // Remove quotes
603 char *quote_start, *quote_end;
604 quote_start = strstr((char*)user_modeline, "\"");
605 if (quote_start)
606 {
607 quote_start++;
608 quote_end = strstr(quote_start, "\"");
609 if (!quote_end || *quote_end++ == 0)
610 return false;
611 user_modeline = quote_end;
612 }
613
614 // Get timing flags
615 mode->interlace = strstr(user_modeline, "interlace")?1:0;
616 mode->doublescan = strstr(user_modeline, "doublescan")?1:0;
617 mode->hsync = strstr(user_modeline, "+hsync")?1:0;
618 mode->vsync = strstr(user_modeline, "+vsync")?1:0;
619
620 // Get timing values
621 double pclock;
622 int e = sscanf(user_modeline, " %lf %d %d %d %d %d %d %d %d",
623 &pclock,
624 &mode->hactive, &mode->hbegin, &mode->hend, &mode->htotal,
625 &mode->vactive, &mode->vbegin, &mode->vend, &mode->vtotal);
626
627 if (e != 9)
628 {
629 log_error("SwitchRes: missing parameter in user modeline\n %s\n", user_modeline);
630 memset(mode, 0, sizeof(struct modeline));
631 return false;
632 }
633
634 // Calculate timings
635 mode->pclock = pclock * 1000000.0;
636 mode->hfreq = mode->pclock / mode->htotal;
637 mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace?2:1);
638 mode->refresh = mode->vfreq;
639 mode->width = mode->hactive;
640 mode->height = mode->vactive;
641 log_verbose("SwitchRes: user modeline %s\n", modeline_print(mode, modeline_txt, MS_FULL));
642
643 return true;
644 }
645
646 //============================================================
647 // modeline_to_monitor_range
648 //============================================================
649
modeline_to_monitor_range(monitor_range * range,modeline * mode)650 int modeline_to_monitor_range(monitor_range *range, modeline *mode)
651 {
652 if (range->vfreq_min == 0)
653 {
654 range->vfreq_min = mode->vfreq - 0.2;
655 range->vfreq_max = mode->vfreq + 0.2;
656 }
657
658 double line_time = 1 / mode->hfreq;
659 double pixel_time = line_time / mode->htotal * 1000000;
660
661 range->hfront_porch = pixel_time * (mode->hbegin - mode->hactive);
662 range->hsync_pulse = pixel_time * (mode->hend - mode->hbegin);
663 range->hback_porch = pixel_time * (mode->htotal - mode->hend);
664
665 range->vfront_porch = line_time * (mode->vbegin - mode->vactive);
666 range->vsync_pulse = line_time * (mode->vend - mode->vbegin);
667 range->vback_porch = line_time * (mode->vtotal - mode->vend);
668 range->vertical_blank = range->vfront_porch + range->vsync_pulse + range->vback_porch;
669
670 range->hsync_polarity = mode->hsync;
671 range->vsync_polarity = mode->vsync;
672
673 range->progressive_lines_min = mode->interlace?0:mode->vactive;
674 range->progressive_lines_max = mode->interlace?0:mode->vactive;
675 range->interlaced_lines_min = mode->interlace?mode->vactive:0;
676 range->interlaced_lines_max= mode->interlace?mode->vactive:0;
677
678 range->hfreq_min = range->vfreq_min * mode->vtotal;
679 range->hfreq_max = range->vfreq_max * mode->vtotal;
680
681 return 1;
682 }
683
684 //============================================================
685 // modeline_is_different
686 //============================================================
687
modeline_is_different(modeline * n,modeline * p)688 int modeline_is_different(modeline *n, modeline *p)
689 {
690 // Remove on last fields in modeline comparison
691 return memcmp(n, p, offsetof(struct modeline, vfreq));
692 }
693
694 //============================================================
695 // monitor_fill_vesa_gtf
696 //============================================================
697
monitor_fill_vesa_gtf(monitor_range * range,const char * max_lines)698 int monitor_fill_vesa_gtf(monitor_range *range, const char *max_lines)
699 {
700 int lines = 0;
701 sscanf(max_lines, "vesa_%d", &lines);
702
703 if (!lines)
704 return 0;
705
706 int i = 0;
707 if (lines >= 480)
708 i += monitor_fill_vesa_range(&range[i], 384, 480);
709 if (lines >= 600)
710 i += monitor_fill_vesa_range(&range[i], 480, 600);
711 if (lines >= 768)
712 i += monitor_fill_vesa_range(&range[i], 600, 768);
713 if (lines >= 1024)
714 i += monitor_fill_vesa_range(&range[i], 768, 1024);
715
716 return i;
717 }
718
719 //============================================================
720 // monitor_fill_vesa_range
721 //============================================================
722
monitor_fill_vesa_range(monitor_range * range,int lines_min,int lines_max)723 int monitor_fill_vesa_range(monitor_range *range, int lines_min, int lines_max)
724 {
725 modeline mode;
726 memset(&mode, 0, sizeof(modeline));
727
728 mode.width = real_res(STANDARD_CRT_ASPECT * lines_max);
729 mode.height = lines_max;
730 mode.refresh = 60;
731 range->vfreq_min = 50;
732 range->vfreq_max = 65;
733
734 modeline_vesa_gtf(&mode);
735 modeline_to_monitor_range(range, &mode);
736
737 range->progressive_lines_min = lines_min;
738 range->hfreq_min = mode.hfreq - 500;
739 range->hfreq_max = mode.hfreq + 500;
740 monitor_show_range(range);
741
742 return 1;
743 }
744
745 //============================================================
746 // round_near
747 //============================================================
748
round_near(double number)749 int round_near(double number)
750 {
751 return number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5);
752 }
753
754 //============================================================
755 // normalize
756 //============================================================
757
normalize(int a,int b)758 int normalize(int a, int b)
759 {
760 int c, d;
761 c = a % b;
762 d = a / b;
763 if (c) d++;
764 return d * b;
765 }
766
767 //============================================================
768 // real_res
769 //============================================================
770
real_res(int x)771 int real_res(int x) {return (int) (x / 8) * 8;}
772