GPU: Rework "All Borders" cropping to be aspect correct

The "All Borders" crop mode was previously creating an aspect ratio that
was completely incorrect when using modes outside of Auto/4:3. We now
scale the aspect ratio relative to the PAL/NTSC aspect ratio to account
for this, regardless of how much of a border the game configures.

Overscan cropping also produced an incorrect aspect ratio outside of 4:3
mode, resulting in minor horizontal stretching. It is now correct,
however, this results in black borders being added in 16:9 for most
games.

To remove these borders, you have two options:

 - Use the "Stretch to Fill" aspect ratio. This will scale the GTE
   aspect ratio to fill the screen.

 - Use the "Only Overscan Area (Aspect Uncorrected)" crop mode. This
   mode retains the "old" behaviour, resulting in a stretched image.
This commit is contained in:
Stenzek 2024-11-25 16:22:31 +10:00
parent dec468966c
commit b180b26728
No known key found for this signature in database
12 changed files with 131 additions and 110 deletions

View File

@ -4323,7 +4323,7 @@ void FullscreenUI::DrawGraphicsSettingsPage()
DrawEnumSetting(bsi, FSUI_ICONSTR(ICON_FA_CROP_ALT, "Crop Mode"),
FSUI_CSTR("Determines how much of the area typically not visible on a consumer TV set to crop/hide."),
"Display", "CropMode", Settings::DEFAULT_DISPLAY_CROP_MODE, &Settings::ParseDisplayCropMode,
&Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName, DisplayCropMode::Count);
&Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName, DisplayCropMode::MaxCount);
DrawEnumSetting(
bsi, FSUI_ICONSTR(ICON_FA_EXPAND, "Scaling"),

View File

@ -594,45 +594,62 @@ float GPU::ComputeVerticalFrequency() const
float GPU::ComputeDisplayAspectRatio() const
{
if (g_settings.debugging.show_vram)
{
return static_cast<float>(VRAM_WIDTH) / static_cast<float>(VRAM_HEIGHT);
}
else if (g_settings.display_force_4_3_for_24bit && m_GPUSTAT.display_area_color_depth_24)
{
// Display off => Doesn't matter.
if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0)
return 4.0f / 3.0f;
}
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Auto)
// PAR 1:1 is not corrected.
if (g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1)
return static_cast<float>(m_crtc_state.display_width) / static_cast<float>(m_crtc_state.display_height);
float ar = 4.0f / 3.0f;
if (!g_settings.display_force_4_3_for_24bit || !m_GPUSTAT.display_area_color_depth_24)
{
const CRTCState& cs = m_crtc_state;
float relative_width = static_cast<float>(cs.horizontal_visible_end - cs.horizontal_visible_start);
float relative_height = static_cast<float>(cs.vertical_visible_end - cs.vertical_visible_start);
if (relative_width <= 0 || relative_height <= 0)
return 4.0f / 3.0f;
if (m_GPUSTAT.pal_mode)
if (g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow && g_gpu_device->HasMainSwapChain())
{
relative_width /= static_cast<float>(PAL_HORIZONTAL_ACTIVE_END - PAL_HORIZONTAL_ACTIVE_START);
relative_height /= static_cast<float>(PAL_VERTICAL_ACTIVE_END - PAL_VERTICAL_ACTIVE_START);
// Match window has already been corrected.
return static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) /
static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
}
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Custom)
{
ar = static_cast<float>(g_settings.display_aspect_ratio_custom_numerator) /
static_cast<float>(g_settings.display_aspect_ratio_custom_denominator);
}
else
{
relative_width /= static_cast<float>(NTSC_HORIZONTAL_ACTIVE_END - NTSC_HORIZONTAL_ACTIVE_START);
relative_height /= static_cast<float>(NTSC_VERTICAL_ACTIVE_END - NTSC_VERTICAL_ACTIVE_START);
ar = g_settings.GetDisplayAspectRatioValue();
}
return (relative_width / relative_height) * (4.0f / 3.0f);
}
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1)
{
if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0)
return 4.0f / 3.0f;
return static_cast<float>(m_crtc_state.display_width) / static_cast<float>(m_crtc_state.display_height);
return ComputeAspectRatioCorrection() * ar;
}
float GPU::ComputeAspectRatioCorrection() const
{
const CRTCState& cs = m_crtc_state;
float relative_width = static_cast<float>(cs.horizontal_visible_end - cs.horizontal_visible_start);
float relative_height = static_cast<float>(cs.vertical_visible_end - cs.vertical_visible_start);
if (relative_width <= 0 || relative_height <= 0 ||
g_settings.display_crop_mode == DisplayCropMode::OverscanUncorrected)
{
return 1.0f;
}
if (m_GPUSTAT.pal_mode)
{
relative_width /= static_cast<float>(PAL_HORIZONTAL_ACTIVE_END - PAL_HORIZONTAL_ACTIVE_START);
relative_height /= static_cast<float>(PAL_VERTICAL_ACTIVE_END - PAL_VERTICAL_ACTIVE_START);
}
else
{
return g_settings.GetDisplayAspectRatioValue();
relative_width /= static_cast<float>(NTSC_HORIZONTAL_ACTIVE_END - NTSC_HORIZONTAL_ACTIVE_START);
relative_height /= static_cast<float>(NTSC_VERTICAL_ACTIVE_END - NTSC_VERTICAL_ACTIVE_START);
}
return (relative_width / relative_height);
}
void GPU::UpdateCRTCConfig()
@ -725,6 +742,10 @@ void GPU::UpdateCRTCDisplayParameters()
(std::min<u16>(cs.regs.X2, horizontal_total) / cs.dot_clock_divider) * cs.dot_clock_divider;
const u16 vertical_display_start = std::min<u16>(cs.regs.Y1, vertical_total);
const u16 vertical_display_end = std::min<u16>(cs.regs.Y2, vertical_total);
const u16 old_horizontal_visible_start = cs.horizontal_visible_start;
const u16 old_horizontal_visible_end = cs.horizontal_visible_end;
const u16 old_vertical_visible_start = cs.vertical_visible_start;
const u16 old_vertical_visible_end = cs.vertical_visible_end;
if (m_GPUSTAT.pal_mode)
{
@ -739,6 +760,7 @@ void GPU::UpdateCRTCDisplayParameters()
break;
case DisplayCropMode::Overscan:
case DisplayCropMode::OverscanUncorrected:
cs.horizontal_visible_start = static_cast<u16>(std::max<int>(0, 628 + g_settings.display_active_start_offset));
cs.horizontal_visible_end =
static_cast<u16>(std::max<int>(cs.horizontal_visible_start, 3188 + g_settings.display_active_end_offset));
@ -776,6 +798,7 @@ void GPU::UpdateCRTCDisplayParameters()
break;
case DisplayCropMode::Overscan:
case DisplayCropMode::OverscanUncorrected:
cs.horizontal_visible_start = static_cast<u16>(std::max<int>(0, 608 + g_settings.display_active_start_offset));
cs.horizontal_visible_end =
static_cast<u16>(std::max<int>(cs.horizontal_visible_start, 3168 + g_settings.display_active_end_offset));
@ -872,6 +895,13 @@ void GPU::UpdateCRTCDisplayParameters()
<< height_shift;
}
if (old_horizontal_visible_start != cs.horizontal_visible_start ||
old_horizontal_visible_end != cs.horizontal_visible_end ||
old_vertical_visible_start != cs.vertical_visible_start || old_vertical_visible_end != cs.vertical_visible_end)
{
System::UpdateGTEAspectRatio();
}
if (cs.display_vram_width != old_vram_width || cs.display_vram_height != old_vram_height)
UpdateResolutionScale();
}

