mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-25 23:29:44 -06:00
Merge pull request #12170 from Filoppi/custom_aspect_ratio
Add support for custom aspect ratios
This commit is contained in:
@ -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<int, int> 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<float>(m_backbuffer_width) / static_cast<float>(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<int>* target_rect,
|
||||
@ -365,38 +401,29 @@ void* Presenter::GetNewSurfaceHandle()
|
||||
|
||||
u32 Presenter::AutoIntegralScale() const
|
||||
{
|
||||
const float efb_aspect_ratio = static_cast<float>(EFB_WIDTH) / EFB_HEIGHT;
|
||||
const float target_aspect_ratio =
|
||||
static_cast<float>(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<u32>(
|
||||
std::round((static_cast<float>(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<u32>(
|
||||
std::round((static_cast<float>(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<u32>(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<float, float> Presenter::ApplyStandardAspectCrop(float width, float height,
|
||||
bool allow_stretch) const
|
||||
{
|
||||
@ -426,13 +453,28 @@ std::tuple<float, float> 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)
|
||||
|
@ -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<int, int> CalculateOutputDimensions(int width, int height,
|
||||
bool allow_stretch = true) const;
|
||||
std::tuple<float, float> 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<int> m_target_rectangle = {};
|
||||
|
||||
u32 m_auto_resolution_scale = 1;
|
||||
|
||||
RcTcacheEntry m_xfb_entry;
|
||||
MathUtil::Rectangle<int> m_xfb_rect;
|
||||
|
||||
// Tracking of XFB textures so we don't render duplicate frames.
|
||||
u64 m_last_xfb_id = std::numeric_limits<u64>::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;
|
||||
|
||||
|
@ -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) <
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user