RetroArch/deps/switchres/modeline.cpp
Subs f1a37f7c75
Bump switchres to 2.2.1 (#16782)
* Remove switchres before bump

* Squashed 'deps/switchres/' content from commit 725e4d484a

git-subtree-dir: deps/switchres
git-subtree-split: 725e4d484a33632618dd44cdc2a61948dd833282
2024-07-18 06:25:07 -07:00

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;}