mirror of
https://github.com/libretro/RetroArch
synced 2024-12-29 12:31:05 +00:00
f1a37f7c75
* Remove switchres before bump * Squashed 'deps/switchres/' content from commit 725e4d484a git-subtree-dir: deps/switchres git-subtree-split: 725e4d484a33632618dd44cdc2a61948dd833282
918 lines
31 KiB
C++
918 lines
31 KiB
C++
/**************************************************************
|
|
|
|
modeline.cpp - Modeline generation and scoring routines
|
|
|
|
---------------------------------------------------------
|
|
|
|
Switchres Modeline generation engine for emulation
|
|
|
|
License GPL-2.0+
|
|
Copyright 2010-2021 Chris Kennedy, Antonio Giner,
|
|
Alexandre Wodarczyk, Gil Delescluse
|
|
|
|
**************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <cstddef>
|
|
#include "modeline.h"
|
|
#include "log.h"
|
|
|
|
#define max(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a > _b ? _a : _b; })
|
|
#define min(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a < _b ? _a : _b; })
|
|
|
|
|
|
//============================================================
|
|
// PROTOTYPES
|
|
//============================================================
|
|
|
|
int get_line_params(modeline *mode, monitor_range *range, int char_size);
|
|
int scale_into_range (int value, int lower_limit, int higher_limit);
|
|
int scale_into_range (double value, double lower_limit, double higher_limit);
|
|
int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff);
|
|
int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace);
|
|
int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace);
|
|
double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace);
|
|
|
|
//============================================================
|
|
// modeline_create
|
|
//============================================================
|
|
|
|
int modeline_create(modeline *s_mode, modeline *t_mode, monitor_range *range, generator_settings *cs)
|
|
{
|
|
double vfreq_real = 0;
|
|
double interlace = 1;
|
|
double doublescan = 1;
|
|
double scan_factor = 1;
|
|
int x_scale = 0;
|
|
int y_scale = 0;
|
|
int v_scale = 0;
|
|
double x_fscale = 0;
|
|
double y_fscale = 0;
|
|
double v_fscale = 0;
|
|
double x_diff = 0;
|
|
double y_diff = 0;
|
|
double v_diff = 0;
|
|
double y_ratio = 0;
|
|
double borders = 0;
|
|
int rotation = s_mode->type & MODE_ROTATED;
|
|
double source_aspect = rotation? 1.0 / (STANDARD_CRT_ASPECT) : (STANDARD_CRT_ASPECT);
|
|
t_mode->result.weight = 0;
|
|
|
|
// ≈≈≈ Vertical refresh ≈≈≈
|
|
// try to fit vertical frequency into current range
|
|
v_scale = scale_into_range(t_mode->vfreq, range->vfreq_min, range->vfreq_max);
|
|
|
|
if (!v_scale && (t_mode->type & V_FREQ_EDITABLE))
|
|
{
|
|
t_mode->vfreq = t_mode->vfreq < range->vfreq_min? range->vfreq_min : range->vfreq_max;
|
|
v_scale = 1;
|
|
}
|
|
else if (v_scale != 1 && !(t_mode->type & V_FREQ_EDITABLE))
|
|
{
|
|
t_mode->result.weight |= R_OUT_OF_RANGE;
|
|
return -1;
|
|
}
|
|
|
|
// ≈≈≈ Vertical resolution ≈≈≈
|
|
// try to fit active lines in the progressive range first
|
|
if (range->progressive_lines_min && (!t_mode->interlace || (t_mode->type & SCAN_EDITABLE)))
|
|
y_scale = scale_into_range(t_mode->vactive, range->progressive_lines_min, range->progressive_lines_max);
|
|
|
|
// if not possible, try to fit in the interlaced range, if any
|
|
if (!y_scale && range->interlaced_lines_min && cs->interlace && (t_mode->interlace || (t_mode->type & SCAN_EDITABLE)))
|
|
{
|
|
y_scale = scale_into_range(t_mode->vactive, range->interlaced_lines_min, range->interlaced_lines_max);
|
|
interlace = 2;
|
|
}
|
|
|
|
// if we succeeded, let's see if we can apply integer scaling
|
|
if (y_scale == 1 || (y_scale > 1 && (t_mode->type & Y_RES_EDITABLE)))
|
|
{
|
|
// check if we should apply doublescan
|
|
if (cs->doublescan && y_scale % 2 == 0)
|
|
{
|
|
y_scale /= 2;
|
|
doublescan = 0.5;
|
|
}
|
|
scan_factor = interlace * doublescan;
|
|
|
|
// Calculate top border in case of multi-standard consumer TVs
|
|
if (cs->v_shift_correct)
|
|
borders = (range->progressive_lines_max - t_mode->vactive * y_scale / interlace) * (1.0 / range->hfreq_min) / 2;
|
|
|
|
// calculate expected achievable refresh for this height
|
|
vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive * y_scale, range, borders, scan_factor));
|
|
if (vfreq_real != t_mode->vfreq * v_scale && !(t_mode->type & V_FREQ_EDITABLE))
|
|
{
|
|
t_mode->result.weight |= R_OUT_OF_RANGE;
|
|
return -1;
|
|
}
|
|
|
|
// calculate the ratio that our scaled yres represents with respect to the original height
|
|
y_ratio = double(t_mode->vactive) * y_scale / s_mode->vactive;
|
|
int y_source_scaled = s_mode->vactive * floor(y_ratio);
|
|
|
|
// if our original height doesn't fit the target height, we're forced to stretch
|
|
if (!y_source_scaled)
|
|
t_mode->result.weight |= R_RES_STRETCH;
|
|
|
|
// otherwise we try to perform integer scaling
|
|
else
|
|
{
|
|
// exclude lcd ranges from raw border computation
|
|
if (t_mode->type & V_FREQ_EDITABLE && range->progressive_lines_max - range->progressive_lines_min > 0)
|
|
{
|
|
// calculate y borders considering physical lines (instead of logical resolution)
|
|
int tot_yres = total_lines_for_yres(t_mode->vactive * y_scale, vfreq_real, range, borders, scan_factor);
|
|
int tot_source = total_lines_for_yres(y_source_scaled, t_mode->vfreq * v_scale, range, borders, scan_factor);
|
|
y_diff = tot_yres > tot_source?double(tot_yres % tot_source) / tot_yres * 100:0;
|
|
|
|
// we penalize for the logical lines we need to add in order to meet the user's lower active lines limit
|
|
int y_min = interlace == 2?range->interlaced_lines_min:range->progressive_lines_min;
|
|
int tot_rest = (y_min >= y_source_scaled / doublescan)? y_min % int(y_source_scaled / doublescan):0;
|
|
y_diff += double(tot_rest) / tot_yres * 100;
|
|
}
|
|
else
|
|
y_diff = double((t_mode->vactive * y_scale) % y_source_scaled) / (t_mode->vactive * y_scale) * 100;
|
|
|
|
// we save the integer ratio between source and target resolutions, this will be used for prescaling
|
|
y_scale = floor(y_ratio);
|
|
|
|
// now if the borders obtained are low enough (< 10%) we'll finally apply integer scaling
|
|
// otherwise we'll stretch the original resolution over the target one
|
|
if (!(y_ratio >= 1.0 && y_ratio < 16.0 && y_diff < 10.0))
|
|
t_mode->result.weight |= R_RES_STRETCH;
|
|
}
|
|
}
|
|
|
|
// otherwise, check if we're allowed to apply fractional scaling
|
|
else if (t_mode->type & Y_RES_EDITABLE)
|
|
t_mode->result.weight |= R_RES_STRETCH;
|
|
|
|
// if there's nothing we can do, we're out of range
|
|
else
|
|
{
|
|
t_mode->result.weight |= R_OUT_OF_RANGE;
|
|
return -1;
|
|
}
|
|
|
|
// ≈≈≈ Horizontal resolution ≈≈≈
|
|
// make the best possible adjustment of xres depending on what happened in the previous steps
|
|
// let's start with the SCALED case
|
|
if (!(t_mode->result.weight & R_RES_STRETCH))
|
|
{
|
|
// apply integer scaling to yres
|
|
if (t_mode->type & Y_RES_EDITABLE) t_mode->vactive *= y_scale;
|
|
|
|
// if we can, let's apply the same scaling to both directions
|
|
if (t_mode->type & X_RES_EDITABLE)
|
|
{
|
|
x_scale = cs->scale_proportional? y_scale : 1;
|
|
double aspect_corrector = max(1.0f, cs->monitor_aspect / source_aspect);
|
|
t_mode->hactive = normalize(double(t_mode->hactive) * double(x_scale) * aspect_corrector, cs->pixel_precision? 1 : 8);
|
|
}
|
|
|
|
// otherwise, try to get the best out of our current xres
|
|
else
|
|
{
|
|
x_scale = t_mode->hactive / s_mode->hactive;
|
|
// if the source width fits our xres, try applying integer scaling
|
|
if (x_scale)
|
|
{
|
|
x_scale = scale_into_aspect(s_mode->hactive, t_mode->hactive, source_aspect, cs->monitor_aspect, &x_diff);
|
|
if (x_diff > 15.0 && t_mode->hactive < cs->super_width)
|
|
t_mode->result.weight |= R_RES_STRETCH;
|
|
}
|
|
// otherwise apply fractional scaling
|
|
else
|
|
t_mode->result.weight |= R_RES_STRETCH;
|
|
}
|
|
}
|
|
|
|
// if the result was fractional scaling in any of the previous steps, deal with it
|
|
if (t_mode->result.weight & R_RES_STRETCH)
|
|
{
|
|
if (t_mode->type & Y_RES_EDITABLE)
|
|
{
|
|
// always try to use the interlaced range first if it exists, for better resolution
|
|
t_mode->vactive = stretch_into_range(t_mode->vfreq * v_scale, range, borders, cs->interlace, &interlace);
|
|
|
|
// check in case we couldn't achieve the desired refresh
|
|
vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive, range, borders, interlace));
|
|
}
|
|
|
|
// check if we can create a normal aspect resolution
|
|
if (t_mode->type & X_RES_EDITABLE)
|
|
t_mode->hactive = max(t_mode->hactive, normalize(STANDARD_CRT_ASPECT * t_mode->vactive, cs->pixel_precision? 1 : 8));
|
|
|
|
// calculate integer scale for prescaling
|
|
x_scale = max(1, scale_into_aspect(s_mode->hactive, t_mode->hactive, source_aspect, cs->monitor_aspect, &x_diff));
|
|
y_scale = max(1, floor(double(t_mode->vactive) / s_mode->vactive));
|
|
|
|
scan_factor = interlace;
|
|
doublescan = 1;
|
|
}
|
|
|
|
x_fscale = double(t_mode->hactive) / s_mode->hactive * source_aspect / cs->monitor_aspect;
|
|
y_fscale = double(t_mode->vactive) / s_mode->vactive;
|
|
v_fscale = vfreq_real / s_mode->vfreq;
|
|
v_diff = (vfreq_real / v_scale) - s_mode->vfreq;
|
|
if (fabs(v_diff) > cs->refresh_tolerance)
|
|
t_mode->result.weight |= R_V_FREQ_OFF;
|
|
|
|
// ≈≈≈ Modeline generation ≈≈≈
|
|
// compute new modeline if we are allowed to
|
|
if (t_mode->type & V_FREQ_EDITABLE)
|
|
{
|
|
double margin = 0;
|
|
double vblank_lines = 0;
|
|
double vvt_ini = 0;
|
|
double interlace_incr = !cs->interlace_force_even && interlace == 2? 0.5 : 0;
|
|
|
|
// Get resulting refresh
|
|
t_mode->vfreq = vfreq_real;
|
|
|
|
// Get total vertical lines
|
|
vvt_ini = total_lines_for_yres(t_mode->vactive, t_mode->vfreq, range, borders, scan_factor) + interlace_incr;
|
|
|
|
// Calculate horizontal frequency
|
|
t_mode->hfreq = t_mode->vfreq * vvt_ini;
|
|
|
|
horizontal_values:
|
|
|
|
// Fill horizontal part of modeline
|
|
get_line_params(t_mode, range, cs->pixel_precision? 1 : 8);
|
|
|
|
// Calculate pixel clock
|
|
t_mode->pclock = t_mode->htotal * t_mode->hfreq;
|
|
if (t_mode->pclock <= cs->pclock_min)
|
|
{
|
|
if (t_mode->type & X_RES_EDITABLE)
|
|
{
|
|
x_scale *= 2;
|
|
x_fscale *= 2;
|
|
t_mode->hactive *= 2;
|
|
goto horizontal_values;
|
|
}
|
|
else
|
|
{
|
|
t_mode->result.weight |= R_OUT_OF_RANGE;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Vertical blanking
|
|
t_mode->vtotal = vvt_ini * scan_factor;
|
|
vblank_lines = round_near(t_mode->hfreq * (range->vertical_blank + borders)) + interlace_incr;
|
|
margin = (t_mode->vtotal - t_mode->vactive - vblank_lines * scan_factor) / (cs->v_shift_correct? 1 : 2);
|
|
|
|
double v_front_porch = margin + t_mode->hfreq * range->vfront_porch * scan_factor + interlace_incr;
|
|
int (*pf_round)(double) = interlace == 2? (cs->interlace_force_even? round_near_even : round_near_odd) : round_near;
|
|
|
|
t_mode->vbegin = t_mode->vactive + max(pf_round(v_front_porch), 1);
|
|
t_mode->vend = t_mode->vbegin + max(round_near(t_mode->hfreq * range->vsync_pulse * scan_factor), 1);
|
|
|
|
// Recalculate final vfreq
|
|
t_mode->vfreq = (t_mode->hfreq / t_mode->vtotal) * scan_factor;
|
|
|
|
t_mode->hsync = range->hsync_polarity;
|
|
t_mode->vsync = range->vsync_polarity;
|
|
t_mode->interlace = interlace == 2? 1 : 0;
|
|
t_mode->doublescan = doublescan == 1? 0 : 1;
|
|
}
|
|
|
|
// finally, store result
|
|
t_mode->result.scan_penalty = (s_mode->interlace != t_mode->interlace? 1 : 0) + (s_mode->doublescan != t_mode->doublescan? 1 : 0);
|
|
t_mode->result.x_scale = t_mode->result.weight & R_RES_STRETCH || t_mode->hactive >= cs->super_width ? x_fscale : (double)x_scale;
|
|
t_mode->result.y_scale = t_mode->result.weight & R_RES_STRETCH? y_fscale : (double)y_scale;
|
|
t_mode->result.v_scale = v_fscale;
|
|
t_mode->result.x_diff = x_diff;
|
|
t_mode->result.y_diff = y_diff;
|
|
t_mode->result.v_diff = v_diff;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//============================================================
|
|
// get_line_params
|
|
//============================================================
|
|
|
|
int get_line_params(modeline *mode, monitor_range *range, int char_size)
|
|
{
|
|
int hhi, hhf, hht;
|
|
int hh, hs, he, ht;
|
|
double line_time, char_time, new_char_time;
|
|
double hfront_porch_min, hsync_pulse_min, hback_porch_min;
|
|
|
|
hfront_porch_min = range->hfront_porch * .90;
|
|
hsync_pulse_min = range->hsync_pulse * .90;
|
|
hback_porch_min = range->hback_porch * .90;
|
|
|
|
line_time = 1 / mode->hfreq * 1000000;
|
|
|
|
hh = round(mode->hactive / char_size);
|
|
hs = he = ht = 1;
|
|
|
|
do {
|
|
char_time = line_time / (hh + hs + he + ht);
|
|
if (hs * char_time < hfront_porch_min ||
|
|
fabs((hs + 1) * char_time - range->hfront_porch) < fabs(hs * char_time - range->hfront_porch))
|
|
hs++;
|
|
|
|
if (he * char_time < hsync_pulse_min ||
|
|
fabs((he + 1) * char_time - range->hsync_pulse) < fabs(he * char_time - range->hsync_pulse))
|
|
he++;
|
|
|
|
if (ht * char_time < hback_porch_min ||
|
|
fabs((ht + 1) * char_time - range->hback_porch) < fabs(ht * char_time - range->hback_porch))
|
|
ht++;
|
|
|
|
new_char_time = line_time / (hh + hs + he + ht);
|
|
} while (new_char_time != char_time);
|
|
|
|
hhi = (hh + hs) * char_size;
|
|
hhf = (hh + hs + he) * char_size;
|
|
hht = (hh + hs + he + ht) * char_size;
|
|
|
|
mode->hbegin = hhi;
|
|
mode->hend = hhf;
|
|
mode->htotal = hht;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//============================================================
|
|
// scale_into_range
|
|
//============================================================
|
|
|
|
int scale_into_range (int value, int lower_limit, int higher_limit)
|
|
{
|
|
int scale = 1;
|
|
while (value * scale < lower_limit) scale ++;
|
|
if (value * scale <= higher_limit)
|
|
return scale;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//============================================================
|
|
// scale_into_range
|
|
//============================================================
|
|
|
|
int scale_into_range (double value, double lower_limit, double higher_limit)
|
|
{
|
|
int scale = 1;
|
|
while (value * scale < lower_limit) scale ++;
|
|
if (value * scale <= higher_limit)
|
|
return scale;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//============================================================
|
|
// scale_into_aspect
|
|
//============================================================
|
|
|
|
int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff)
|
|
{
|
|
int scale = 1, best_scale = 1;
|
|
double diff = 0;
|
|
*best_diff = 0;
|
|
|
|
while (source_res * scale <= tot_res)
|
|
{
|
|
diff = fabs(1.0 - (users_monitor_aspect / (double(tot_res) / double(source_res * scale) * original_monitor_aspect))) * 100.0;
|
|
if (diff < *best_diff || *best_diff == 0)
|
|
{
|
|
*best_diff = diff;
|
|
best_scale = scale;
|
|
}
|
|
scale ++;
|
|
}
|
|
return best_scale;
|
|
}
|
|
|
|
//============================================================
|
|
// stretch_into_range
|
|
//============================================================
|
|
|
|
int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace)
|
|
{
|
|
int yres, lower_limit;
|
|
|
|
if (range->interlaced_lines_min && interlace_allowed)
|
|
{
|
|
yres = range->interlaced_lines_max;
|
|
lower_limit = range->interlaced_lines_min;
|
|
*interlace = 2;
|
|
}
|
|
else
|
|
{
|
|
yres = range->progressive_lines_max;
|
|
lower_limit = range->progressive_lines_min;
|
|
}
|
|
|
|
while (yres > lower_limit && max_vfreq_for_yres(yres, range, borders, *interlace) < vfreq)
|
|
yres -= 8;
|
|
|
|
return yres;
|
|
}
|
|
|
|
|
|
//============================================================
|
|
// total_lines_for_yres
|
|
//============================================================
|
|
|
|
int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace)
|
|
{
|
|
int vvt = max(yres / interlace + round_near(vfreq * yres / (interlace * (1.0 - vfreq * (range->vertical_blank + borders))) * (range->vertical_blank + borders)), 1);
|
|
while ((vfreq * vvt < range->hfreq_min) && (vfreq * (vvt + 1) < range->hfreq_max)) vvt++;
|
|
return vvt;
|
|
}
|
|
|
|
//============================================================
|
|
// max_vfreq_for_yres
|
|
//============================================================
|
|
|
|
double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace)
|
|
{
|
|
return range->hfreq_max / (yres / interlace + round_near(range->hfreq_max * (range->vertical_blank + borders)));
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_print
|
|
//============================================================
|
|
|
|
char * modeline_print(modeline *mode, char *modeline, int flags)
|
|
{
|
|
char label[48]={'\x00'};
|
|
char params[192]={'\x00'};
|
|
|
|
if (flags & MS_LABEL)
|
|
sprintf(label, "\"%dx%d_%d%s %.6fKHz %.6fHz\"", mode->hactive, mode->vactive, mode->refresh, mode->interlace?"i":"", mode->hfreq/1000, mode->vfreq);
|
|
|
|
if (flags & MS_LABEL_SDL)
|
|
sprintf(label, "\"%dx%d_%.6f\"", mode->hactive, mode->vactive, mode->vfreq);
|
|
|
|
if (flags & MS_PARAMS)
|
|
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,
|
|
mode->interlace?"interlace":"", mode->doublescan?"doublescan":"", mode->hsync?"+hsync":"-hsync", mode->vsync?"+vsync":"-vsync");
|
|
|
|
sprintf(modeline, "%s%s", label, params);
|
|
|
|
return modeline;
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_result
|
|
//============================================================
|
|
|
|
char * modeline_result(modeline *mode, char *result)
|
|
{
|
|
log_verbose(" rng(%d): ", mode->range);
|
|
|
|
if (mode->result.weight & R_OUT_OF_RANGE)
|
|
sprintf(result, " out of range");
|
|
|
|
else
|
|
sprintf(result, "%4d x%4d_%3.6f%s%s %3.6f [%s] scale(%.3f, %.3f, %.3f) diff(%.3f, %.3f, %.3f)",
|
|
mode->hactive, mode->vactive, mode->vfreq, mode->interlace?"i":"p", mode->doublescan?"d":"", mode->hfreq/1000, mode->result.weight & R_RES_STRETCH?"fract":"integ",
|
|
mode->result.x_scale, mode->result.y_scale, mode->result.v_scale, mode->result.x_diff, mode->result.y_diff, mode->result.v_diff);
|
|
return result;
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_compare
|
|
//============================================================
|
|
|
|
int modeline_compare(modeline *t, modeline *best)
|
|
{
|
|
bool vector = (t->hactive == (int)t->result.x_scale);
|
|
|
|
if (t->result.weight < best->result.weight)
|
|
return 1;
|
|
|
|
else if (t->result.weight <= best->result.weight)
|
|
{
|
|
double t_v_diff = fabs(t->result.v_diff);
|
|
double b_v_diff = fabs(best->result.v_diff);
|
|
|
|
if (t->result.weight & R_RES_STRETCH || vector)
|
|
{
|
|
double t_y_score = t->result.y_scale * (t->interlace?(2.0/3.0):1.0);
|
|
double b_y_score = best->result.y_scale * (best->interlace?(2.0/3.0):1.0);
|
|
|
|
if ((t_v_diff < b_v_diff) ||
|
|
((t_v_diff == b_v_diff) && (t_y_score > b_y_score)) ||
|
|
((t_v_diff == b_v_diff) && (t_y_score == b_y_score) && (t->result.x_scale > best->result.x_scale)))
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
int t_y_score = t->result.y_scale + t->result.scan_penalty;
|
|
int b_y_score = best->result.y_scale + best->result.scan_penalty;
|
|
double xy_diff = roundf((t->result.x_diff + t->result.y_diff) * 100) / 100;
|
|
double best_xy_diff = roundf((best->result.x_diff + best->result.y_diff) * 100) / 100;
|
|
|
|
if ((t_y_score < b_y_score) ||
|
|
((t_y_score == b_y_score) && (xy_diff < best_xy_diff)) ||
|
|
((t_y_score == b_y_score) && (xy_diff == best_xy_diff) && (t->result.x_scale < best->result.x_scale)) ||
|
|
((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)))
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_vesa_gtf
|
|
// Based on the VESA GTF spreadsheet by Andy Morrish 1/5/97
|
|
//============================================================
|
|
|
|
int modeline_vesa_gtf(modeline *m)
|
|
{
|
|
int C, M;
|
|
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;
|
|
int h_sync_width_percent, h_sync_width_pixels, h_blanking_pixels, h_front_porch_pixels, h_total_pixels;
|
|
double v_freq, v_freq_est, v_freq_real, v_sync_v_back_porch;
|
|
double h_freq, h_period, h_period_real, h_ideal_blanking;
|
|
double pixel_freq, interlace;
|
|
|
|
// Check if there's a value defined for vfreq. We're assuming input vfreq is the total field vfreq regardless interlace
|
|
v_freq = m->vfreq? m->vfreq:double(m->refresh);
|
|
|
|
// These values are GTF defined defaults
|
|
v_sync_lines = 3;
|
|
v_porch_lines_min = 1;
|
|
v_front_porch_lines = v_porch_lines_min;
|
|
v_sync_v_back_porch = 550;
|
|
h_sync_width_percent = 8;
|
|
M = 128.0 / 256 * 600;
|
|
C = ((40 - 20) * 128.0 / 256) + 20;
|
|
|
|
// GTF calculation
|
|
interlace = m->interlace?0.5:0;
|
|
h_period = ((1.0 / v_freq) - (v_sync_v_back_porch / 1000000)) / ((double)m->height + v_front_porch_lines + interlace) * 1000000;
|
|
v_sync_v_back_porch_lines = round_near(v_sync_v_back_porch / h_period);
|
|
v_back_porch_lines = v_sync_v_back_porch_lines - v_sync_lines;
|
|
v_total_lines = m->height + v_front_porch_lines + v_sync_lines + v_back_porch_lines;
|
|
v_freq_est = (1.0 / h_period) / v_total_lines * 1000000;
|
|
h_period_real = h_period / (v_freq / v_freq_est);
|
|
v_freq_real = (1.0 / h_period_real) / v_total_lines * 1000000;
|
|
h_ideal_blanking = double(C - (M * h_period_real / 1000));
|
|
h_blanking_pixels = round_near(m->width * h_ideal_blanking /(100 - h_ideal_blanking) / (2 * 8)) * (2 * 8);
|
|
h_total_pixels = m->width + h_blanking_pixels;
|
|
pixel_freq = h_total_pixels / h_period_real * 1000000;
|
|
h_freq = 1000000 / h_period_real;
|
|
h_sync_width_pixels = round_near(h_sync_width_percent * h_total_pixels / 100 / 8) * 8;
|
|
h_front_porch_pixels = (h_blanking_pixels / 2) - h_sync_width_pixels;
|
|
|
|
// Results
|
|
m->hactive = m->width;
|
|
m->hbegin = m->hactive + h_front_porch_pixels;
|
|
m->hend = m->hbegin + h_sync_width_pixels;
|
|
m->htotal = h_total_pixels;
|
|
m->vactive = m->height;
|
|
m->vbegin = m->vactive + v_front_porch_lines;
|
|
m->vend = m->vbegin + v_sync_lines;
|
|
m->vtotal = v_total_lines;
|
|
m->hfreq = h_freq;
|
|
m->vfreq = v_freq_real;
|
|
m->pclock = pixel_freq;
|
|
m->hsync = 0;
|
|
m->vsync = 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_parse
|
|
//============================================================
|
|
|
|
int modeline_parse(const char *user_modeline, modeline *mode)
|
|
{
|
|
char modeline_txt[256]={'\x00'};
|
|
|
|
if (!strcmp(user_modeline, "auto"))
|
|
return false;
|
|
|
|
// Remove quotes
|
|
char *quote_start, *quote_end;
|
|
quote_start = strstr((char*)user_modeline, "\"");
|
|
if (quote_start)
|
|
{
|
|
quote_start++;
|
|
quote_end = strstr(quote_start, "\"");
|
|
if (!quote_end || *quote_end++ == 0)
|
|
return false;
|
|
user_modeline = quote_end;
|
|
}
|
|
|
|
// Get timing flags
|
|
mode->interlace = strstr(user_modeline, "interlace")?1:0;
|
|
mode->doublescan = strstr(user_modeline, "doublescan")?1:0;
|
|
mode->hsync = strstr(user_modeline, "+hsync")?1:0;
|
|
mode->vsync = strstr(user_modeline, "+vsync")?1:0;
|
|
|
|
// Get timing values
|
|
double pclock;
|
|
int e = sscanf(user_modeline, " %lf %d %d %d %d %d %d %d %d",
|
|
&pclock,
|
|
&mode->hactive, &mode->hbegin, &mode->hend, &mode->htotal,
|
|
&mode->vactive, &mode->vbegin, &mode->vend, &mode->vtotal);
|
|
|
|
if (e != 9)
|
|
{
|
|
log_error("Switchres: missing parameter in user modeline\n %s\n", user_modeline);
|
|
memset(mode, 0, sizeof(struct modeline));
|
|
return false;
|
|
}
|
|
|
|
// Calculate timings
|
|
mode->pclock = pclock * 1000000.0;
|
|
mode->hfreq = mode->pclock / mode->htotal;
|
|
mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace?2:1);
|
|
mode->refresh = mode->vfreq;
|
|
mode->width = mode->hactive;
|
|
mode->height = mode->vactive;
|
|
log_verbose("Switchres: user modeline %s\n", modeline_print(mode, modeline_txt, MS_FULL));
|
|
|
|
return true;
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_to_monitor_range
|
|
//============================================================
|
|
|
|
int modeline_to_monitor_range(monitor_range *range, modeline *mode)
|
|
{
|
|
// If Vfreq range is empty, create it around the provided vfreq
|
|
if (range->vfreq_min == 0.0f) range->vfreq_min = mode->vfreq - 0.2;
|
|
if (range->vfreq_max == 0.0f) range->vfreq_max = mode->vfreq + 0.2;
|
|
|
|
// Make sure the range includes the target vfreq
|
|
if (mode->vfreq < range->vfreq_min || mode->vfreq > range->vfreq_max)
|
|
return 0;
|
|
|
|
double line_time = 1 / mode->hfreq;
|
|
double pixel_time = line_time / mode->htotal * 1000000;
|
|
double interlace_factor = mode->interlace? 0.5 : 1.0;
|
|
|
|
range->hfront_porch = pixel_time * (mode->hbegin - mode->hactive);
|
|
range->hsync_pulse = pixel_time * (mode->hend - mode->hbegin);
|
|
range->hback_porch = pixel_time * (mode->htotal - mode->hend);
|
|
|
|
// We floor the vertical fields to remove the half line from interlaced modes, because
|
|
// the modeline generator will add it automatically. Otherwise it would be added twice.
|
|
range->vfront_porch = line_time * floor((mode->vbegin - mode->vactive) * interlace_factor);
|
|
range->vsync_pulse = line_time * floor((mode->vend - mode->vbegin) * interlace_factor);
|
|
range->vback_porch = line_time * floor((mode->vtotal - mode->vend) * interlace_factor);
|
|
range->vertical_blank = range->vfront_porch + range->vsync_pulse + range->vback_porch;
|
|
|
|
range->hsync_polarity = mode->hsync;
|
|
range->vsync_polarity = mode->vsync;
|
|
|
|
range->progressive_lines_min = mode->interlace? 0 : mode->vactive;
|
|
range->progressive_lines_max = mode->interlace? 0 : mode->vactive;
|
|
range->interlaced_lines_min = mode->interlace? mode->vactive : 0;
|
|
range->interlaced_lines_max= mode->interlace? mode->vactive : 0;
|
|
|
|
range->hfreq_min = range->vfreq_min * mode->vtotal * interlace_factor;
|
|
range->hfreq_max = range->vfreq_max * mode->vtotal * interlace_factor;
|
|
|
|
return 1;
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_adjust
|
|
//============================================================
|
|
|
|
int modeline_adjust(modeline *mode, double hfreq_max, generator_settings *cs)
|
|
{
|
|
// If input values are out of range, they are fixed within range and returned in the cs struct.
|
|
|
|
// H size ajdustment, valid values 0.5-2.0
|
|
if (cs->h_size != 1.0f)
|
|
{
|
|
if (cs->h_size > 2.0f)
|
|
cs->h_size = 2.0f;
|
|
else if (cs->h_size < 0.5f)
|
|
cs->h_size = 0.5f;
|
|
|
|
monitor_range range;
|
|
memset(&range, 0, sizeof(monitor_range));
|
|
|
|
modeline_to_monitor_range(&range, mode);
|
|
|
|
range.hfront_porch /= cs->h_size;
|
|
range.hback_porch /= cs->h_size;
|
|
|
|
modeline_create(mode, mode, &range, cs);
|
|
}
|
|
|
|
// H shift adjustment, positive or negative value
|
|
if (cs->h_shift != 0)
|
|
{
|
|
if (cs->h_shift >= mode->hbegin - mode->hactive)
|
|
cs->h_shift = mode->hbegin - mode->hactive - 1;
|
|
|
|
else if (cs->h_shift <= mode->hend - mode->htotal)
|
|
cs->h_shift = mode->hend - mode->htotal + 1;
|
|
|
|
mode->hbegin -= cs->h_shift;
|
|
mode->hend -= cs->h_shift;
|
|
}
|
|
|
|
// V shift adjustment, positive or negative value
|
|
if (cs->v_shift != 0)
|
|
{
|
|
int vactive = mode->vactive;
|
|
int vbegin = mode->vbegin;
|
|
int vend = mode->vend;
|
|
int vtotal = mode->vtotal;
|
|
|
|
if (mode->interlace)
|
|
{
|
|
vactive >>= 1;
|
|
vbegin >>= 1;
|
|
vend >>= 1;
|
|
vtotal >>= 1;
|
|
}
|
|
|
|
int v_front_porch = vbegin - vactive;
|
|
int v_back_porch = vend - vtotal;
|
|
int max_vtotal = hfreq_max / mode->vfreq;
|
|
int border = max_vtotal - vtotal;
|
|
int padding = 0;
|
|
|
|
// v_shift positive
|
|
if (cs->v_shift >= v_front_porch)
|
|
{
|
|
int v_front_porch_ex = v_front_porch + border;
|
|
if (cs->v_shift >= v_front_porch_ex)
|
|
cs->v_shift = v_front_porch_ex - 1;
|
|
|
|
padding = cs->v_shift - v_front_porch + 1;
|
|
vbegin += padding;
|
|
vend += padding;
|
|
vtotal += padding;
|
|
}
|
|
|
|
// v_shift negative
|
|
else if (cs->v_shift <= v_back_porch + 1)
|
|
{
|
|
int v_back_porch_ex = v_back_porch - border;
|
|
if (cs->v_shift <= v_back_porch_ex + 1)
|
|
cs->v_shift = v_back_porch_ex + 2;
|
|
|
|
padding = -(cs->v_shift - v_back_porch - 2);
|
|
vtotal += padding;
|
|
}
|
|
|
|
vbegin -= cs->v_shift;
|
|
vend -= cs->v_shift;
|
|
|
|
if (mode->interlace)
|
|
{
|
|
vbegin = (vbegin << 1) | (mode->vbegin & 1);
|
|
vend = (vend << 1) | (mode->vend & 1);
|
|
vtotal = (vtotal << 1) | (mode->vtotal & 1);
|
|
}
|
|
|
|
mode->vbegin = vbegin;
|
|
mode->vend = vend;
|
|
mode->vtotal = vtotal;
|
|
|
|
if (padding != 0)
|
|
{
|
|
mode->hfreq = mode->vfreq * mode->vtotal / (mode->interlace? 2.0 : 1.0);
|
|
|
|
monitor_range range;
|
|
memset(&range, 0, sizeof(monitor_range));
|
|
modeline_to_monitor_range(&range, mode);
|
|
monitor_show_range(&range);
|
|
modeline_create(mode, mode, &range, cs);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_is_different
|
|
//============================================================
|
|
|
|
int modeline_is_different(modeline *n, modeline *p)
|
|
{
|
|
// Remove on last fields in modeline comparison
|
|
return memcmp(n, p, offsetof(struct modeline, vfreq));
|
|
}
|
|
|
|
//============================================================
|
|
// modeline_copy_timings
|
|
//============================================================
|
|
|
|
void modeline_copy_timings(modeline *n, modeline *p)
|
|
{
|
|
// Only copy relevant timing fields
|
|
memcpy(n, p, offsetof(struct modeline, width));
|
|
}
|
|
|
|
//============================================================
|
|
// monitor_fill_vesa_gtf
|
|
//============================================================
|
|
|
|
int monitor_fill_vesa_gtf(monitor_range *range, const char *max_lines)
|
|
{
|
|
int lines = 0;
|
|
sscanf(max_lines, "vesa_%d", &lines);
|
|
|
|
if (!lines)
|
|
return 0;
|
|
|
|
int i = 0;
|
|
if (lines >= 480)
|
|
i += monitor_fill_vesa_range(&range[i], 384, 480);
|
|
if (lines >= 600)
|
|
i += monitor_fill_vesa_range(&range[i], 480, 600);
|
|
if (lines >= 768)
|
|
i += monitor_fill_vesa_range(&range[i], 600, 768);
|
|
if (lines >= 1024)
|
|
i += monitor_fill_vesa_range(&range[i], 768, 1024);
|
|
|
|
return i;
|
|
}
|
|
|
|
//============================================================
|
|
// monitor_fill_vesa_range
|
|
//============================================================
|
|
|
|
int monitor_fill_vesa_range(monitor_range *range, int lines_min, int lines_max)
|
|
{
|
|
modeline mode;
|
|
memset(&mode, 0, sizeof(modeline));
|
|
|
|
mode.width = real_res(STANDARD_CRT_ASPECT * lines_max);
|
|
mode.height = lines_max;
|
|
mode.refresh = 60;
|
|
range->vfreq_min = 50;
|
|
range->vfreq_max = 65;
|
|
|
|
modeline_vesa_gtf(&mode);
|
|
modeline_to_monitor_range(range, &mode);
|
|
|
|
range->progressive_lines_min = lines_min;
|
|
range->hfreq_min = mode.hfreq - 500;
|
|
range->hfreq_max = mode.hfreq + 500;
|
|
monitor_show_range(range);
|
|
|
|
return 1;
|
|
}
|
|
|
|
//============================================================
|
|
// round_near
|
|
//============================================================
|
|
|
|
int round_near(double number)
|
|
{
|
|
return number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5);
|
|
}
|
|
|
|
//============================================================
|
|
// round_near_odd
|
|
//============================================================
|
|
|
|
int round_near_odd(double number)
|
|
{
|
|
return int(ceil(number)) % 2 == 0? floor(number) : ceil(number);
|
|
}
|
|
|
|
//============================================================
|
|
// round_near_even
|
|
//============================================================
|
|
|
|
int round_near_even(double number)
|
|
{
|
|
return int(ceil(number)) % 2 == 1? floor(number) : ceil(number);
|
|
}
|
|
|
|
//============================================================
|
|
// normalize
|
|
//============================================================
|
|
|
|
int normalize(int a, int b)
|
|
{
|
|
int c, d;
|
|
c = a % b;
|
|
d = a / b;
|
|
if (c) d++;
|
|
return d * b;
|
|
}
|
|
|
|
//============================================================
|
|
// real_res
|
|
//============================================================
|
|
|
|
int real_res(int x) {return (int) (x / 8) * 8;}
|