diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index 747ecceb5b..4d18bd17ac 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -21,6 +21,10 @@ const Info GFX_ADAPTER{{System::GFX, "Hardware", "Adapter"}, 0}; const Info GFX_WIDESCREEN_HACK{{System::GFX, "Settings", "wideScreenHack"}, false}; const Info GFX_ASPECT_RATIO{{System::GFX, "Settings", "AspectRatio"}, AspectMode::Auto}; +const Info GFX_CUSTOM_ASPECT_RATIO_WIDTH{{System::GFX, "Settings", "CustomAspectRatioWidth"}, + 1}; +const Info GFX_CUSTOM_ASPECT_RATIO_HEIGHT{{System::GFX, "Settings", "CustomAspectRatioHeight"}, + 1}; const Info GFX_SUGGESTED_ASPECT_RATIO{{System::GFX, "Settings", "SuggestedAspectRatio"}, AspectMode::Auto}; const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD{ diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 33f08daf27..d641ce4aaa 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -28,6 +28,8 @@ extern const Info GFX_ADAPTER; extern const Info GFX_WIDESCREEN_HACK; extern const Info GFX_ASPECT_RATIO; +extern const Info GFX_CUSTOM_ASPECT_RATIO_WIDTH; +extern const Info GFX_CUSTOM_ASPECT_RATIO_HEIGHT; extern const Info GFX_SUGGESTED_ASPECT_RATIO; extern const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD; extern const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP; diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index c89ac88f84..5848d9ef16 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -477,7 +477,7 @@ float VideoInterfaceManager::GetAspectRatio() const // signal (which would be 4:3) // This function only deals with standard aspect ratios. For widescreen aspect ratios, - // multiply the result by 1.33333.. + // multiply the result by 1.33333... (the ratio between 16:9 and 4:3) // 1. Get our active area in BT.601 samples (more or less pixels) int active_lines = m_vertical_timing_register.ACV; diff --git a/Source/Core/Core/HW/VideoInterface.h b/Source/Core/Core/HW/VideoInterface.h index e68ebfc2ad..8bb1e23405 100644 --- a/Source/Core/Core/HW/VideoInterface.h +++ b/Source/Core/Core/HW/VideoInterface.h @@ -388,9 +388,9 @@ public: u32 GetTicksPerHalfLine() const; u32 GetTicksPerField() const; - // Get the aspect ratio of VI's active area. + // Get the aspect ratio of VI's active area (rarely matching pure 4:3). // This function only deals with standard aspect ratios. For widescreen aspect ratios, multiply - // the result by 1.33333.. + // the result by 1.33333... (the ratio between 16:9 and 4:3) float GetAspectRatio() const; // Create a fake VI mode for a fifolog diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp index afdedc3ea0..9f86ea5ed1 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp @@ -359,8 +359,9 @@ void AdvancedWidget::AddDescriptions() "level 9 but finish in significantly less time.

" "If unsure, leave this at 6."); static const char TR_CROPPING_DESCRIPTION[] = QT_TR_NOOP( - "Crops the picture from its native aspect ratio to 4:3 or " - "16:9.

If unsure, leave this unchecked."); + "Crops the picture from its native aspect ratio (which rarely exactly matches 4:3 or 16:9)," + " to the specific user target aspect ratio (e.g. 4:3 or 16:9).

" + "If unsure, leave this unchecked."); static const char TR_PROGRESSIVE_SCAN_DESCRIPTION[] = QT_TR_NOOP( "Enables progressive scan if supported by the emulated software. Most games don't have " "any issue with this.

If unsure, leave this " diff --git a/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp index e13f637a55..cefe86fdfb 100644 --- a/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp @@ -561,12 +561,11 @@ void EnhancementsWidget::AddDescriptions() "causes slowdowns or graphical issues.

If unsure, leave " "this unchecked."); static const char TR_WIDESCREEN_HACK_DESCRIPTION[] = QT_TR_NOOP( - "Forces the game to output graphics for any aspect ratio. Use with \"Aspect Ratio\" set to " - "\"Force 16:9\" to force 4:3-only games to run at 16:9.

Rarely produces good " - "results and " - "often partially breaks graphics and game UIs. Unnecessary (and detrimental) if using any " - "AR/Gecko-code widescreen patches.

If unsure, leave " - "this unchecked."); + "Forces the game to output graphics at any aspect ratio by expanding the view frustum " + "without stretching the image.
This is a hack, and its results will vary widely game " + "to game (it often causes the UI to stretch).
" + "Game-specific AR/Gecko-code aspect ratio patches are preferable over this if available." + "

If unsure, leave this unchecked."); static const char TR_REMOVE_FOG_DESCRIPTION[] = QT_TR_NOOP("Makes distant objects more visible by removing fog, thus increasing the overall " "detail.

Disabling fog will break some games which rely on proper fog " diff --git a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp index 6ea5f82c0c..aa4a7c5563 100644 --- a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp @@ -20,6 +20,7 @@ #include "DolphinQt/Config/ConfigControls/ConfigBool.h" #include "DolphinQt/Config/ConfigControls/ConfigChoice.h" +#include "DolphinQt/Config/ConfigControls/ConfigInteger.h" #include "DolphinQt/Config/ConfigControls/ConfigRadio.h" #include "DolphinQt/Config/Graphics/GraphicsWindow.h" #include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h" @@ -54,9 +55,20 @@ void GeneralWidget::CreateWidgets() m_video_layout = new QGridLayout(); m_backend_combo = new ToolTipComboBox(); - m_aspect_combo = - new ConfigChoice({tr("Auto"), tr("Force 16:9"), tr("Force 4:3"), tr("Stretch to Window")}, - Config::GFX_ASPECT_RATIO); + m_aspect_combo = new ConfigChoice( + {tr("Auto"), tr("Force 16:9"), tr("Force 4:3"), tr("Stretch to Window"), tr("Custom")}, + Config::GFX_ASPECT_RATIO); + m_custom_aspect_label = new QLabel(tr("Custom Aspect Ratio:")); + m_custom_aspect_label->setHidden(true); + constexpr int MAX_CUSTOM_ASPECT_RATIO_RESOLUTION = 10000; + m_custom_aspect_width = new ConfigInteger(1, MAX_CUSTOM_ASPECT_RATIO_RESOLUTION, + Config::GFX_CUSTOM_ASPECT_RATIO_WIDTH); + m_custom_aspect_width->setEnabled(false); + m_custom_aspect_width->setHidden(true); + m_custom_aspect_height = new ConfigInteger(1, MAX_CUSTOM_ASPECT_RATIO_RESOLUTION, + Config::GFX_CUSTOM_ASPECT_RATIO_HEIGHT); + m_custom_aspect_height->setEnabled(false); + m_custom_aspect_height->setHidden(true); m_adapter_combo = new ToolTipComboBox; m_enable_vsync = new ConfigBool(tr("V-Sync"), Config::GFX_VSYNC); m_enable_fullscreen = new ConfigBool(tr("Start in Fullscreen"), Config::MAIN_FULLSCREEN); @@ -70,16 +82,20 @@ void GeneralWidget::CreateWidgets() } m_video_layout->addWidget(new QLabel(tr("Backend:")), 0, 0); - m_video_layout->addWidget(m_backend_combo, 0, 1); + m_video_layout->addWidget(m_backend_combo, 0, 1, 1, -1); m_video_layout->addWidget(new QLabel(tr("Adapter:")), 1, 0); - m_video_layout->addWidget(m_adapter_combo, 1, 1); + m_video_layout->addWidget(m_adapter_combo, 1, 1, 1, -1); m_video_layout->addWidget(new QLabel(tr("Aspect Ratio:")), 3, 0); - m_video_layout->addWidget(m_aspect_combo, 3, 1); + m_video_layout->addWidget(m_aspect_combo, 3, 1, 1, -1); - m_video_layout->addWidget(m_enable_vsync, 4, 0); - m_video_layout->addWidget(m_enable_fullscreen, 4, 1); + m_video_layout->addWidget(m_custom_aspect_label, 4, 0); + m_video_layout->addWidget(m_custom_aspect_width, 4, 1); + m_video_layout->addWidget(m_custom_aspect_height, 4, 2); + + m_video_layout->addWidget(m_enable_vsync, 5, 0); + m_video_layout->addWidget(m_enable_fullscreen, 5, 1, 1, -1); // Other auto* m_options_box = new QGroupBox(tr("Other")); @@ -138,6 +154,14 @@ void GeneralWidget::ConnectWidgets() Config::SetBaseOrCurrent(Config::GFX_ADAPTER, index); emit BackendChanged(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND))); }); + connect(m_aspect_combo, qOverload(&QComboBox::currentIndexChanged), this, [&](int index) { + const bool is_custom_aspect_ratio = (index == static_cast(AspectMode::Custom)); + m_custom_aspect_width->setEnabled(is_custom_aspect_ratio); + m_custom_aspect_height->setEnabled(is_custom_aspect_ratio); + m_custom_aspect_label->setHidden(!is_custom_aspect_ratio); + m_custom_aspect_width->setHidden(!is_custom_aspect_ratio); + m_custom_aspect_height->setHidden(!is_custom_aspect_ratio); + }); } void GeneralWidget::LoadSettings() @@ -145,6 +169,13 @@ void GeneralWidget::LoadSettings() // Video Backend m_backend_combo->setCurrentIndex(m_backend_combo->findData( QVariant(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND))))); + + const bool is_custom_aspect_ratio = (Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::Custom); + m_custom_aspect_width->setEnabled(is_custom_aspect_ratio); + m_custom_aspect_height->setEnabled(is_custom_aspect_ratio); + m_custom_aspect_label->setHidden(!is_custom_aspect_ratio); + m_custom_aspect_width->setHidden(!is_custom_aspect_ratio); + m_custom_aspect_height->setHidden(!is_custom_aspect_ratio); } void GeneralWidget::SaveSettings() @@ -207,11 +238,15 @@ void GeneralWidget::AddDescriptions() QT_TR_NOOP("Uses the main Dolphin window for rendering rather than " "a separate render window.

