diff --git a/CMakeLists.txt b/CMakeLists.txt index 5654eb6037..1ad78dcc96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -731,7 +731,7 @@ if(PLAYER_TARGET_PLATFORM STREQUAL "SDL3") elseif(PLAYER_TARGET_PLATFORM STREQUAL "SDL2") if(NINTENDO_WII) set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 AESND OFF") - set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 AESND) + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 AESND OFF) else() set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 OFF) @@ -739,7 +739,7 @@ elseif(PLAYER_TARGET_PLATFORM STREQUAL "SDL2") elseif(PLAYER_TARGET_PLATFORM STREQUAL "SDL1") if(NINTENDO_WII) set(PLAYER_AUDIO_BACKEND "AESND" CACHE STRING "Audio system to use. Options: SDL1 AESND OFF") - set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 AESND) + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 AESND OFF) else() set(PLAYER_AUDIO_BACKEND "SDL1" CACHE STRING "Audio system to use. Options: SDL1 OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 OFF) diff --git a/src/baseui.h b/src/baseui.h index fcc8c0aeea..d4ea9a4e89 100644 --- a/src/baseui.h +++ b/src/baseui.h @@ -254,6 +254,15 @@ class BaseUi { /** Turns a touch ui on or off. */ virtual void ToggleTouchUi() {}; + /** + * Adjusts the scaling of the screen (for overscan/underscan) + * + * @param scale Screen scaling in percent (50 - 150) + */ + virtual void SetScreenScale(int scale) { + (void)scale; + } + /** * @return current video options. */ diff --git a/src/game_config.cpp b/src/game_config.cpp index 664e28aa6a..390fb252ef 100644 --- a/src/game_config.cpp +++ b/src/game_config.cpp @@ -84,6 +84,7 @@ void Game_ConfigVideo::Hide() { touch_ui.SetOptionVisible(false); pause_when_focus_lost.SetOptionVisible(false); game_resolution.SetOptionVisible(false); + screen_scale.SetOptionVisible(false); } void Game_ConfigAudio::Hide() { @@ -598,6 +599,7 @@ void Game_Config::LoadFromStream(Filesystem_Stream::InputStream& is) { video.touch_ui.FromIni(ini); video.pause_when_focus_lost.FromIni(ini); video.game_resolution.FromIni(ini); + video.screen_scale.FromIni(ini); if (ini.HasValue("Video", "WindowX") && ini.HasValue("Video", "WindowY") && ini.HasValue("Video", "WindowWidth") && ini.HasValue("Video", "WindowHeight")) { video.window_x.FromIni(ini); @@ -695,6 +697,7 @@ void Game_Config::WriteToStream(Filesystem_Stream::OutputStream& os) const { video.touch_ui.ToIni(os); video.pause_when_focus_lost.ToIni(os); video.game_resolution.ToIni(os); + video.screen_scale.ToIni(os); // only preserve when toggling between window and fullscreen is supported if (video.fullscreen.IsOptionVisible()) { diff --git a/src/game_config.h b/src/game_config.h index 280ce4d1f2..271e573972 100644 --- a/src/game_config.h +++ b/src/game_config.h @@ -134,6 +134,7 @@ struct Game_ConfigVideo { Utils::MakeSvArray("Original (Recommended)", "Widescreen (Experimental)", "Ultrawide (Experimental)"), Utils::MakeSvArray("original", "widescreen", "ultrawide"), Utils::MakeSvArray("The default resolution (320x240, 4:3)", "Can cause glitches (416x240, 16:9)", "Can cause glitches (560x240, 21:9)")}; + RangeConfigParam screen_scale{ "Scaling", "Adjust screen scaling (Overscan/Underscan)", "Video", "ScreenScale", 100, 50, 150 }; // These are never shown and are used to restore the window to the previous position ConfigParam window_x{ "", "", "Video", "WindowX", -1 }; diff --git a/src/platform/sdl/sdl2_ui.cpp b/src/platform/sdl/sdl2_ui.cpp index 201802ecaf..87e7a8ec5a 100644 --- a/src/platform/sdl/sdl2_ui.cpp +++ b/src/platform/sdl/sdl2_ui.cpp @@ -586,6 +586,11 @@ void Sdl2Ui::ToggleVsync() { #endif } +void Sdl2Ui::SetScreenScale(int scale) { + vcfg.screen_scale.Set(std::clamp(scale, 50, 150)); + window.size_changed = true; +} + void Sdl2Ui::UpdateDisplay() { #ifdef __WIIU__ if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Bilinear && window.scale > 0.f) { @@ -609,60 +614,55 @@ void Sdl2Ui::UpdateDisplay() { // Based on SDL2 function UpdateLogicalSize window.size_changed = false; - float width_float = static_cast(window.width); - float height_float = static_cast(window.height); + int win_width = window.width * vcfg.screen_scale.Get() / 100.0; + int win_height = window.height * vcfg.screen_scale.Get() / 100.0; + + int border_x = (window.width - win_width) / 2; + int border_y = (window.height - win_height) / 2; + + float width_float = static_cast(win_width); + float height_float = static_cast(win_height); float want_aspect = (float)main_surface->width() / main_surface->height(); float real_aspect = width_float / height_float; - auto do_stretch = [this]() { + auto do_stretch = [this, border_x, win_width]() { if (vcfg.stretch.Get()) { - viewport.x = 0; - viewport.w = window.width; + viewport.x = border_x; + viewport.w = win_width; } }; if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Integer) { // Integer division on purpose if (want_aspect > real_aspect) { - window.scale = static_cast(window.width / main_surface->width()); + window.scale = static_cast(win_width / main_surface->width()); } else { - window.scale = static_cast(window.height / main_surface->height()); + window.scale = static_cast(win_height / main_surface->height()); } viewport.w = static_cast(ceilf(main_surface->width() * window.scale)); - viewport.x = (window.width - viewport.w) / 2; + viewport.x = (win_width - viewport.w) / 2 + border_x; viewport.h = static_cast(ceilf(main_surface->height() * window.scale)); - viewport.y = (window.height - viewport.h) / 2; + viewport.y = (win_height - viewport.h) / 2 + border_y; do_stretch(); - SDL_RenderSetViewport(sdl_renderer, &viewport); - } else if (fabs(want_aspect - real_aspect) < 0.0001) { - // The aspect ratios are the same, let SDL2 scale it - window.scale = width_float / main_surface->width(); - SDL_RenderSetViewport(sdl_renderer, nullptr); - - // Only used here for the mouse coordinates - viewport.x = 0; - viewport.y = 0; - viewport.w = window.width; - viewport.h = window.height; } else if (want_aspect > real_aspect) { // Letterboxing (black bars top and bottom) window.scale = width_float / main_surface->width(); - viewport.x = 0; - viewport.w = window.width; + viewport.x = border_x; + viewport.w = win_width; viewport.h = static_cast(ceilf(main_surface->height() * window.scale)); - viewport.y = (window.height - viewport.h) / 2; + viewport.y = (win_height - viewport.h) / 2 + border_y; do_stretch(); SDL_RenderSetViewport(sdl_renderer, &viewport); } else { - // black bars left and right + // black bars left and right (or nothing when aspect ratio matches) window.scale = height_float / main_surface->height(); - viewport.y = 0; - viewport.h = window.height; + viewport.y = border_y; + viewport.h = win_height; viewport.w = static_cast(ceilf(main_surface->width() * window.scale)); - viewport.x = (window.width - viewport.w) / 2; + viewport.x = (win_width - viewport.w) / 2 + border_x; do_stretch(); SDL_RenderSetViewport(sdl_renderer, &viewport); } @@ -1291,6 +1291,10 @@ void Sdl2Ui::vGetConfig(Game_ConfigVideo& cfg) const { cfg.stretch.SetOptionVisible(true); cfg.game_resolution.SetOptionVisible(true); cfg.pause_when_focus_lost.SetOptionVisible(true); + cfg.screen_scale.SetOptionVisible(true); +#if defined(__wii__) + cfg.screen_scale.SetMax(100); +#endif cfg.vsync.Set(current_display_mode.vsync); cfg.window_zoom.Set(current_display_mode.zoom); diff --git a/src/platform/sdl/sdl2_ui.h b/src/platform/sdl/sdl2_ui.h index f8a1707994..56849ee53c 100644 --- a/src/platform/sdl/sdl2_ui.h +++ b/src/platform/sdl/sdl2_ui.h @@ -69,6 +69,7 @@ class Sdl2Ui final : public BaseUi { void SetScalingMode(ConfigEnum::ScalingMode) override; void ToggleStretch() override; void ToggleVsync() override; + void SetScreenScale(int scale) override; void vGetConfig(Game_ConfigVideo& cfg) const override; bool OpenURL(std::string_view url) override; Rect GetWindowMetrics() const override; diff --git a/src/platform/sdl/sdl3_ui.cpp b/src/platform/sdl/sdl3_ui.cpp index e0e810c199..c31a7bb684 100644 --- a/src/platform/sdl/sdl3_ui.cpp +++ b/src/platform/sdl/sdl3_ui.cpp @@ -517,6 +517,11 @@ void Sdl3Ui::ToggleVsync() { } } +void Sdl3Ui::SetScreenScale(int scale) { + vcfg.screen_scale.Set(std::clamp(scale, 50, 150)); + window.size_changed = true; +} + void Sdl3Ui::UpdateDisplay() { #ifdef __WIIU__ if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Bilinear && window.scale > 0.f) { @@ -540,60 +545,56 @@ void Sdl3Ui::UpdateDisplay() { // Based on SDL2 function UpdateLogicalSize window.size_changed = false; - float width_float = static_cast(window.width); - float height_float = static_cast(window.height); + int win_width = window.width * vcfg.screen_scale.Get() / 100.0; + int win_height = window.height * vcfg.screen_scale.Get() / 100.0; + + int border_x = (window.width - win_width) / 2; + int border_y = (window.height - win_height) / 2; + + float width_float = static_cast(win_width); + float height_float = static_cast(win_height); float want_aspect = (float)main_surface->width() / main_surface->height(); float real_aspect = width_float / height_float; - auto do_stretch = [this]() { + auto do_stretch = [this, border_x, win_width]() { if (vcfg.stretch.Get()) { - viewport.x = 0; - viewport.w = window.width; + viewport.x = border_x; + viewport.w = win_width; } }; if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Integer) { // Integer division on purpose if (want_aspect > real_aspect) { - window.scale = static_cast(window.width / main_surface->width()); + window.scale = static_cast(win_width / main_surface->width()); } else { - window.scale = static_cast(window.height / main_surface->height()); + window.scale = static_cast(win_height / main_surface->height()); } viewport.w = static_cast(ceilf(main_surface->width() * window.scale)); - viewport.x = (window.width - viewport.w) / 2; + viewport.x = (win_width - viewport.w) / 2 + border_x; viewport.h = static_cast(ceilf(main_surface->height() * window.scale)); - viewport.y = (window.height - viewport.h) / 2; + viewport.y = (win_height - viewport.h) / 2 + border_y; do_stretch(); SDL_SetRenderViewport(sdl_renderer, &viewport); - } else if (fabs(want_aspect - real_aspect) < 0.0001) { - // The aspect ratios are the same, let SDL2 scale it - window.scale = width_float / main_surface->width(); - SDL_SetRenderViewport(sdl_renderer, nullptr); - - // Only used here for the mouse coordinates - viewport.x = 0; - viewport.y = 0; - viewport.w = window.width; - viewport.h = window.height; } else if (want_aspect > real_aspect) { // Letterboxing (black bars top and bottom) window.scale = width_float / main_surface->width(); - viewport.x = 0; - viewport.w = window.width; + viewport.x = border_x; + viewport.w = win_width; viewport.h = static_cast(ceilf(main_surface->height() * window.scale)); - viewport.y = (window.height - viewport.h) / 2; + viewport.y = (win_height - viewport.h) / 2 + border_y; do_stretch(); SDL_SetRenderViewport(sdl_renderer, &viewport); } else { - // black bars left and right + // black bars left and right (or nothing when aspect ratio matches) window.scale = height_float / main_surface->height(); - viewport.y = 0; - viewport.h = window.height; + viewport.y = border_y; + viewport.h = win_height; viewport.w = static_cast(ceilf(main_surface->width() * window.scale)); - viewport.x = (window.width - viewport.w) / 2; + viewport.x = (win_width - viewport.w) / 2 + border_x; do_stretch(); SDL_SetRenderViewport(sdl_renderer, &viewport); } @@ -1217,6 +1218,7 @@ void Sdl3Ui::vGetConfig(Game_ConfigVideo& cfg) const { cfg.stretch.SetOptionVisible(true); cfg.game_resolution.SetOptionVisible(true); cfg.pause_when_focus_lost.SetOptionVisible(true); + cfg.screen_scale.SetOptionVisible(true); cfg.vsync.Set(current_display_mode.vsync); cfg.window_zoom.Set(current_display_mode.zoom); diff --git a/src/platform/sdl/sdl3_ui.h b/src/platform/sdl/sdl3_ui.h index badae3c773..482887ebde 100644 --- a/src/platform/sdl/sdl3_ui.h +++ b/src/platform/sdl/sdl3_ui.h @@ -69,6 +69,7 @@ class Sdl3Ui final : public BaseUi { void SetScalingMode(ConfigEnum::ScalingMode) override; void ToggleStretch() override; void ToggleVsync() override; + void SetScreenScale(int scale) override; void vGetConfig(Game_ConfigVideo& cfg) const override; bool OpenURL(std::string_view url) override; Rect GetWindowMetrics() const override; diff --git a/src/window_settings.cpp b/src/window_settings.cpp index 772f8a7a8e..198670e38e 100644 --- a/src/window_settings.cpp +++ b/src/window_settings.cpp @@ -284,6 +284,7 @@ void Window_Settings::RefreshVideo() { AddOption(cfg.pause_when_focus_lost, [cfg]() mutable { DisplayUi->SetPauseWhenFocusLost(cfg.pause_when_focus_lost.Toggle()); }); AddOption(cfg.touch_ui, [](){ DisplayUi->ToggleTouchUi(); }); AddOption(cfg.game_resolution, [this]() { DisplayUi->SetGameResolution(static_cast(GetCurrentOption().current_value)); }); + AddOption(cfg.screen_scale, [this](){ DisplayUi->SetScreenScale(GetCurrentOption().current_value); }); } void Window_Settings::RefreshAudio() {