diff --git a/runtime/include/aurora_events.h b/runtime/include/aurora_events.h index 211ebf603..62a20f167 100644 --- a/runtime/include/aurora_events.h +++ b/runtime/include/aurora_events.h @@ -18,9 +18,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 11962a49c..bb76fad86 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 advertisedAspectRatio; + std::optional forceAspect; std::optional windowPosX; std::optional windowPosY; std::optional windowWidth; @@ -271,7 +272,8 @@ 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" + "advertised_aspect_ratio = \"16:9\"\n" + "force_aspect = false\n" "resolution_multiplier = 1.0\n" "frame_interpolation_fps = 0\n" "display_mode = \"windowed\"\n" @@ -403,7 +405,8 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { } config.widescreen = FindConfigValue(document, "video", "widescreen"); - config.forceAspect169 = FindConfigValue(document, "video", "force_16_9"); + config.advertisedAspectRatio = FindConfigValue(document, "video", "advertised_aspect_ratio"); + config.forceAspect = FindConfigValue(document, "video", "force_aspect"); 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 +763,23 @@ 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 AdvertisedAspectRatio169(bool fallback = false) { + if (Get().advertisedAspectRatio) { + return *Get().advertisedAspectRatio != "4:3"; + } + return WidescreenEnabled(fallback); +} + +inline bool SetAdvertisedAspectRatio169(bool value) { + return WriteSetting("video", "advertised_aspect_ratio", value ? "\"16:9\"" : "\"4:3\""); +} + +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) { + return WriteSetting("video", "force_aspect", value ? "true" : "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..2ba5b1f6e 100644 --- a/runtime/src/dynamic_aspect.cpp +++ b/runtime/src/dynamic_aspect.cpp @@ -2,7 +2,6 @@ #include "memory.h" #include -#include #include "aurora_events.h" #include "hle/gx/gx_dynamic_aspect.h" @@ -14,10 +13,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_forceAspect = false; +bool g_referenceWidescreen = true; +float g_referenceAspect = MkwDynamicAspect::kPresentationAspect169; bool g_policyDirty = false; uint32_t g_lastEggWidth43 = 0; uint32_t g_lastEggWidth169 = 0; @@ -131,18 +129,14 @@ void WriteEggScreenRecord(uint32_t recordAddr, uint32_t width) { void ApplyEggScreenRecords(uint32_t surfaceWidth, uint32_t surfaceHeight) { using namespace MkwDynamicAspect; const uint32_t width43 = - g_widescreenConfigured ? EggRecordWidth(kEggRecordWidth43, surfaceWidth, surfaceHeight) - : kEggRecordWidth43; + EggRecordWidth(kEggRecordWidth43, surfaceWidth, surfaceHeight, g_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, g_referenceAspect)); AssertOffscreenScreenBypass(); if (width43 == g_lastEggWidth43 && width169 == g_lastEggWidth169) { return; @@ -172,58 +166,45 @@ 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; - g_policyDirty = true; - } if (surfaceWidth == 0 || surfaceHeight == 0) { return; } - if (!g_widescreenConfigured) { - if (g_policyDirty) { + if (g_policyDirty) { + if (g_forceAspect && !g_referenceWidescreen) { 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); + if (!g_forceAspect) { + AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH); + ApplyEggScreenRecords(surfaceWidth, surfaceHeight); + return; + } + ApplyEggScreenRecords(g_referenceWidescreen ? 16u : 4u, + g_referenceWidescreen ? 9u : 3u); } -bool MkwForceAspect169Requested() { - return g_requestedForceAspect169.load(std::memory_order_acquire); +void SetMkwForceAspect(bool enabled) { + g_forceAspect = enabled; + g_policyDirty = true; + uint32_t surfaceWidth = 0; + uint32_t surfaceHeight = 0; + AuroraGetSurfaceSize(&surfaceWidth, &surfaceHeight); + UpdateMkwDynamicAspectSurface(surfaceWidth, surfaceHeight); } -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_forceAspect = forceAspect; + g_referenceWidescreen = widescreen; + g_referenceAspect = widescreen ? MkwDynamicAspect::kPresentationAspect169 : 4.0f / 3.0f; + 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); + ApplyEggScreenRecords(forceAspect ? (widescreen ? 16u : 4u) : surfaceWidth, + forceAspect ? (widescreen ? 9u : 3u) : surfaceHeight); } diff --git a/runtime/src/hle/gx/gx_dynamic_aspect.h b/runtime/src/hle/gx/gx_dynamic_aspect.h index 7d64eea44..ef64cbeee 100644 --- a/runtime/src/hle/gx/gx_dynamic_aspect.h +++ b/runtime/src/hle/gx/gx_dynamic_aspect.h @@ -37,6 +37,11 @@ inline float AspectRatioFactor(uint32_t width, uint32_t height) noexcept { return SurfaceAspect(width, height) / kPresentationAspect169; } +inline float AspectRatioFactor(uint32_t width, uint32_t height, + float referenceAspect) 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 { @@ -53,6 +58,15 @@ inline float VerticalExpansion(uint32_t width, uint32_t height) noexcept { return std::clamp(1.0f / factor, 1.0f, kMaxVerticalExpansion); } +inline float VerticalExpansion(uint32_t width, uint32_t height, + float referenceAspect) 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 = @@ -61,6 +75,17 @@ inline uint32_t EggRecordWidth(uint32_t baseWidth, uint32_t surfaceWidth, static_cast(std::lround(scaledWidth / 4.0f)) * 4u, 4u, 0xFFFCu); } +inline uint32_t EggRecordWidth(uint32_t baseWidth, uint32_t surfaceWidth, + uint32_t surfaceHeight, + float referenceAspect) noexcept { + const float scaledWidth = static_cast(baseWidth) * + std::max(1.0f, AspectRatioFactor(surfaceWidth, + surfaceHeight, + referenceAspect)); + return std::clamp( + static_cast(std::lround(scaledWidth / 4.0f)) * 4u, 4u, 0xFFFCu); +} + inline float EggHorizontalScale(uint32_t recordWidth) noexcept { return kEggFramebufferWidth / static_cast(std::max(recordWidth, 1u)); } diff --git a/runtime/src/hle/sc.cpp b/runtime/src/hle/sc.cpp index 72314e5a6..df9cb481f 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::AdvertisedAspectRatio169(true) ? 1u : 0u; } PPC_NATIVE_OVERRIDE(801B1BE4, SCGetAspectRatio_HLE, uint32_t, (), ()); diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index ae85098fd..8815c0d6e 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1374,8 +1374,9 @@ int RuntimeMain(int argc, char** argv) { auroraConfig.logCallback = &RuntimeAuroraLogCallback; auroraConfig.logLevel = LOG_DEBUG; const bool configWidescreen = RuntimeConfigFile::WidescreenEnabled(true); - const bool forceAspect169 = RuntimeConfigFile::ForceAspect169Enabled(); - auroraConfig.windowWidth = (configWidescreen || forceAspect169) ? 854 : 640; + const bool advertisedAspect169 = RuntimeConfigFile::AdvertisedAspectRatio169(configWidescreen); + const bool forceAspect = RuntimeConfigFile::ForceAspectEnabled(); + auroraConfig.windowWidth = (advertisedAspect169 || forceAspect) ? 854 : 640; auroraConfig.windowHeight = 480; auroraConfig.windowWidth = RuntimeConfigFile::WindowWidth(auroraConfig.windowWidth); auroraConfig.windowHeight = RuntimeConfigFile::WindowHeight(auroraConfig.windowHeight); @@ -1393,7 +1394,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(advertisedAspect169, forceAspect, auroraConfig.windowWidth, auroraConfig.windowHeight); VISetFrameBufferScale(resolutionMultiplier); // One table for both directions. RuntimeConfigFile::IsSupportedGraphicsApi @@ -1460,16 +1461,18 @@ int RuntimeMain(int argc, char** argv) { UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width, auroraInfo.windowSize.native_fb_height); settings_overlay::InitializeRuntimeSettings(); - RT_LOG(RT_TAG_CONFIG) << "video.widescreen=" << (configWidescreen ? "true" : "false") - << " force_16_9=" << (forceAspect169 ? "true" : "false") - << " SCGetAspectRatio=" << (configWidescreen || forceAspect169 ? 1 : 0) + RT_LOG(RT_TAG_CONFIG) << "video.advertised_aspect_ratio=" + << (advertisedAspect169 ? "16:9" : "4:3") + << " force_aspect=" << (forceAspect ? "true" : "false") + << " SCGetAspectRatio=" << (advertisedAspect169 ? 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 ? "fixed" : (advertisedAspect169 ? "stretch" : "fit")) << " presentAspect=" - << (forceAspect169 ? "16:9" : (configWidescreen ? "surface (dynamic EGG canvas)" : "4:3")) + << (forceAspect ? (advertisedAspect169 ? "16:9" : "4:3") + : (advertisedAspect169 ? "surface (dynamic EGG canvas)" : "4:3")) << 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 4873c4c0c..0ddae55ea 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -112,7 +112,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_advertisedAspect169 = RuntimeConfigFile::AdvertisedAspectRatio169(true); #if defined(__APPLE__) bool g_metalFxSpatialUpscaling = RuntimeConfigFile::MetalFxSpatialUpscaling(false); #endif @@ -1069,11 +1070,23 @@ 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); + const char* advertisedAspectLabel = g_advertisedAspect169 ? "16:9" : "4:3"; + if (ImGui::BeginCombo("Advertised aspect ratio (restart required)", advertisedAspectLabel)) { + if (ImGui::Selectable("16:9", g_advertisedAspect169)) { + g_advertisedAspect169 = true; + RuntimeConfigFile::SetAdvertisedAspectRatio169(true); + } + if (ImGui::Selectable("4:3", !g_advertisedAspect169)) { + g_advertisedAspect169 = false; + RuntimeConfigFile::SetAdvertisedAspectRatio169(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;