If unsure, leave " "this unchecked."); - static const char TR_ASPECT_RATIO_DESCRIPTION[] = QT_TR_NOOP( - "Selects which aspect ratio to use when rendering.

Auto: Uses the native aspect " - "ratio
Force 16:9: Mimics an analog TV with a widescreen aspect ratio.
Force 4:3: " - "Mimics a standard 4:3 analog TV.
Stretch to Window: Stretches the picture to the " - "window size.

If unsure, select Auto."); + static const char TR_ASPECT_RATIO_DESCRIPTION[] = + QT_TR_NOOP("Selects which aspect ratio to use when rendering.
" + "Each game can have a slightly different native aspect ratio." + "

Auto: Uses the native aspect ratio" + "
Force 16:9: Mimics an analog TV with a widescreen aspect ratio." + "
Force 4:3: Mimics a standard 4:3 analog TV." + "
Stretch to Window: Stretches the picture to the window size." + "
Custom: For games running with specific custom aspect ratio cheats.

" + "If unsure, select Auto."); static const char TR_VSYNC_DESCRIPTION[] = QT_TR_NOOP( "Waits for vertical blanks in order to prevent tearing.

Decreases performance " "if emulation speed is below 100%.

If unsure, leave " @@ -260,6 +295,9 @@ void GeneralWidget::AddDescriptions() m_aspect_combo->SetTitle(tr("Aspect Ratio")); m_aspect_combo->SetDescription(tr(TR_ASPECT_RATIO_DESCRIPTION)); + m_custom_aspect_width->SetTitle(tr("Custom Aspect Ratio Width")); + m_custom_aspect_height->SetTitle(tr("Custom Aspect Ratio Height")); + m_enable_vsync->SetDescription(tr(TR_VSYNC_DESCRIPTION)); m_enable_fullscreen->SetDescription(tr(TR_FULLSCREEN_DESCRIPTION)); diff --git a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h index 346eec9013..4f908bfa30 100644 --- a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h +++ b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h @@ -9,10 +9,12 @@ class ConfigBool; class ConfigChoice; +class ConfigInteger; class ConfigRadioInt; class GraphicsWindow; class QCheckBox; class QComboBox; +class QLabel; class QRadioButton; class QGridLayout; class ToolTipComboBox; @@ -41,6 +43,9 @@ private: ToolTipComboBox* m_backend_combo; ToolTipComboBox* m_adapter_combo; ConfigChoice* m_aspect_combo; + QLabel* m_custom_aspect_label; + ConfigInteger* m_custom_aspect_width; + ConfigInteger* m_custom_aspect_height; ConfigBool* m_enable_vsync; ConfigBool* m_enable_fullscreen; diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index a21e177377..9e3f5e4a16 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -402,12 +402,15 @@ void HotkeyScheduler::Run() case AspectMode::Stretch: OSD::AddMessage("Stretch"); break; - case AspectMode::Analog: + case AspectMode::ForceStandard: OSD::AddMessage("Force 4:3"); break; - case AspectMode::AnalogWide: + case AspectMode::ForceWide: OSD::AddMessage("Force 16:9"); break; + case AspectMode::Custom: + OSD::AddMessage("Custom"); + break; case AspectMode::Auto: default: OSD::AddMessage("Auto"); diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 279d07893e..bf4b6af33b 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -4,6 +4,7 @@ #include "VideoCommon/Present.h" #include "Common/ChunkFile.h" +#include "Core/Config/GraphicsSettings.h" #include "Core/HW/VideoInterface.h" #include "Core/Host.h" #include "Core/System.h" @@ -13,6 +14,7 @@ #include "Present.h" #include "VideoCommon/AbstractGfx.h" #include "VideoCommon/FrameDumper.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/OnScreenUI.h" #include "VideoCommon/PostProcessing.h" #include "VideoCommon/Statistics.h" @@ -28,9 +30,10 @@ static constexpr int VIDEO_ENCODER_LCM = 4; namespace VideoCommon { -static float AspectToWidescreen(float aspect) +// Stretches the native/internal analog resolution aspect ratio from ~4:3 to ~16:9 +static float SourceAspectRatioToWidescreen(float source_aspect) { - return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); + return source_aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); } static std::tuple FindClosestIntegerResolution(float width, float height, @@ -201,20 +204,46 @@ void Presenter::ProcessFrameDumping(u64 ticks) const void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height) { + const bool is_first = m_backbuffer_width == 0 && m_backbuffer_height == 0; + const bool size_changed = + (m_backbuffer_width != backbuffer_width || m_backbuffer_height != backbuffer_height); m_backbuffer_width = backbuffer_width; m_backbuffer_height = backbuffer_height; UpdateDrawRectangle(); + + OnBackbufferSet(size_changed, is_first); } void Presenter::SetBackbuffer(SurfaceInfo info) { + const bool is_first = m_backbuffer_width == 0 && m_backbuffer_height == 0; + const bool size_changed = + (m_backbuffer_width != (int)info.width || m_backbuffer_height != (int)info.height); m_backbuffer_width = info.width; m_backbuffer_height = info.height; m_backbuffer_scale = info.scale; m_backbuffer_format = info.format; if (m_onscreen_ui) m_onscreen_ui->SetScale(info.scale); + + OnBackbufferSet(size_changed, is_first); +} + +void Presenter::OnBackbufferSet(bool size_changed, bool is_first_set) +{ UpdateDrawRectangle(); + + // Automatically update the resolution scale if the window size changed, + // or if the game XFB resolution changed. + if (size_changed && !is_first_set && g_ActiveConfig.iEFBScale == EFB_SCALE_AUTO_INTEGRAL && + m_auto_resolution_scale != AutoIntegralScale()) + { + g_framebuffer_manager->RecreateEFBFramebuffer(); + } + if (size_changed || is_first_set) + { + m_auto_resolution_scale = AutoIntegralScale(); + } } void Presenter::ConfigChanged(u32 changed_bits) @@ -292,15 +321,22 @@ float Presenter::CalculateDrawAspectRatio(bool allow_stretch) const return (static_cast(m_backbuffer_width) / static_cast(m_backbuffer_height)); auto& vi = Core::System::GetInstance().GetVideoInterface(); - const float aspect_ratio = vi.GetAspectRatio(); + const float source_aspect_ratio = vi.GetAspectRatio(); - if (aspect_mode == AspectMode::AnalogWide || + // This will scale up the source ~4:3 resolution to its equivalent ~16:9 resolution + if (aspect_mode == AspectMode::ForceWide || (aspect_mode == AspectMode::Auto && g_widescreen->IsGameWidescreen())) { - return AspectToWidescreen(aspect_ratio); + return SourceAspectRatioToWidescreen(source_aspect_ratio); + } + // For the "custom" mode, we force the exact target aspect ratio, without + // acknowleding the difference between the source aspect ratio and 4:3. + else if (aspect_mode == AspectMode::Custom) + { + return g_ActiveConfig.GetCustomAspectRatio(); } - return aspect_ratio; + return source_aspect_ratio; } void Presenter::AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rect, @@ -365,38 +401,29 @@ void* Presenter::GetNewSurfaceHandle() u32 Presenter::AutoIntegralScale() const { - const float efb_aspect_ratio = static_cast(EFB_WIDTH) / EFB_HEIGHT; - const float target_aspect_ratio = - static_cast(m_target_rectangle.GetWidth()) / m_target_rectangle.GetHeight(); - - u32 target_width; - u32 target_height; - - // Instead of using the entire window (back buffer) resolution, - // find the portion of it that will actually contain the EFB output, - // and ignore the portion that will likely have black bars. - if (target_aspect_ratio >= efb_aspect_ratio) - { - target_height = m_target_rectangle.GetHeight(); - target_width = static_cast( - std::round((static_cast(m_target_rectangle.GetWidth()) / target_aspect_ratio) * - efb_aspect_ratio)); - } + // Take the source resolution (XFB) and stretch it on the target aspect ratio. + // If the target resolution is larger (on either x or y), we scale the source + // by a integer multiplier until it won't have to be scaled up anymore. + u32 source_width = m_last_xfb_width; + u32 source_height = m_last_xfb_height; + const u32 target_width = m_target_rectangle.GetWidth(); + const u32 target_height = m_target_rectangle.GetHeight(); + const float source_aspect_ratio = (float)source_width / source_height; + const float target_aspect_ratio = (float)target_width / target_height; + if (source_aspect_ratio >= target_aspect_ratio) + source_width = std::round(source_height * target_aspect_ratio); else - { - target_width = m_target_rectangle.GetWidth(); - target_height = static_cast( - std::round((static_cast(m_target_rectangle.GetHeight()) * target_aspect_ratio) / - efb_aspect_ratio)); - } - - // Calculate a scale based on the adjusted window size - u32 width = EFB_WIDTH * target_width / m_last_xfb_width; - u32 height = EFB_HEIGHT * target_height / m_last_xfb_height; - return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); + source_height = std::round(source_width / target_aspect_ratio); + const u32 width_scale = + source_width > 0 ? ((target_width + (source_width - 1)) / source_width) : 1; + const u32 height_scale = + source_height > 0 ? ((target_height + (source_height - 1)) / source_height) : 1; + // Limit to the max to avoid creating textures larger than their max supported resolution. + return std::min(std::max(width_scale, height_scale), + static_cast(Config::Get(Config::GFX_MAX_EFB_SCALE))); } -void Presenter::SetWindowSize(int width, int height) +void Presenter::SetSuggestedWindowSize(int width, int height) { // While trying to guess the best window resolution, we can't allow it to use the // "AspectMode::Stretch" setting because that would self influence the output result, @@ -414,7 +441,7 @@ void Presenter::SetWindowSize(int width, int height) Host_RequestRenderWindowSize(out_width, out_height); } -// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch. +// Crop to exact forced aspect ratios if enabled and not AspectMode::Stretch. std::tuple Presenter::ApplyStandardAspectCrop(float width, float height, bool allow_stretch) const { @@ -426,13 +453,28 @@ std::tuple Presenter::ApplyStandardAspectCrop(float width, float h if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch) return {width, height}; - // Force 4:3 or 16:9 by cropping the image. + // Force aspect ratios by cropping the image. const float current_aspect = width / height; - const float expected_aspect = - (aspect_mode == AspectMode::AnalogWide || - (aspect_mode == AspectMode::Auto && g_widescreen->IsGameWidescreen())) ? - (16.0f / 9.0f) : - (4.0f / 3.0f); + float expected_aspect; + switch (aspect_mode) + { + default: + case AspectMode::Auto: + expected_aspect = g_widescreen->IsGameWidescreen() ? (16.0f / 9.0f) : (4.0f / 3.0f); + break; + case AspectMode::ForceWide: + expected_aspect = 16.0f / 9.0f; + break; + case AspectMode::ForceStandard: + expected_aspect = 4.0f / 3.0f; + break; + // There should be no cropping needed in the custom case, + // as output should always exactly match the target aspect ratio + case AspectMode::Custom: + expected_aspect = g_ActiveConfig.GetCustomAspectRatio(); + break; + } + if (current_aspect > expected_aspect) { // keep height, crop width @@ -457,11 +499,13 @@ void Presenter::UpdateDrawRectangle() if (g_ActiveConfig.bWidescreenHack) { auto& vi = Core::System::GetInstance().GetVideoInterface(); - float source_aspect = vi.GetAspectRatio(); + float source_aspect_ratio = vi.GetAspectRatio(); + // If the game is meant to be in widescreen (or forced to), + // scale the source aspect ratio to it. if (g_widescreen->IsGameWidescreen()) - source_aspect = AspectToWidescreen(source_aspect); + source_aspect_ratio = SourceAspectRatioToWidescreen(source_aspect_ratio); - const float adjust = source_aspect / draw_aspect_ratio; + const float adjust = source_aspect_ratio / draw_aspect_ratio; if (adjust > 1) { // Vert+ @@ -652,7 +696,7 @@ void Presenter::Present() // Update the window size based on the frame that was just rendered. // Due to depending on guest state, we need to call this every frame. - SetWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); + SetSuggestedWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); } return; } @@ -694,7 +738,7 @@ void Presenter::Present() { // Update the window size based on the frame that was just rendered. // Due to depending on guest state, we need to call this every frame. - SetWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); + SetSuggestedWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); } if (m_onscreen_ui) diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index c8bbf44257..3a36bf0913 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -46,18 +46,20 @@ public: void ConfigChanged(u32 changed_bits); - // Display resolution + // Window resolution (display resolution if fullscreen) int GetBackbufferWidth() const { return m_backbuffer_width; } int GetBackbufferHeight() const { return m_backbuffer_height; } float GetBackbufferScale() const { return m_backbuffer_scale; } u32 AutoIntegralScale() const; AbstractTextureFormat GetBackbufferFormat() const { return m_backbuffer_format; } - void SetWindowSize(int width, int height); + void SetSuggestedWindowSize(int width, int height); void SetBackbuffer(int backbuffer_width, int backbuffer_height); void SetBackbuffer(SurfaceInfo info); + void OnBackbufferSet(bool size_changed, bool is_first_set); void UpdateDrawRectangle(); + // Returns the target aspect ratio the XFB output should be drawn with. float CalculateDrawAspectRatio(bool allow_stretch = true) const; // Crops the target rectangle to the framebuffer dimensions, reducing the size of the source @@ -103,6 +105,8 @@ private: void ProcessFrameDumping(u64 ticks) const; + void OnBackBufferSizeChanged(); + std::tuple CalculateOutputDimensions(int width, int height, bool allow_stretch = true) const; std::tuple ApplyStandardAspectCrop(float width, float height, @@ -126,15 +130,20 @@ private: Common::Flag m_surface_changed; Common::Flag m_surface_resized; + // The presentation rectangle. + // Width and height correspond to the final output resolution. + // Offsets imply black borders (if the window aspect ratio doesn't match the game's one). MathUtil::Rectangle m_target_rectangle = {}; + u32 m_auto_resolution_scale = 1; + RcTcacheEntry m_xfb_entry; MathUtil::Rectangle m_xfb_rect; // Tracking of XFB textures so we don't render duplicate frames. u64 m_last_xfb_id = std::numeric_limits::max(); - // These will be set on the first call to SetWindowSize. + // These will be set on the first call to SetSuggestedWindowSize. int m_last_window_request_width = 0; int m_last_window_request_height = 0; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index b2f7dc87dd..17450ae0ed 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -91,8 +91,9 @@ static bool IsAnamorphicProjection(const Projection::Raw& projection, const View const VideoConfig& config) { // If ratio between our projection and viewport aspect ratios is similar to 16:9 / 4:3 - // we have an anamorphic projection. This value can be overridden - // by a GameINI. + // we have an anamorphic projection. This value can be overridden by a GameINI. + // Game cheats that change the aspect ratio to natively unsupported ones + // won't be automatically recognized here. return std::abs(CalculateProjectionViewportRatio(projection, viewport) - config.widescreen_heuristic_widescreen_ratio) < diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 426d8f7c8b..d9cb9ad0c3 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -85,6 +85,8 @@ void VideoConfig::Refresh() bWidescreenHack = Config::Get(Config::GFX_WIDESCREEN_HACK); aspect_mode = Config::Get(Config::GFX_ASPECT_RATIO); + custom_aspect_width = Config::Get(Config::GFX_CUSTOM_ASPECT_RATIO_WIDTH); + custom_aspect_height = Config::Get(Config::GFX_CUSTOM_ASPECT_RATIO_HEIGHT); suggested_aspect_mode = Config::Get(Config::GFX_SUGGESTED_ASPECT_RATIO); widescreen_heuristic_transition_threshold = Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD); @@ -287,6 +289,7 @@ void CheckForConfigChanges() const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0; const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods; + const AspectMode old_aspect_mode = g_ActiveConfig.aspect_mode; const AspectMode old_suggested_aspect_mode = g_ActiveConfig.suggested_aspect_mode; const bool old_widescreen_hack = g_ActiveConfig.bWidescreenHack; const auto old_post_processing_shader = g_ActiveConfig.sPostProcessingShader; @@ -336,6 +339,8 @@ void CheckForConfigChanges() changed_bits |= CONFIG_CHANGE_BIT_BBOX; if (old_efb_scale != g_ActiveConfig.iEFBScale) changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE; + if (old_aspect_mode != g_ActiveConfig.aspect_mode) + changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO; if (old_suggested_aspect_mode != g_ActiveConfig.suggested_aspect_mode) changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO; if (old_widescreen_hack != g_ActiveConfig.bWidescreenHack) diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 0cff6e7c66..31c10059d5 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -21,10 +21,11 @@ constexpr int EFB_SCALE_AUTO_INTEGRAL = 0; enum class AspectMode : int { - Auto, - AnalogWide, - Analog, + Auto, // 4:3 or 16:9 + ForceWide, // 16:9 + ForceStandard, // 4:3 Stretch, + Custom, }; enum class StereoMode : int @@ -105,6 +106,8 @@ struct VideoConfig final bool bVSyncActive = false; bool bWidescreenHack = false; AspectMode aspect_mode{}; + int custom_aspect_width = 1; + int custom_aspect_height = 1; AspectMode suggested_aspect_mode{}; u32 widescreen_heuristic_transition_threshold = 0; float widescreen_heuristic_aspect_ratio_slop = 0.f; @@ -365,6 +368,8 @@ struct VideoConfig final bool UsingUberShaders() const; u32 GetShaderCompilerThreads() const; u32 GetShaderPrecompilerThreads() const; + + float GetCustomAspectRatio() const { return (float)custom_aspect_width / custom_aspect_height; } }; extern VideoConfig g_Config; diff --git a/Source/Core/VideoCommon/Widescreen.cpp b/Source/Core/VideoCommon/Widescreen.cpp index 4ca1ac3ccd..8b63082525 100644 --- a/Source/Core/VideoCommon/Widescreen.cpp +++ b/Source/Core/VideoCommon/Widescreen.cpp @@ -36,23 +36,24 @@ void WidescreenManager::Update() m_is_game_widescreen = Config::Get(Config::SYSCONF_WIDESCREEN); // suggested_aspect_mode overrides SYSCONF_WIDESCREEN - if (g_ActiveConfig.suggested_aspect_mode == AspectMode::Analog) + if (g_ActiveConfig.suggested_aspect_mode == AspectMode::ForceStandard) m_is_game_widescreen = false; - else if (g_ActiveConfig.suggested_aspect_mode == AspectMode::AnalogWide) + else if (g_ActiveConfig.suggested_aspect_mode == AspectMode::ForceWide) m_is_game_widescreen = true; // If widescreen hack is disabled override game's AR if UI is set to 4:3 or 16:9. if (!g_ActiveConfig.bWidescreenHack) { const auto aspect_mode = g_ActiveConfig.aspect_mode; - if (aspect_mode == AspectMode::Analog) + if (aspect_mode == AspectMode::ForceStandard) m_is_game_widescreen = false; - else if (aspect_mode == AspectMode::AnalogWide) + else if (aspect_mode == AspectMode::ForceWide) m_is_game_widescreen = true; } } // Heuristic to detect if a GameCube game is in 16:9 anamorphic widescreen mode. +// Cheats that change the game aspect ratio to natively unsupported ones won't be recognized here. void WidescreenManager::UpdateWidescreenHeuristic() { const auto flush_statistics = g_vertex_manager->ResetFlushAspectRatioCount(); @@ -63,9 +64,10 @@ void WidescreenManager::UpdateWidescreenHeuristic() Update(); - // If widescreen hack isn't active and aspect_mode (UI) is 4:3 or 16:9 don't use heuristic. - if (!g_ActiveConfig.bWidescreenHack && (g_ActiveConfig.aspect_mode == AspectMode::Analog || - g_ActiveConfig.aspect_mode == AspectMode::AnalogWide)) + // If widescreen hack isn't active and aspect_mode (user setting) + // is set to a forced aspect ratio, don't use heuristic. + if (!g_ActiveConfig.bWidescreenHack && (g_ActiveConfig.aspect_mode == AspectMode::ForceStandard || + g_ActiveConfig.aspect_mode == AspectMode::ForceWide)) return; // Modify the threshold based on which aspect ratio we're already using: diff --git a/Source/Core/VideoCommon/Widescreen.h b/Source/Core/VideoCommon/Widescreen.h index c8dc5847ec..586aa48e84 100644 --- a/Source/Core/VideoCommon/Widescreen.h +++ b/Source/Core/VideoCommon/Widescreen.h @@ -11,11 +11,14 @@ class PointerWrap; // This class is responsible for tracking the game's aspect ratio. +// This exclusively supports 4:3 or 16:9 detection by default. class WidescreenManager { public: WidescreenManager(); + // Just a helper to tell whether the game seems to be running in widescreen, + // or if it's being forced to. bool IsGameWidescreen() const { return m_is_game_widescreen; } void DoState(PointerWrap& p);