diff --git a/runtime/include/aurora_events.h b/runtime/include/aurora_events.h index 27e84eaeb..7dbaf00c3 100644 --- a/runtime/include/aurora_events.h +++ b/runtime/include/aurora_events.h @@ -19,9 +19,8 @@ #endif extern "C" bool g_dynamicAspectRatioEnabled; -void ConfigureMkwDynamicAspect(bool widescreen, bool forceAspect169, uint32_t surfaceWidth, uint32_t surfaceHeight); -void SetMkwForceAspect169(bool enabled); -bool MkwForceAspect169Requested(); +void ConfigureMkwDynamicAspect(bool widescreen, bool forceAspect, uint32_t surfaceWidth, uint32_t surfaceHeight); +void SetMkwForceAspect(bool enabled); void UpdateMkwDynamicAspectSurface(uint32_t surfaceWidth, uint32_t surfaceHeight); // Arms the "keep EGG::Frustum's projection scale" flag on every screen that // renders to a fixed-size offscreen target. Cheap and idempotent; called from diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index 3648d0eaa..2f0f7a637 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -33,7 +33,8 @@ struct RuntimeUserConfig { std::optional widescreen; - std::optional forceAspect169; + std::optional forceAspect; + bool legacyForceAspect = false; std::optional windowPosX; std::optional windowPosY; std::optional windowWidth; @@ -271,7 +272,7 @@ inline void EnsureConfigFile() { "# Set paths.dvd_root to an extracted Mario Kart Wii DATA directory.\n\n" "[video]\n" "widescreen = true\n" - "force_16_9 = false\n" + "force_aspect = false\n" "resolution_multiplier = 1.0\n" "frame_interpolation_fps = 0\n" "display_mode = \"windowed\"\n" @@ -403,7 +404,15 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { } config.widescreen = FindConfigValue(document, "video", "widescreen"); - config.forceAspect169 = FindConfigValue(document, "video", "force_16_9"); + config.forceAspect = FindConfigValue(document, "video", "force_aspect"); + if (!config.forceAspect) { + // Legacy key; it always forced a 16:9 game. + config.forceAspect = FindConfigValue(document, "video", "force_16_9"); + config.legacyForceAspect = config.forceAspect.has_value(); + if (config.forceAspect.value_or(false)) { + config.widescreen = true; + } + } config.windowPosX = FindConfigInt(document, "video", "window_x"); config.windowPosY = FindConfigInt(document, "video", "window_y"); if (auto value = FindConfigUint(document, "video", "window_width"); value && *value != 0) { @@ -760,13 +769,30 @@ inline bool WidescreenEnabled(bool fallback = false) { return Get().widescreen.value_or(fallback); } -inline bool ForceAspect169Enabled(bool fallback = false) { - return Get().forceAspect169.value_or(fallback); +inline bool ForceAspectEnabled(bool fallback = false) { + return Get().forceAspect.value_or(fallback); } -inline bool SetForceAspect169(bool value) { - Mutable().forceAspect169 = value; - return WriteSetting("video", "force_16_9", value ? "true" : "false"); +inline bool SetForceAspect(bool value) { + Mutable().forceAspect = value; + return WriteSetting("video", "force_aspect", value ? "true" : "false"); +} + +// The in-memory value stays as booted; SCGetAspectRatio must not change mid-session. +inline bool SetWidescreen(bool value) { + return WriteSetting("video", "widescreen", value ? "true" : "false"); +} + +// Rewrites a legacy force_16_9 as force_aspect plus the widescreen it implied. +inline void MigrateLegacyForceAspect() { + if (!Get().legacyForceAspect) { + return; + } + const bool force = ForceAspectEnabled(); + if ((!force || WriteSetting("video", "widescreen", "true")) && + WriteSetting("video", "force_aspect", force ? "true" : "false")) { + Mutable().legacyForceAspect = false; + } } inline bool WindowPosition(int32_t& x, int32_t& y) { diff --git a/runtime/src/dynamic_aspect.cpp b/runtime/src/dynamic_aspect.cpp index 2646e0fb0..67f94fb1e 100644 --- a/runtime/src/dynamic_aspect.cpp +++ b/runtime/src/dynamic_aspect.cpp @@ -14,10 +14,9 @@ extern "C" int g_gxFrameCount; namespace { -bool g_widescreenConfigured = false; -bool g_widescreenSetting = false; -bool g_forceAspect169 = false; -std::atomic_bool g_requestedForceAspect169{false}; +bool g_widescreen = true; +bool g_forceAspect = false; +std::atomic_bool g_requestedForceAspect{false}; bool g_policyDirty = false; uint32_t g_lastEggWidth43 = 0; uint32_t g_lastEggWidth169 = 0; @@ -130,19 +129,20 @@ void WriteEggScreenRecord(uint32_t recordAddr, uint32_t width) { void ApplyEggScreenRecords(uint32_t surfaceWidth, uint32_t surfaceHeight) { using namespace MkwDynamicAspect; + if (g_forceAspect) { + surfaceWidth = g_widescreen ? 16u : 4u; + surfaceHeight = g_widescreen ? 9u : 3u; + } + const float referenceAspect = g_widescreen ? kPresentationAspect169 : kPresentationAspect43; const uint32_t width43 = - g_widescreenConfigured ? EggRecordWidth(kEggRecordWidth43, surfaceWidth, surfaceHeight) - : kEggRecordWidth43; + EggRecordWidth(kEggRecordWidth43, surfaceWidth, surfaceHeight, referenceAspect); const uint32_t width169 = - g_widescreenConfigured ? EggRecordWidth(kEggRecordWidth169, surfaceWidth, surfaceHeight) - : kEggRecordWidth169; + EggRecordWidth(kEggRecordWidth169, surfaceWidth, surfaceHeight); WriteEggScreenRecord(kEggScreenRecord43, width43); WriteEggScreenRecord(kEggScreenRecord169, width169); Memory::WriteFloat32(kEggScreenProjScaleX, 1.0f); Memory::WriteFloat32(kEggScreenProjScaleY, - g_widescreenConfigured - ? VerticalExpansion(surfaceWidth, surfaceHeight) - : 1.0f); + VerticalExpansion(surfaceWidth, surfaceHeight, referenceAspect)); AssertOffscreenScreenBypass(); if (width43 == g_lastEggWidth43 && width169 == g_lastEggWidth169) { return; @@ -172,58 +172,38 @@ void AssertMkwOffscreenScreenBypass() { } void UpdateMkwDynamicAspectSurface(uint32_t surfaceWidth, uint32_t surfaceHeight) { - const bool requestedForceAspect169 = g_requestedForceAspect169.load(std::memory_order_acquire); - if (g_forceAspect169 != requestedForceAspect169) { - g_forceAspect169 = requestedForceAspect169; - g_widescreenConfigured = g_widescreenSetting || g_forceAspect169; - g_dynamicAspectRatioEnabled = g_widescreenConfigured; + const bool requestedForceAspect = g_requestedForceAspect.load(std::memory_order_acquire); + if (g_forceAspect != requestedForceAspect) { + g_forceAspect = requestedForceAspect; g_policyDirty = true; } if (surfaceWidth == 0 || surfaceHeight == 0) { return; } - if (!g_widescreenConfigured) { - if (g_policyDirty) { + if (g_policyDirty) { + if (g_forceAspect && !g_widescreen) { AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); VILockAspectRatio(4, 3); - ApplyEggScreenRecords(surfaceWidth, surfaceHeight); - g_policyDirty = false; + } else { + AuroraSetViewportPolicy(g_forceAspect ? AURORA_VIEWPORT_16_9 : AURORA_VIEWPORT_STRETCH); + VIUnlockAspectRatio(); } - return; - } - if (g_policyDirty) { - VIUnlockAspectRatio(); g_policyDirty = false; } - AuroraSetViewportPolicy(g_forceAspect169 ? AURORA_VIEWPORT_16_9 : AURORA_VIEWPORT_STRETCH); - ApplyEggScreenRecords(g_forceAspect169 ? 16u : surfaceWidth, - g_forceAspect169 ? 9u : surfaceHeight); -} - -void SetMkwForceAspect169(bool enabled) { - g_requestedForceAspect169.store(enabled, std::memory_order_release); + ApplyEggScreenRecords(surfaceWidth, surfaceHeight); } -bool MkwForceAspect169Requested() { - return g_requestedForceAspect169.load(std::memory_order_acquire); +void SetMkwForceAspect(bool enabled) { + g_requestedForceAspect.store(enabled, std::memory_order_release); } -void ConfigureMkwDynamicAspect(bool widescreen, bool forceAspect169, uint32_t surfaceWidth, uint32_t surfaceHeight) { - g_widescreenSetting = widescreen; - g_widescreenConfigured = widescreen || forceAspect169; - g_forceAspect169 = forceAspect169; - g_requestedForceAspect169.store(forceAspect169, std::memory_order_relaxed); - g_dynamicAspectRatioEnabled = g_widescreenConfigured; +void ConfigureMkwDynamicAspect(bool widescreen, bool forceAspect, uint32_t surfaceWidth, uint32_t surfaceHeight) { + g_widescreen = widescreen; + g_forceAspect = forceAspect; + g_requestedForceAspect.store(forceAspect, std::memory_order_relaxed); + g_policyDirty = true; + g_dynamicAspectRatioEnabled = true; g_lastEggWidth43 = 0; g_lastEggWidth169 = 0; - if (g_widescreenConfigured) { - VIUnlockAspectRatio(); - ApplyEggScreenRecords(forceAspect169 ? 16u : surfaceWidth, - forceAspect169 ? 9u : surfaceHeight); - return; - } - - AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); - VILockAspectRatio(4, 3); ApplyEggScreenRecords(surfaceWidth, surfaceHeight); } diff --git a/runtime/src/hle/gx/gx_dynamic_aspect.h b/runtime/src/hle/gx/gx_dynamic_aspect.h index 7d64eea44..943f05222 100644 --- a/runtime/src/hle/gx/gx_dynamic_aspect.h +++ b/runtime/src/hle/gx/gx_dynamic_aspect.h @@ -13,6 +13,7 @@ namespace MkwDynamicAspect { inline constexpr float kPresentationAspect169 = 16.0f / 9.0f; +inline constexpr float kPresentationAspect43 = 4.0f / 3.0f; inline constexpr float kEggFramebufferWidth = 608.0f; inline constexpr uint32_t kEggScreenHeight = 456u; inline constexpr uint32_t kEggRecordWidth43 = 608u; @@ -31,32 +32,35 @@ inline float SurfaceAspect(uint32_t width, uint32_t height) noexcept { static_cast(width) / static_cast(height)); } -// surfaceAspect / (16:9). Above 1 the surface is wider than 16:9, below 1 it -// is tighter (4:3, portrait). -inline float AspectRatioFactor(uint32_t width, uint32_t height) noexcept { - return SurfaceAspect(width, height) / kPresentationAspect169; +// surfaceAspect / referenceAspect. Above 1 the surface is wider than the +// reference, below 1 it is tighter (4:3, portrait). +inline float AspectRatioFactor(uint32_t width, uint32_t height, + float referenceAspect = kPresentationAspect169) noexcept { + return SurfaceAspect(width, height) / referenceAspect; } -// Hor+ factor, clamped at 1 so a tighter-than-16:9 surface never narrows the -// canvas (that would crop away scene the 16:9 view showed). -inline float HorizontalExpansion(uint32_t width, uint32_t height) noexcept { - return std::max(1.0f, AspectRatioFactor(width, height)); +// Hor+ factor, clamped at 1 so a tighter surface never narrows the canvas +// (that would crop away scene the reference view showed). +inline float HorizontalExpansion(uint32_t width, uint32_t height, + float referenceAspect = kPresentationAspect169) noexcept { + return std::max(1.0f, AspectRatioFactor(width, height, referenceAspect)); } -// Vert+ factor, clamped at 1 so a wider-than-16:9 surface never shortens the -// frustum. Exactly one of the two expansions is ever greater than 1. -inline float VerticalExpansion(uint32_t width, uint32_t height) noexcept { - const float factor = AspectRatioFactor(width, height); +// Vert+ factor, clamped at 1 so a wider surface never shortens the frustum. +// Exactly one of the two expansions is ever greater than 1. +inline float VerticalExpansion(uint32_t width, uint32_t height, + float referenceAspect = kPresentationAspect169) noexcept { + const float factor = AspectRatioFactor(width, height, referenceAspect); if (!(factor > 0.0f)) { return 1.0f; } return std::clamp(1.0f / factor, 1.0f, kMaxVerticalExpansion); } -inline uint32_t EggRecordWidth(uint32_t baseWidth, uint32_t surfaceWidth, - uint32_t surfaceHeight) noexcept { - const float scaledWidth = - static_cast(baseWidth) * HorizontalExpansion(surfaceWidth, surfaceHeight); +inline uint32_t EggRecordWidth(uint32_t baseWidth, uint32_t surfaceWidth, uint32_t surfaceHeight, + float referenceAspect = kPresentationAspect169) noexcept { + const float scaledWidth = static_cast(baseWidth) * + HorizontalExpansion(surfaceWidth, surfaceHeight, referenceAspect); return std::clamp( static_cast(std::lround(scaledWidth / 4.0f)) * 4u, 4u, 0xFFFCu); } diff --git a/runtime/src/hle/sc.cpp b/runtime/src/hle/sc.cpp index 72314e5a6..22e45dce9 100644 --- a/runtime/src/hle/sc.cpp +++ b/runtime/src/hle/sc.cpp @@ -53,7 +53,7 @@ PPC_NATIVE_OVERRIDE(801B0220, SCCheckStatus_HLE, uint32_t, (), ()); // Returns: 0 = 4:3, 1 = 16:9 extern "C" uint32_t SCGetAspectRatio_HLE() { - return (RuntimeConfigFile::WidescreenEnabled(true) || MkwForceAspect169Requested()) ? 1u : 0u; + return RuntimeConfigFile::WidescreenEnabled(true) ? 1u : 0u; } PPC_NATIVE_OVERRIDE(801B1BE4, SCGetAspectRatio_HLE, uint32_t, (), ()); diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 6d0dd6bd2..a34c86cf4 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1391,9 +1391,10 @@ int RuntimeMain(int argc, char** argv) { auroraConfig.cachePath = auroraCachePath.c_str(); auroraConfig.logCallback = &RuntimeAuroraLogCallback; auroraConfig.logLevel = LOG_DEBUG; + RuntimeConfigFile::MigrateLegacyForceAspect(); const bool configWidescreen = RuntimeConfigFile::WidescreenEnabled(true); - const bool forceAspect169 = RuntimeConfigFile::ForceAspect169Enabled(); - auroraConfig.windowWidth = (configWidescreen || forceAspect169) ? 854 : 640; + const bool forceAspect = RuntimeConfigFile::ForceAspectEnabled(); + auroraConfig.windowWidth = configWidescreen ? 854 : 640; auroraConfig.windowHeight = 480; auroraConfig.windowWidth = RuntimeConfigFile::WindowWidth(auroraConfig.windowWidth); auroraConfig.windowHeight = RuntimeConfigFile::WindowHeight(auroraConfig.windowHeight); @@ -1412,7 +1413,7 @@ int RuntimeMain(int argc, char** argv) { auroraConfig.vsync = RuntimeConfigFile::Vsync(); auroraConfig.desiredBackend = BACKEND_AUTO; const float resolutionMultiplier = RuntimeConfigFile::ResolutionMultiplier(1.0f); - ConfigureMkwDynamicAspect(configWidescreen, forceAspect169, + ConfigureMkwDynamicAspect(configWidescreen, forceAspect, auroraConfig.windowWidth, auroraConfig.windowHeight); VISetFrameBufferScale(resolutionMultiplier); // One table for both directions. RuntimeConfigFile::IsSupportedGraphicsApi @@ -1483,15 +1484,15 @@ int RuntimeMain(int argc, char** argv) { aurora_set_skip_unready_pipelines(false); } RT_LOG(RT_TAG_CONFIG) << "video.widescreen=" << (configWidescreen ? "true" : "false") - << " force_16_9=" << (forceAspect169 ? "true" : "false") - << " SCGetAspectRatio=" << (configWidescreen || forceAspect169 ? 1 : 0) + << " force_aspect=" << (forceAspect ? "true" : "false") + << " SCGetAspectRatio=" << (configWidescreen ? 1 : 0) << " resolutionMultiplier=" << resolutionMultiplier << " window=" << auroraInfo.windowSize.width << "x" << auroraInfo.windowSize.height << " native=" << auroraInfo.windowSize.native_fb_width << "x" << auroraInfo.windowSize.native_fb_height - << " viewportPolicy=" << (forceAspect169 ? "16:9" : (configWidescreen ? "stretch" : "fit")) + << " viewportPolicy=" << (forceAspect ? (configWidescreen ? "16:9" : "fit") : "stretch") << " presentAspect=" - << (forceAspect169 ? "16:9" : (configWidescreen ? "surface (dynamic EGG canvas)" : "4:3")) + << (forceAspect ? (configWidescreen ? "16:9" : "4:3") : "surface (dynamic EGG canvas)") << std::endl; g_auroraInitialized.store(true, std::memory_order_release); GxWorker::SetWaitCallback(ServiceGuestTimingDuringAuroraFrameWait); diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index ed1a1b85b..e52926d21 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -113,7 +113,8 @@ bool g_skipUnreadyPipelines = RuntimeConfigFile::SkipUnreadyPipelines(true); bool g_vsync = RuntimeConfigFile::Vsync(); bool g_disableCopyFilter = RuntimeConfigFile::DisableCopyFilter(true); bool g_showFps = RuntimeConfigFile::ShowFps(true); -bool g_forceAspect169 = RuntimeConfigFile::ForceAspect169Enabled(); +bool g_forceAspect = RuntimeConfigFile::ForceAspectEnabled(); +bool g_widescreen = RuntimeConfigFile::WidescreenEnabled(true); #if defined(__APPLE__) bool g_metalFxSpatialUpscaling = RuntimeConfigFile::MetalFxSpatialUpscaling(false); #endif @@ -1080,11 +1081,22 @@ void DrawAudioSettings() { void DrawGraphicsSettings() { g_displayMode = static_cast(aurora_get_display_mode()); - if (ImGui::Checkbox("Force 16:9", &g_forceAspect169)) { - SetMkwForceAspect169(g_forceAspect169); - RuntimeConfigFile::SetForceAspect169(g_forceAspect169); + if (ImGui::BeginCombo("Aspect ratio (restart required)", g_widescreen ? "16:9" : "4:3")) { + if (ImGui::Selectable("16:9", g_widescreen)) { + g_widescreen = true; + RuntimeConfigFile::SetWidescreen(true); + } + if (ImGui::Selectable("4:3", !g_widescreen)) { + g_widescreen = false; + RuntimeConfigFile::SetWidescreen(false); + } + ImGui::EndCombo(); + } + if (ImGui::Checkbox("Force aspect ratio", &g_forceAspect)) { + SetMkwForceAspect(g_forceAspect); + RuntimeConfigFile::SetForceAspect(g_forceAspect); } - ImGui::TextDisabled("Keep a 16:9 image with black bars when the window has another shape."); + ImGui::TextDisabled("Keep the current aspect ratio with black bars when the window has another shape."); ImGui::Separator(); struct EffectFlag { const char* label; diff --git a/runtime/tests/runtime_config_tests.cpp b/runtime/tests/runtime_config_tests.cpp index 4883a1e1c..5af70a09c 100644 --- a/runtime/tests/runtime_config_tests.cpp +++ b/runtime/tests/runtime_config_tests.cpp @@ -51,6 +51,20 @@ int main() { const RuntimeUserConfig config = RuntimeConfigFile::ParseConfig(input, "invalid.toml"); if (!Require(!config.metalFxSpatialUpscaling, "invalid MetalFX setting was accepted")) return 1; } + { + std::istringstream input("[video]\nwidescreen = false\nforce_16_9 = true\n"); + const RuntimeUserConfig config = RuntimeConfigFile::ParseConfig(input, "legacy.toml"); + if (!Require(config.forceAspect == std::optional{true} && + config.widescreen == std::optional{true} && config.legacyForceAspect, + "legacy force_16_9 did not force a 16:9 game")) return 1; + } + { + std::istringstream input("[video]\nwidescreen = false\nforce_aspect = true\nforce_16_9 = true\n"); + const RuntimeUserConfig config = RuntimeConfigFile::ParseConfig(input, "migrated.toml"); + if (!Require(config.forceAspect == std::optional{true} && + config.widescreen == std::optional{false} && !config.legacyForceAspect, + "legacy force_16_9 overrode force_aspect")) return 1; + } if (!SetsFps("[video]\nshow_fps = false\n", "bare key was not replaced")) return 1; if (!SetsFps("[video]\n\"show_fps\" = false\n", "basic-quoted key was not replaced")) return 1; if (!SetsFps("[video]\n'show_fps'=false\n", "literal-quoted key was not replaced")) return 1;