View File

@ -184,6 +184,7 @@ public:
float ComputeHorizontalFrequency() const;
float ComputeVerticalFrequency() const;
float ComputeDisplayAspectRatio() const;
float ComputeAspectRatioCorrection() const;
static std::unique_ptr<GPU> CreateHardwareRenderer(Error* error);
static std::unique_ptr<GPU> CreateSoftwareRenderer(Error* error);

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gte.h"
#include "cpu_core.h"
#include "cpu_core_private.h"
#include "cpu_pgxp.h"
@ -227,47 +226,22 @@ bool GTE::DoState(StateWrapper& sw)
return !sw.HasError();
}
void GTE::UpdateAspectRatio(u32 window_width, u32 window_height)
void GTE::SetAspectRatio(DisplayAspectRatio aspect, u32 custom_num, u32 custom_denom)
{
if (!g_settings.gpu_widescreen_hack)
{
s_config.aspect_ratio = DisplayAspectRatio::R4_3;
s_config.aspect_ratio = aspect;
if (aspect != DisplayAspectRatio::Custom)
return;
}
s_config.aspect_ratio = g_settings.display_aspect_ratio;
u32 num, denom;
switch (s_config.aspect_ratio)
{
case DisplayAspectRatio::MatchWindow:
{
num = window_width;
denom = window_height;
}
break;
case DisplayAspectRatio::Custom:
{
num = g_settings.display_aspect_ratio_custom_numerator;
denom = g_settings.display_aspect_ratio_custom_denominator;
}
break;
default:
return;
}
// (4 / 3) / (num / denom) => gcd((4 * denom) / (3 * num))
const u32 x = 4u * denom;
const u32 y = 3u * num;
const u32 x = 4u * custom_denom;
const u32 y = 3u * custom_num;
const u32 gcd = std::gcd(x, y);
s_config.custom_aspect_ratio_numerator = x / gcd;
s_config.custom_aspect_ratio_denominator = y / gcd;
s_config.custom_aspect_ratio_f =
static_cast<float>((4.0 / 3.0) / (static_cast<double>(num) / static_cast<double>(denom)));
static_cast<float>((4.0 / 3.0) / (static_cast<double>(custom_num) / static_cast<double>(custom_denom)));
}
u32 GTE::ReadRegister(u32 index)
@ -709,7 +683,6 @@ void GTE::RTPS(const s16 V[3], u8 shift, bool lm, bool last)
break;
case DisplayAspectRatio::Custom:
case DisplayAspectRatio::MatchWindow:
Sx = ((((s64(result) * s64(REGS.IR1)) * s64(s_config.custom_aspect_ratio_numerator)) /
s64(s_config.custom_aspect_ratio_denominator)) +
s64(REGS.OFX));
@ -764,7 +737,6 @@ void GTE::RTPS(const s16 V[3], u8 shift, bool lm, bool last)
switch (s_config.aspect_ratio)
{
case DisplayAspectRatio::MatchWindow:
case DisplayAspectRatio::Custom:
precise_x = precise_x * s_config.custom_aspect_ratio_f;
break;

View File

@ -6,12 +6,14 @@
class StateWrapper;
enum class DisplayAspectRatio : u8;
namespace GTE {
void Initialize();
void Reset();
bool DoState(StateWrapper& sw);
void UpdateAspectRatio(u32 window_width, u32 window_height);
void SetAspectRatio(DisplayAspectRatio aspect, u32 custom_num, u32 custom_denom);
// control registers are offset by +32
u32 ReadRegister(u32 index);

View File

@ -465,13 +465,11 @@ void Host::UpdateDisplayWindow(bool fullscreen)
return;
}
const u32 new_width = g_gpu_device->GetMainSwapChain()->GetWidth();
const u32 new_height = g_gpu_device->GetMainSwapChain()->GetHeight();
const float f_width = static_cast<float>(new_width);
const float f_height = static_cast<float>(new_height);
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
System::DisplayWindowResized(new_width, new_height);
System::DisplayWindowResized();
}
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
@ -489,13 +487,11 @@ void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
return;
}
const u32 new_width = g_gpu_device->GetMainSwapChain()->GetWidth();
const u32 new_height = g_gpu_device->GetMainSwapChain()->GetHeight();
const float f_width = static_cast<float>(new_width);
const float f_height = static_cast<float>(new_height);
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
System::DisplayWindowResized(new_width, new_height);
System::DisplayWindowResized();
}
void Host::ReleaseGPUDevice()

