diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 6028de9..2333dfb 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING) #include @@ -735,10 +736,13 @@ static void DestructTempResources() } static std::thread::id g_presentThreadId = std::this_thread::get_id(); +static std::atomic g_readyForCommands; PPC_FUNC_IMPL(__imp__sub_824ECA00); PPC_FUNC(sub_824ECA00) { + // Guard against thread ownership changes when between command lists. + g_readyForCommands.wait(false); g_presentThreadId = std::this_thread::get_id(); __imp__sub_824ECA00(ctx, base); } @@ -1623,6 +1627,9 @@ static void BeginCommandList() commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 1); commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 2); commandList->setGraphicsDescriptorSet(g_samplerDescriptorSet.get(), 3); + + g_readyForCommands = true; + g_readyForCommands.notify_one(); } template @@ -1652,7 +1659,7 @@ static void ApplyLowEndDefaults() } } -bool Video::CreateHostDevice(const char *sdlVideoDriver) +bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) { for (uint32_t i = 0; i < 16; i++) g_inputSlots[i].index = i; @@ -1672,17 +1679,39 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) std::vector interfaceFunctions; #ifdef UNLEASHED_RECOMP_D3D12 + bool allowVulkanRedirection = true; + + if (graphicsApiRetry) + { + // If we are attempting to create again after a reboot due to a crash, swap the order. + g_vulkan = !g_vulkan; + + // Don't allow redirection to Vulkan if we are retrying after a crash, + // so the user can at least boot the game with D3D12 if Vulkan fails to work. + allowVulkanRedirection = false; + } + interfaceFunctions.push_back(g_vulkan ? CreateVulkanInterfaceWrapper : CreateD3D12Interface); interfaceFunctions.push_back(g_vulkan ? CreateD3D12Interface : CreateVulkanInterfaceWrapper); #else interfaceFunctions.push_back(CreateVulkanInterfaceWrapper); #endif - for (RenderInterfaceFunction *interfaceFunction : interfaceFunctions) + for (size_t i = 0; i < interfaceFunctions.size(); i++) { - g_interface = interfaceFunction(); - if (g_interface != nullptr) + RenderInterfaceFunction* interfaceFunction = interfaceFunctions[i]; + +#ifdef UNLEASHED_RECOMP_D3D12 + // Wrap the device creation in __try/__except to survive from driver crashes. + __try +#endif { + g_interface = interfaceFunction(); + if (g_interface == nullptr) + { + continue; + } + g_device = g_interface->createDevice(Config::GraphicsDevice); if (g_device != nullptr) { @@ -1691,16 +1720,40 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) #ifdef UNLEASHED_RECOMP_D3D12 if (interfaceFunction == CreateD3D12Interface) { - if (deviceDescription.vendor == RenderDeviceVendor::AMD) + if (allowVulkanRedirection) { - // AMD Drivers before this version have a known issue where MSAA resolve targets will fail to work correctly. - // If no specific graphics API was selected, we silently destroy this one and move to the next option as it'll - // just work incorrectly otherwise and result in visual glitches and 3D rendering not working in general. - constexpr uint64_t MinimumAMDDriverVersion = 0x1F00005DC2005CULL; // 31.0.24002.92 - if ((Config::GraphicsAPI == EGraphicsAPI::Auto) && (deviceDescription.driverVersion < MinimumAMDDriverVersion)) + bool redirectToVulkan = false; + + if (deviceDescription.vendor == RenderDeviceVendor::AMD) + { + // AMD Drivers before this version have a known issue where MSAA resolve targets will fail to work correctly. + // If no specific graphics API was selected, we silently destroy this one and move to the next option as it'll + // just work incorrectly otherwise and result in visual glitches and 3D rendering not working in general. + constexpr uint64_t MinimumAMDDriverVersion = 0x1F00005DC2005CULL; // 31.0.24002.92 + if ((Config::GraphicsAPI == EGraphicsAPI::Auto) && (deviceDescription.driverVersion < MinimumAMDDriverVersion)) + redirectToVulkan = true; + } + else if (deviceDescription.vendor == RenderDeviceVendor::INTEL) + { + // Intel drivers on D3D12 are extremely buggy, introducing various graphical glitches. + // We will redirect users to Vulkan until a workaround can be found. + if (Config::GraphicsAPI == EGraphicsAPI::Auto) + redirectToVulkan = true; + } + + if (redirectToVulkan) { g_device.reset(); g_interface.reset(); + + // In case Vulkan fails to initialize, we will try D3D12 again afterwards, + // just to get the game to boot. This only really happens in very old Intel GPU drivers. + if (!g_vulkan) + { + interfaceFunctions.push_back(CreateD3D12Interface); + allowVulkanRedirection = false; + } + continue; } } @@ -1719,6 +1772,22 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) break; } } +#ifdef UNLEASHED_RECOMP_D3D12 + __except (EXCEPTION_EXECUTE_HANDLER) + { + if (graphicsApiRetry) + { + // If we were retrying, and this also failed, then we'll show the user neither of the graphics APIs succeeded. + return false; + } + else + { + // If this is the first crash we ran into, reboot and try the other graphics API. + os::process::StartProcess(os::process::GetExecutablePath(), { "--graphics-api-retry" }); + std::_Exit(0); + } + } +#endif } if (g_device == nullptr) @@ -1726,6 +1795,14 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) return false; } +#ifdef UNLEASHED_RECOMP_D3D12 + if (graphicsApiRetry) + { + // If we managed to create a device after retrying it in a reboot, remember the one we picked. + Config::GraphicsAPI = g_vulkan ? EGraphicsAPI::Vulkan : EGraphicsAPI::D3D12; + } +#endif + g_capabilities = g_device->getCapabilities(); LoadEmbeddedResources(); @@ -2360,18 +2437,22 @@ static void DrawProfiler() ImGui::NewLine(); - O1HeapDiagnostics diagnostics, physicalDiagnostics; + if (g_userHeap.heap != nullptr && g_userHeap.physicalHeap != nullptr) { - std::lock_guard lock(g_userHeap.mutex); - diagnostics = o1heapGetDiagnostics(g_userHeap.heap); + O1HeapDiagnostics diagnostics, physicalDiagnostics; + { + std::lock_guard lock(g_userHeap.mutex); + diagnostics = o1heapGetDiagnostics(g_userHeap.heap); + } + { + std::lock_guard lock(g_userHeap.physicalMutex); + physicalDiagnostics = o1heapGetDiagnostics(g_userHeap.physicalHeap); + } + + ImGui::Text("Heap Allocated: %d MB", int32_t(diagnostics.allocated / (1024 * 1024))); + ImGui::Text("Physical Heap Allocated: %d MB", int32_t(physicalDiagnostics.allocated / (1024 * 1024))); } - { - std::lock_guard lock(g_userHeap.physicalMutex); - physicalDiagnostics = o1heapGetDiagnostics(g_userHeap.physicalHeap); - } - - ImGui::Text("Heap Allocated: %d MB", int32_t(diagnostics.allocated / (1024 * 1024))); - ImGui::Text("Physical Heap Allocated: %d MB", int32_t(physicalDiagnostics.allocated / (1024 * 1024))); + ImGui::Text("GPU Waits: %d", int32_t(g_waitForGPUCount)); ImGui::Text("Buffer Uploads: %d", int32_t(g_bufferUploadCount)); ImGui::NewLine(); @@ -2710,6 +2791,8 @@ static std::atomic g_executedCommandList; void Video::Present() { + g_readyForCommands = false; + RenderCommand cmd; cmd.type = RenderCommandType::ExecutePendingStretchRectCommands; g_renderQueue.enqueue(cmd); diff --git a/UnleashedRecomp/gpu/video.h b/UnleashedRecomp/gpu/video.h index 3a394be..b07169b 100644 --- a/UnleashedRecomp/gpu/video.h +++ b/UnleashedRecomp/gpu/video.h @@ -18,7 +18,7 @@ struct Video static inline uint32_t s_viewportWidth; static inline uint32_t s_viewportHeight; - static bool CreateHostDevice(const char *sdlVideoDriver); + static bool CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry); static void WaitOnSwapChain(); static void Present(); static void StartPipelinePrecompilation(); diff --git a/UnleashedRecomp/locale/config_locale.cpp b/UnleashedRecomp/locale/config_locale.cpp index 7da6dc8..1242e4e 100644 --- a/UnleashedRecomp/locale/config_locale.cpp +++ b/UnleashedRecomp/locale/config_locale.cpp @@ -233,7 +233,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EControllerIcons) { ELanguage::English, { - { EControllerIcons::Auto, { "AUTO", "Auto : the game will determine which icons to use based on the current input device." } }, + { EControllerIcons::Auto, { "AUTO", "Auto: the game will determine which icons to use based on the current input device." } }, { EControllerIcons::Xbox, { "XBOX", "" } }, { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } } @@ -407,7 +407,7 @@ CONFIG_DEFINE_LOCALE(EffectsVolume) CONFIG_DEFINE_LOCALE(MusicAttenuation) { { ELanguage::English, { "Music Attenuation", "Fade out the game's music when external media is playing." } }, - { ELanguage::Japanese, { "BGM[減衰:げんすい]", "[外部:がいぶ]メディアを\u200B[再生:さいせい]すると\u200Bゲームの\u200B[音楽:おんがく]を\u200Bフェードアウトします" } }, + { ELanguage::Japanese, { "BGM[減衰:げんすい]", "[外部:がいぶ]メディアを\u200B[再生:さいせい]すると\u200Bゲームの\u200B[音楽:おんがく]を\u200Bフェードアウト\u200Bします" } }, { ELanguage::German, { "Musikdämpfung", "Stelle die Musik des Spiels stumm während externe Medien abgespielt werden." } }, { ELanguage::French, { "Atténuation audio", "Abaisse le volume des musiques du jeu lorsqu'un média externe est en cours de lecture." } }, { ELanguage::Spanish, { "Atenuación de música", "Atenúa la música del juego cuando un reproductor multimedia se encuentra activo." } }, @@ -508,7 +508,7 @@ CONFIG_DEFINE_LOCALE(BattleTheme) CONFIG_DEFINE_LOCALE(WindowSize) { { ELanguage::English, { "Window Size", "Adjust the size of the game window in windowed mode." } }, - { ELanguage::Japanese, { "ウィンドウサイズ", "ウィンドウモードでの\u200Bゲームの\u200Bウィンドウサイズを\u200B[調整:ちょうせい]できます" } }, + { ELanguage::Japanese, { "ウィンドウサイズ", "ウィンドウ\u200Bモードでの\u200Bゲームの\u200Bウィンドウサイズを\u200B[調整:ちょうせい]できます" } }, { ELanguage::German, { "Fenstergröße", "Ändere die Größe des Spielfensters im Fenstermodus." } }, { ELanguage::French, { "Taille de la fenêtre", "Modifie la taille de la fenêtre de jeu en mode fenêtré." } }, { ELanguage::Spanish, { "Tamaño de ventana", "Ajusta el tamaño de la ventana de juego." } }, diff --git a/UnleashedRecomp/locale/locale.cpp b/UnleashedRecomp/locale/locale.cpp index 27bf6f2..7544f31 100644 --- a/UnleashedRecomp/locale/locale.cpp +++ b/UnleashedRecomp/locale/locale.cpp @@ -366,7 +366,7 @@ std::unordered_map> { ELanguage::English, "Installation complete!\nThis project is brought to you by:" }, { ELanguage::Japanese, "インストール[完了:かんりょう]!\nプロジェクト[制作:せいさく]:" }, { ELanguage::German, "Installation abgeschlossen!\nDieses Projekt wird präsentiert von:" }, - { ELanguage::French, "Installation terminée !\nCe projet vous est présenté par :" }, + { ELanguage::French, "Installation terminée !\nCe projet vous est présenté\npar :" }, { ELanguage::Spanish, "¡Instalación completada!\nEste proyecto ha sido posible gracias a:" }, { ELanguage::Italian, "Installazione completata!\nQuesto progetto è stato creato da:" } } diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index 7cf0747..e8123b3 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -208,6 +208,7 @@ int main(int argc, char *argv[]) bool forceDLCInstaller = false; bool useDefaultWorkingDirectory = false; bool forceInstallationCheck = false; + bool graphicsApiRetry = false; const char *sdlVideoDriver = nullptr; for (uint32_t i = 1; i < argc; i++) @@ -216,6 +217,7 @@ int main(int argc, char *argv[]) forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0); useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], "--use-cwd") == 0); forceInstallationCheck = forceInstallationCheck || (strcmp(argv[i], "--install-check") == 0); + graphicsApiRetry = graphicsApiRetry || (strcmp(argv[i], "--graphics-api-retry") == 0); if (strcmp(argv[i], "--sdl-video-driver") == 0) { @@ -234,9 +236,6 @@ int main(int argc, char *argv[]) } Config::Load(); - - if (!PersistentStorageManager::LoadBinary()) - LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus); if (forceInstallationCheck) { @@ -326,7 +325,7 @@ int main(int argc, char *argv[]) bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled; if (runInstallerWizard) { - if (!Video::CreateHostDevice(sdlVideoDriver)) + if (!Video::CreateHostDevice(sdlVideoDriver, graphicsApiRetry)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow); std::_Exit(1); @@ -340,13 +339,16 @@ int main(int argc, char *argv[]) ModLoader::Init(); + if (!PersistentStorageManager::LoadBinary()) + LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus); + KiSystemStartup(); uint32_t entry = LdrLoadModule(modulePath); if (!runInstallerWizard) { - if (!Video::CreateHostDevice(sdlVideoDriver)) + if (!Video::CreateHostDevice(sdlVideoDriver, graphicsApiRetry)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow); std::_Exit(1); diff --git a/UnleashedRecomp/patches/aspect_ratio_patches.cpp b/UnleashedRecomp/patches/aspect_ratio_patches.cpp index d1780b1..02f49ee 100644 --- a/UnleashedRecomp/patches/aspect_ratio_patches.cpp +++ b/UnleashedRecomp/patches/aspect_ratio_patches.cpp @@ -1650,3 +1650,39 @@ PPC_FUNC(sub_82E54950) __imp__sub_82E54950(ctx, base); } } + +// Credits while Sonic is running are offseted by 133 pixels at 16:9. +// We can make this dynamic by remembering the anchoring and shifting accordingly. +void EndingTextAllocMidAsmHook(PPCRegister& r3) +{ + r3.u32 += sizeof(uint32_t); +} + +static constexpr uint32_t ENDING_TEXT_SIZE = 0x164; + +void EndingTextCtorRightMidAsmHook(PPCRegister& r3) +{ + *reinterpret_cast(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_RIGHT; +} + +void EndingTextCtorLeftMidAsmHook(PPCRegister& r3) +{ + *reinterpret_cast(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_LEFT; +} + +void EndingTextCtorCenterMidAsmHook(PPCRegister& r3) +{ + *reinterpret_cast(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_CENTER; +} + +void EndingTextPositionMidAsmHook(PPCRegister& r31, PPCRegister& f13) +{ + uint32_t align = *reinterpret_cast(g_memory.base + r31.u32 + ENDING_TEXT_SIZE); + + // Since widescreen is always forced, 133 offset will always be part of the position. + if (align == ALIGN_RIGHT) + f13.f64 += -133.0 * (1.0 - g_aspectRatioNarrowScale); + + else if (align == ALIGN_LEFT) + f13.f64 += 133.0 * (1.0 - g_aspectRatioNarrowScale); +} diff --git a/UnleashedRecomp/res/version.txt b/UnleashedRecomp/res/version.txt index a486ce0..d9130f4 100644 --- a/UnleashedRecomp/res/version.txt +++ b/UnleashedRecomp/res/version.txt @@ -1,4 +1,4 @@ VERSION_MILESTONE="" VERSION_MAJOR=1 VERSION_MINOR=0 -VERSION_REVISION=2 +VERSION_REVISION=3 diff --git a/UnleashedRecomp/ui/imgui_utils.cpp b/UnleashedRecomp/ui/imgui_utils.cpp index be085b4..31592bf 100644 --- a/UnleashedRecomp/ui/imgui_utils.cpp +++ b/UnleashedRecomp/ui/imgui_utils.cpp @@ -510,6 +510,11 @@ std::vector Split(const char* strStart, const ImFont* font, float f if (*str == '\n') str++; + if (strncmp(str, "\u200B", 3) == 0) + { + str += 3; + } + lineStart = str; continue; } diff --git a/UnleashedRecompLib/config/SWA.toml b/UnleashedRecompLib/config/SWA.toml index db5bf2d..eb871b8 100644 --- a/UnleashedRecompLib/config/SWA.toml +++ b/UnleashedRecompLib/config/SWA.toml @@ -1106,3 +1106,38 @@ name = "UseAlternateTitleMidAsmHook" address = 0x82580F44 jump_address_on_true = 0x82580F48 jump_address_on_false = 0x82580FA0 + +[[midasm_hook]] +name = "EndingTextAllocMidAsmHook" +address = 0x8257E284 +registers = ["r3"] + +[[midasm_hook]] +name = "EndingTextAllocMidAsmHook" +address = 0x8257E45C +registers = ["r3"] + +[[midasm_hook]] +name = "EndingTextAllocMidAsmHook" +address = 0x8257EDD8 +registers = ["r3"] + +[[midasm_hook]] +name = "EndingTextCtorRightMidAsmHook" +address = 0x8257E304 +registers = ["r3"] + +[[midasm_hook]] +name = "EndingTextCtorLeftMidAsmHook" +address = 0x8257E4DC +registers = ["r3"] + +[[midasm_hook]] +name = "EndingTextCtorCenterMidAsmHook" +address = 0x8257EE40 +registers = ["r3"] + +[[midasm_hook]] +name = "EndingTextPositionMidAsmHook" +address = 0x82580168 +registers = ["r31", "f13"] diff --git a/tools/XenosRecomp b/tools/XenosRecomp index 4897cf7..56738e5 160000 --- a/tools/XenosRecomp +++ b/tools/XenosRecomp @@ -1 +1 @@ -Subproject commit 4897cf7ef2070120310c28a1a672b427d745dad8 +Subproject commit 56738e5893ed7c4dc108996590475c52726623e3