mirror of
https://github.com/holub/mame
synced 2025-07-04 09:28:51 +03:00
emu/render.cpp: Improved scale factor selection. (#8961)
Fixes aspect related issues, undesired overscan, etc. (GitHub #8209, GitHub #8387, MT08110)
This commit is contained in:
parent
51e318e100
commit
7a6749ab86
@ -1197,63 +1197,103 @@ void render_target::compute_visible_area(s32 target_width, s32 target_height, fl
|
|||||||
|
|
||||||
// apply orientation if required
|
// apply orientation if required
|
||||||
if (target_orientation & ORIENTATION_SWAP_XY)
|
if (target_orientation & ORIENTATION_SWAP_XY)
|
||||||
src_aspect = 1.0 / src_aspect;
|
src_aspect = 1.0f / src_aspect;
|
||||||
|
|
||||||
// get target aspect
|
// we need the ratio of target to source aspect
|
||||||
float target_aspect = (float)target_width / (float)target_height * target_pixel_aspect;
|
float aspect_ratio = m_keepaspect ? (float)target_width / (float)target_height * target_pixel_aspect / src_aspect : 1.0f;
|
||||||
|
|
||||||
|
// first compute (a, b) scale factors to fit the screen
|
||||||
|
float a = (float)target_width / src_width;
|
||||||
|
float b = (float)target_height / src_height;
|
||||||
|
|
||||||
// apply automatic axial stretching if required
|
// apply automatic axial stretching if required
|
||||||
int scale_mode = m_scale_mode;
|
int scale_mode = m_scale_mode;
|
||||||
if (m_scale_mode == SCALE_FRACTIONAL_AUTO)
|
if (scale_mode == SCALE_FRACTIONAL_AUTO)
|
||||||
|
scale_mode = (m_manager.machine().system().flags & ORIENTATION_SWAP_XY) ^ (target_orientation & ORIENTATION_SWAP_XY) ?
|
||||||
|
SCALE_FRACTIONAL_Y : SCALE_FRACTIONAL_X;
|
||||||
|
|
||||||
|
// determine the scaling method for each axis
|
||||||
|
bool a_is_fract = (scale_mode == SCALE_FRACTIONAL_X || scale_mode == SCALE_FRACTIONAL);
|
||||||
|
bool b_is_fract = (scale_mode == SCALE_FRACTIONAL_Y || scale_mode == SCALE_FRACTIONAL);
|
||||||
|
|
||||||
|
// check if we have user defined scale factors, if so use them instead, but only on integer axes
|
||||||
|
int a_user = a_is_fract ? 0 : m_int_scale_x;
|
||||||
|
int b_user = b_is_fract ? 0 : m_int_scale_y;
|
||||||
|
|
||||||
|
// we allow overscan either explicitely or if integer scale factors are forced by user
|
||||||
|
bool int_overscan = m_int_overscan || (m_keepaspect && (a_user != 0 || b_user != 0));
|
||||||
|
float a_max = std::max(a, (float)a_user);
|
||||||
|
float b_max = std::max(b, (float)b_user);
|
||||||
|
|
||||||
|
|
||||||
|
// get the usable bounding box considering the type of scaling for each axis
|
||||||
|
float usable_aspect = (a_is_fract ? a : std::max(1.0f, floorf(a))) * src_width /
|
||||||
|
((b_is_fract ? b : std::max(1.0f, floorf(b))) * src_height) * target_pixel_aspect;
|
||||||
|
|
||||||
|
// depending on the relative shape between target and source, let's define 'a' and 'b' so that:
|
||||||
|
// * a is the leader axis (first to hit a boundary)
|
||||||
|
// * b is the follower axis
|
||||||
|
if (usable_aspect > src_aspect)
|
||||||
{
|
{
|
||||||
bool is_rotated = (m_manager.machine().system().flags & ORIENTATION_SWAP_XY) ^ (target_orientation & ORIENTATION_SWAP_XY);
|
std::swap(a, b);
|
||||||
scale_mode = is_rotated ? SCALE_FRACTIONAL_Y : SCALE_FRACTIONAL_X;
|
std::swap(a_user, b_user);
|
||||||
|
std::swap(a_is_fract, b_is_fract);
|
||||||
|
std::swap(a_max, b_max);
|
||||||
|
aspect_ratio = 1.0f / aspect_ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
// first compute scale factors to fit the screen
|
// now find an (a, b) pair that best fits our boundaries and scale options
|
||||||
float xscale = (float)target_width / src_width;
|
float a_best = 1.0f, b_best = 1.0f;
|
||||||
float yscale = (float)target_height / src_height;
|
float diff = 1000;
|
||||||
|
|
||||||
// apply aspect correction
|
// fill (a0, a1) range
|
||||||
if (m_keepaspect)
|
float u = a_user == 0 ? a : (float)a_user;
|
||||||
|
float a_range[] = {a_is_fract ? u : std::max(1.0f, floorf(u)), a_is_fract ? u : std::max(1.0f, roundf(u))};
|
||||||
|
|
||||||
|
for (float aa : a_range)
|
||||||
{
|
{
|
||||||
if (target_aspect > src_aspect)
|
// apply aspect correction to 'b' axis if needed, considering resulting 'a' borders
|
||||||
xscale *= src_aspect / target_aspect;
|
float ba = b * (m_keepaspect ? aspect_ratio * (aa / a) : 1.0f);
|
||||||
else
|
|
||||||
yscale *= target_aspect / src_aspect;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool x_fits = render_round_nearest(xscale) * src_width <= target_width;
|
// fill (b0, b1) range
|
||||||
bool y_fits = render_round_nearest(yscale) * src_height <= target_height;
|
float v = b_user == 0 ? ba : (float)b_user;
|
||||||
|
float b_range[] = {b_is_fract ? v : std::max(1.0f, floorf(v)), b_is_fract ? v : std::max(1.0f, roundf(v))};
|
||||||
|
|
||||||
// compute integer scale factors
|
for (float bb : b_range)
|
||||||
float integer_x = std::max(1.0f, float(m_int_overscan || x_fits ? render_round_nearest(xscale) : floor(xscale)));
|
{
|
||||||
float integer_y = std::max(1.0f, float(m_int_overscan || y_fits ? render_round_nearest(yscale) : floor(yscale)));
|
// we may need to propagate proportions back to 'a' axis
|
||||||
|
float ab = aa;
|
||||||
|
if (m_keepaspect && a_user == 0)
|
||||||
|
{
|
||||||
|
if (a_is_fract) ab *= (bb / ba);
|
||||||
|
else if (b_user != 0) ab = std::max(1.0f, roundf(ab * (bb / ba)));
|
||||||
|
}
|
||||||
|
|
||||||
// check if we have user defined scale factors, if so use them instead
|
// if overscan isn't allowed, discard values that exceed the usable bounding box, except a minimum of 1.0f
|
||||||
integer_x = m_int_scale_x > 0 ? m_int_scale_x : integer_x;
|
if (!int_overscan && ((ab > a_max && bb > 1.0f) || (bb > b_max && ab > 1.0f)))
|
||||||
integer_y = m_int_scale_y > 0 ? m_int_scale_y : integer_y;
|
continue;
|
||||||
|
|
||||||
// now apply desired scale mode
|
// score the result
|
||||||
if (scale_mode == SCALE_FRACTIONAL_X)
|
float new_diff = fabsf(aspect_ratio * (a / b) - (ab / bb));
|
||||||
{
|
|
||||||
if (m_keepaspect) xscale *= integer_y / yscale;
|
if (new_diff <= diff)
|
||||||
yscale = integer_y;
|
{
|
||||||
}
|
diff = new_diff;
|
||||||
else if (scale_mode == SCALE_FRACTIONAL_Y)
|
a_best = ab;
|
||||||
{
|
b_best = bb;
|
||||||
if (m_keepaspect) yscale *= integer_x / xscale;
|
}
|
||||||
xscale = integer_x;
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
xscale = integer_x;
|
|
||||||
yscale = integer_y;
|
|
||||||
}
|
}
|
||||||
|
a = a_best;
|
||||||
|
b = b_best;
|
||||||
|
|
||||||
|
// restore orientation
|
||||||
|
if (usable_aspect > src_aspect)
|
||||||
|
std::swap(a, b);
|
||||||
|
|
||||||
// set the final width/height
|
// set the final width/height
|
||||||
visible_width = render_round_nearest(src_width * xscale);
|
visible_width = render_round_nearest(src_width * a);
|
||||||
visible_height = render_round_nearest(src_height * yscale);
|
visible_height = render_round_nearest(src_height * b);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,6 +304,7 @@ win_window_info::win_window_info(
|
|||||||
, m_targetview(0)
|
, m_targetview(0)
|
||||||
, m_targetorient(0)
|
, m_targetorient(0)
|
||||||
, m_targetvismask(0)
|
, m_targetvismask(0)
|
||||||
|
, m_targetscalemode(0)
|
||||||
, m_lastclicktime(std::chrono::steady_clock::time_point::min())
|
, m_lastclicktime(std::chrono::steady_clock::time_point::min())
|
||||||
, m_lastclickx(0)
|
, m_lastclickx(0)
|
||||||
, m_lastclicky(0)
|
, m_lastclicky(0)
|
||||||
@ -783,12 +784,14 @@ void win_window_info::update()
|
|||||||
int const targetorient = target()->orientation();
|
int const targetorient = target()->orientation();
|
||||||
render_layer_config const targetlayerconfig = target()->layer_config();
|
render_layer_config const targetlayerconfig = target()->layer_config();
|
||||||
u32 const targetvismask = target()->visibility_mask();
|
u32 const targetvismask = target()->visibility_mask();
|
||||||
if (targetview != m_targetview || targetorient != m_targetorient || targetlayerconfig != m_targetlayerconfig || targetvismask != m_targetvismask)
|
int const targetscalemode = target()->scale_mode();
|
||||||
|
if (targetview != m_targetview || targetorient != m_targetorient || targetlayerconfig != m_targetlayerconfig || targetvismask != m_targetvismask || targetscalemode != m_targetscalemode)
|
||||||
{
|
{
|
||||||
m_targetview = targetview;
|
m_targetview = targetview;
|
||||||
m_targetorient = targetorient;
|
m_targetorient = targetorient;
|
||||||
m_targetlayerconfig = targetlayerconfig;
|
m_targetlayerconfig = targetlayerconfig;
|
||||||
m_targetvismask = targetvismask;
|
m_targetvismask = targetvismask;
|
||||||
|
m_targetscalemode = targetscalemode;
|
||||||
|
|
||||||
// in window mode, reminimize/maximize
|
// in window mode, reminimize/maximize
|
||||||
if (!fullscreen())
|
if (!fullscreen())
|
||||||
|
@ -107,6 +107,7 @@ public:
|
|||||||
int m_targetorient;
|
int m_targetorient;
|
||||||
render_layer_config m_targetlayerconfig;
|
render_layer_config m_targetlayerconfig;
|
||||||
u32 m_targetvismask;
|
u32 m_targetvismask;
|
||||||
|
int m_targetscalemode;
|
||||||
|
|
||||||
// input info
|
// input info
|
||||||
std::chrono::steady_clock::time_point m_lastclicktime;
|
std::chrono::steady_clock::time_point m_lastclicktime;
|
||||||
|
Loading…
Reference in New Issue
Block a user