View File

@ -1614,10 +1614,11 @@ const char* Settings::GetDisplayDeinterlacingModeDisplayName(DisplayDeinterlacin
"DisplayDeinterlacingMode");
}
static constexpr const std::array s_display_crop_mode_names = {"None", "Overscan", "Borders"};
static constexpr const std::array s_display_crop_mode_names = {"None", "Overscan", "OverscanUncorrected", "Borders"};
static constexpr const std::array s_display_crop_mode_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "None", "DisplayCropMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Only Overscan Area", "DisplayCropMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Only Overscan Area (Aspect Uncorrected)", "DisplayCropMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "All Borders", "DisplayCropMode"),
};
@ -1662,7 +1663,7 @@ static constexpr const std::array s_display_aspect_ratio_names = {
"20:9",
"PAR 1:1"};
static constexpr const std::array s_display_aspect_ratio_values = {
-1.0f, -1.0f, -1.0f, 4.0f / 3.0f, 16.0f / 9.0f, 19.0f / 9.0f, 20.0f / 9.0f, -1.0f};
4.0f / 3.0f, 4.0f / 3.0f, 4.0f / 3.0f, 4.0f / 3.0f, 16.0f / 9.0f, 19.0f / 9.0f, 20.0f / 9.0f, -1.0f};
std::optional<DisplayAspectRatio> Settings::ParseDisplayAspectRatio(const char* str)
{
@ -1691,28 +1692,7 @@ const char* Settings::GetDisplayAspectRatioDisplayName(DisplayAspectRatio ar)
float Settings::GetDisplayAspectRatioValue() const
{
switch (display_aspect_ratio)
{
case DisplayAspectRatio::MatchWindow:
{
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return s_display_aspect_ratio_values[static_cast<size_t>(DEFAULT_DISPLAY_ASPECT_RATIO)];
return static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) /
static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
}
case DisplayAspectRatio::Custom:
{
return static_cast<float>(display_aspect_ratio_custom_numerator) /
static_cast<float>(display_aspect_ratio_custom_denominator);
}
default:
{
return s_display_aspect_ratio_values[static_cast<size_t>(display_aspect_ratio)];
}
}
return s_display_aspect_ratio_values[static_cast<size_t>(display_aspect_ratio)];
}
static constexpr const std::array s_display_alignment_names = {"LeftOrTop", "Center", "RightOrBottom"};

View File

@ -1941,9 +1941,6 @@ bool System::Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, b
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, fullscreen, error))
return false;
if (GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
GTE::UpdateAspectRatio(swap_chain->GetWidth(), swap_chain->GetHeight());
if (g_settings.gpu_pgxp_enable)
CPU::PGXP::Initialize();
@ -1965,6 +1962,7 @@ bool System::Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, b
s_state.cpu_thread_handle = Threading::ThreadHandle::GetForCallingThread();
UpdateGTEAspectRatio();
UpdateThrottlePeriod();
UpdateMemorySaveStateSettings();
@ -4402,8 +4400,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
(g_settings.display_aspect_ratio_custom_numerator != old_settings.display_aspect_ratio_custom_numerator ||
g_settings.display_aspect_ratio_custom_denominator != old_settings.display_aspect_ratio_custom_denominator)))
{
if (GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
GTE::UpdateAspectRatio(swap_chain->GetWidth(), swap_chain->GetHeight());
UpdateGTEAspectRatio();
}
if (g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable ||
@ -5651,8 +5648,7 @@ void System::ToggleWidescreen()
Settings::GetDisplayAspectRatioDisplayName(g_settings.display_aspect_ratio), 5.0f));
}
if (GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
GTE::UpdateAspectRatio(swap_chain->GetWidth(), swap_chain->GetHeight());
UpdateGTEAspectRatio();
}
void System::ToggleSoftwareRendering()
@ -5698,13 +5694,12 @@ void System::RequestDisplaySize(float scale /*= 0.0f*/)
Host::RequestResizeHostDisplay(static_cast<s32>(requested_width), static_cast<s32>(requested_height));
}
void System::DisplayWindowResized(u32 width, u32 height)
void System::DisplayWindowResized()
{
if (!IsValid())
return;
if (g_settings.gpu_widescreen_hack && g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow)
GTE::UpdateAspectRatio(width, height);
UpdateGTEAspectRatio();
g_gpu->RestoreDeviceContext();
g_gpu->UpdateResolutionScale();
@ -5719,6 +5714,47 @@ void System::DisplayWindowResized(u32 width, u32 height)
}
}
void System::UpdateGTEAspectRatio()
{
if (!IsValid())
return;
DisplayAspectRatio gte_ar = g_settings.display_aspect_ratio;
u32 custom_num = 0;
u32 custom_denom = 0;
if (!g_settings.gpu_widescreen_hack)
{
// No WS hack => no correction.
gte_ar = DisplayAspectRatio::R4_3;
}
else if (gte_ar == DisplayAspectRatio::Custom)
{
// Custom AR => use values.
custom_num = g_settings.display_aspect_ratio_custom_numerator;
custom_denom = g_settings.display_aspect_ratio_custom_denominator;
}
else if (gte_ar == DisplayAspectRatio::MatchWindow)
{
if (const GPUSwapChain* main_swap_chain = g_gpu_device->GetMainSwapChain())
{
// Pre-apply the native aspect ratio correction to the window size.
// MatchWindow does not correct the display aspect ratio, so we need to apply it here.
const float correction = g_gpu->ComputeAspectRatioCorrection();
custom_num =
static_cast<u32>(std::max(std::round(static_cast<float>(main_swap_chain->GetWidth()) / correction), 1.0f));
custom_denom = std::max<u32>(main_swap_chain->GetHeight(), 1u);
gte_ar = DisplayAspectRatio::Custom;
}
else
{
// Assume 4:3 until we get a window.
gte_ar = DisplayAspectRatio::R4_3;
}
}
GTE::SetAspectRatio(gte_ar, custom_num, custom_denom);
}
bool System::PresentDisplay(bool explicit_present, u64 present_time)
{
// acquire for IO.MousePos.

View File

@ -33,8 +33,11 @@ void FrameDone();
GPUVSyncMode GetEffectiveVSyncMode();
bool ShouldAllowPresentThrottle();
/// Call when host display size changes, use with "match display" aspect ratio setting.
void DisplayWindowResized(u32 width, u32 height);
/// Call when host display size changes.
void DisplayWindowResized();
/// Updates the internal GTE aspect ratio. Use with "match display" aspect ratio setting.
void UpdateGTEAspectRatio();
/// Performs mandatory hardware checks.
bool PerformEarlyHardwareChecks(Error* error);

View File

@ -141,8 +141,9 @@ enum class DisplayCropMode : u8
{
None,
Overscan,
OverscanUncorrected,
Borders,
Count
MaxCount
};
enum class DisplayAspectRatio : u8

View File

@ -671,7 +671,7 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayDeinterlacingModeDisplayName(static_cast<DisplayDeinterlacingMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::MaxCount); i++)
{
m_ui.displayCropMode->addItem(
QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));

View File

@ -2030,7 +2030,7 @@ void MainWindow::connectSignals()
Settings::DEFAULT_GPU_RENDERER, GPURenderer::Count);
SettingWidgetBinder::BindMenuToEnumSetting(
m_ui.menuCropMode, "Display", "CropMode", &Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName,
&Settings::GetDisplayCropModeDisplayName, Settings::DEFAULT_DISPLAY_CROP_MODE, DisplayCropMode::Count);
&Settings::GetDisplayCropModeDisplayName, Settings::DEFAULT_DISPLAY_CROP_MODE, DisplayCropMode::MaxCount);
SettingWidgetBinder::BindMenuToEnumSetting(m_ui.menuLogLevel, "Logging", "LogLevel", &Settings::ParseLogLevelName,
&Settings::GetLogLevelName, &Settings::GetLogLevelDisplayName,
Settings::DEFAULT_LOG_LEVEL, Log::Level::MaxCount);