Merge branch 'hedge-dev:main' into ControllerHotplugImprovements

This commit is contained in:
Al. Lopez 2025-04-06 16:10:02 -04:00 committed by GitHub
commit 6e7a2f6a13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 193 additions and 32 deletions

View file

@ -31,6 +31,7 @@
#include <user/config.h> #include <user/config.h>
#include <sdl_listener.h> #include <sdl_listener.h>
#include <xxHashMap.h> #include <xxHashMap.h>
#include <os/process.h>
#if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING) #if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING)
#include <magic_enum/magic_enum.hpp> #include <magic_enum/magic_enum.hpp>
@ -735,10 +736,13 @@ static void DestructTempResources()
} }
static std::thread::id g_presentThreadId = std::this_thread::get_id(); static std::thread::id g_presentThreadId = std::this_thread::get_id();
static std::atomic<bool> g_readyForCommands;
PPC_FUNC_IMPL(__imp__sub_824ECA00); PPC_FUNC_IMPL(__imp__sub_824ECA00);
PPC_FUNC(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(); g_presentThreadId = std::this_thread::get_id();
__imp__sub_824ECA00(ctx, base); __imp__sub_824ECA00(ctx, base);
} }
@ -1623,6 +1627,9 @@ static void BeginCommandList()
commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 1); commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 1);
commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 2); commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 2);
commandList->setGraphicsDescriptorSet(g_samplerDescriptorSet.get(), 3); commandList->setGraphicsDescriptorSet(g_samplerDescriptorSet.get(), 3);
g_readyForCommands = true;
g_readyForCommands.notify_one();
} }
template<typename T> template<typename T>
@ -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++) for (uint32_t i = 0; i < 16; i++)
g_inputSlots[i].index = i; g_inputSlots[i].index = i;
@ -1672,17 +1679,39 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver)
std::vector<RenderInterfaceFunction *> interfaceFunctions; std::vector<RenderInterfaceFunction *> interfaceFunctions;
#ifdef UNLEASHED_RECOMP_D3D12 #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 ? CreateVulkanInterfaceWrapper : CreateD3D12Interface);
interfaceFunctions.push_back(g_vulkan ? CreateD3D12Interface : CreateVulkanInterfaceWrapper); interfaceFunctions.push_back(g_vulkan ? CreateD3D12Interface : CreateVulkanInterfaceWrapper);
#else #else
interfaceFunctions.push_back(CreateVulkanInterfaceWrapper); interfaceFunctions.push_back(CreateVulkanInterfaceWrapper);
#endif #endif
for (RenderInterfaceFunction *interfaceFunction : interfaceFunctions) for (size_t i = 0; i < interfaceFunctions.size(); i++)
{ {
g_interface = interfaceFunction(); RenderInterfaceFunction* interfaceFunction = interfaceFunctions[i];
if (g_interface != nullptr)
#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); g_device = g_interface->createDevice(Config::GraphicsDevice);
if (g_device != nullptr) if (g_device != nullptr)
{ {
@ -1691,16 +1720,40 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver)
#ifdef UNLEASHED_RECOMP_D3D12 #ifdef UNLEASHED_RECOMP_D3D12
if (interfaceFunction == CreateD3D12Interface) 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. bool redirectToVulkan = false;
// 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. if (deviceDescription.vendor == RenderDeviceVendor::AMD)
constexpr uint64_t MinimumAMDDriverVersion = 0x1F00005DC2005CULL; // 31.0.24002.92 {
if ((Config::GraphicsAPI == EGraphicsAPI::Auto) && (deviceDescription.driverVersion < MinimumAMDDriverVersion)) // 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_device.reset();
g_interface.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; continue;
} }
} }
@ -1719,6 +1772,22 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver)
break; 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) if (g_device == nullptr)
@ -1726,6 +1795,14 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver)
return false; 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(); g_capabilities = g_device->getCapabilities();
LoadEmbeddedResources(); LoadEmbeddedResources();
@ -2360,18 +2437,22 @@ static void DrawProfiler()
ImGui::NewLine(); ImGui::NewLine();
O1HeapDiagnostics diagnostics, physicalDiagnostics; if (g_userHeap.heap != nullptr && g_userHeap.physicalHeap != nullptr)
{ {
std::lock_guard lock(g_userHeap.mutex); O1HeapDiagnostics diagnostics, physicalDiagnostics;
diagnostics = o1heapGetDiagnostics(g_userHeap.heap); {
} std::lock_guard lock(g_userHeap.mutex);
{ diagnostics = o1heapGetDiagnostics(g_userHeap.heap);
std::lock_guard lock(g_userHeap.physicalMutex); }
physicalDiagnostics = o1heapGetDiagnostics(g_userHeap.physicalHeap); {
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("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("GPU Waits: %d", int32_t(g_waitForGPUCount));
ImGui::Text("Buffer Uploads: %d", int32_t(g_bufferUploadCount)); ImGui::Text("Buffer Uploads: %d", int32_t(g_bufferUploadCount));
ImGui::NewLine(); ImGui::NewLine();
@ -2710,6 +2791,8 @@ static std::atomic<bool> g_executedCommandList;
void Video::Present() void Video::Present()
{ {
g_readyForCommands = false;
RenderCommand cmd; RenderCommand cmd;
cmd.type = RenderCommandType::ExecutePendingStretchRectCommands; cmd.type = RenderCommandType::ExecutePendingStretchRectCommands;
g_renderQueue.enqueue(cmd); g_renderQueue.enqueue(cmd);

View file

@ -18,7 +18,7 @@ struct Video
static inline uint32_t s_viewportWidth; static inline uint32_t s_viewportWidth;
static inline uint32_t s_viewportHeight; 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 WaitOnSwapChain();
static void Present(); static void Present();
static void StartPipelinePrecompilation(); static void StartPipelinePrecompilation();

View file

@ -233,7 +233,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EControllerIcons)
{ {
ELanguage::English, 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::Xbox, { "XBOX", "" } },
{ EControllerIcons::PlayStation, { "PLAYSTATION", "" } } { EControllerIcons::PlayStation, { "PLAYSTATION", "" } }
} }
@ -407,7 +407,7 @@ CONFIG_DEFINE_LOCALE(EffectsVolume)
CONFIG_DEFINE_LOCALE(MusicAttenuation) CONFIG_DEFINE_LOCALE(MusicAttenuation)
{ {
{ ELanguage::English, { "Music Attenuation", "Fade out the game's music when external media is playing." } }, { 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::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::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." } }, { 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) CONFIG_DEFINE_LOCALE(WindowSize)
{ {
{ ELanguage::English, { "Window Size", "Adjust the size of the game window in windowed mode." } }, { 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::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::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." } }, { ELanguage::Spanish, { "Tamaño de ventana", "Ajusta el tamaño de la ventana de juego." } },

View file

@ -366,7 +366,7 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
{ ELanguage::English, "Installation complete!\nThis project is brought to you by:" }, { ELanguage::English, "Installation complete!\nThis project is brought to you by:" },
{ ELanguage::Japanese, "インストール[完了:かんりょう]\nプロジェクト[制作:せいさく]" }, { ELanguage::Japanese, "インストール[完了:かんりょう]\nプロジェクト[制作:せいさく]" },
{ ELanguage::German, "Installation abgeschlossen!\nDieses Projekt wird präsentiert von:" }, { 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::Spanish, "¡Instalación completada!\nEste proyecto ha sido posible gracias a:" },
{ ELanguage::Italian, "Installazione completata!\nQuesto progetto è stato creato da:" } { ELanguage::Italian, "Installazione completata!\nQuesto progetto è stato creato da:" }
} }

View file

@ -208,6 +208,7 @@ int main(int argc, char *argv[])
bool forceDLCInstaller = false; bool forceDLCInstaller = false;
bool useDefaultWorkingDirectory = false; bool useDefaultWorkingDirectory = false;
bool forceInstallationCheck = false; bool forceInstallationCheck = false;
bool graphicsApiRetry = false;
const char *sdlVideoDriver = nullptr; const char *sdlVideoDriver = nullptr;
for (uint32_t i = 1; i < argc; i++) 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); forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0);
useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], "--use-cwd") == 0); useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], "--use-cwd") == 0);
forceInstallationCheck = forceInstallationCheck || (strcmp(argv[i], "--install-check") == 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) if (strcmp(argv[i], "--sdl-video-driver") == 0)
{ {
@ -235,9 +237,6 @@ int main(int argc, char *argv[])
Config::Load(); Config::Load();
if (!PersistentStorageManager::LoadBinary())
LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus);
if (forceInstallationCheck) if (forceInstallationCheck)
{ {
// Create the console to show progress to the user, otherwise it will seem as if the game didn't boot at all. // Create the console to show progress to the user, otherwise it will seem as if the game didn't boot at all.
@ -326,7 +325,7 @@ int main(int argc, char *argv[])
bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled; bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled;
if (runInstallerWizard) 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); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow);
std::_Exit(1); std::_Exit(1);
@ -340,13 +339,16 @@ int main(int argc, char *argv[])
ModLoader::Init(); ModLoader::Init();
if (!PersistentStorageManager::LoadBinary())
LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus);
KiSystemStartup(); KiSystemStartup();
uint32_t entry = LdrLoadModule(modulePath); uint32_t entry = LdrLoadModule(modulePath);
if (!runInstallerWizard) 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); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow);
std::_Exit(1); std::_Exit(1);

View file

@ -1650,3 +1650,39 @@ PPC_FUNC(sub_82E54950)
__imp__sub_82E54950(ctx, base); __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<uint32_t*>(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_RIGHT;
}
void EndingTextCtorLeftMidAsmHook(PPCRegister& r3)
{
*reinterpret_cast<uint32_t*>(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_LEFT;
}
void EndingTextCtorCenterMidAsmHook(PPCRegister& r3)
{
*reinterpret_cast<uint32_t*>(g_memory.base + r3.u32 + ENDING_TEXT_SIZE) = ALIGN_CENTER;
}
void EndingTextPositionMidAsmHook(PPCRegister& r31, PPCRegister& f13)
{
uint32_t align = *reinterpret_cast<uint32_t*>(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);
}

View file

@ -1,4 +1,4 @@
VERSION_MILESTONE="" VERSION_MILESTONE=""
VERSION_MAJOR=1 VERSION_MAJOR=1
VERSION_MINOR=0 VERSION_MINOR=0
VERSION_REVISION=2 VERSION_REVISION=3

View file

@ -510,6 +510,11 @@ std::vector<std::string> Split(const char* strStart, const ImFont* font, float f
if (*str == '\n') if (*str == '\n')
str++; str++;
if (strncmp(str, "\u200B", 3) == 0)
{
str += 3;
}
lineStart = str; lineStart = str;
continue; continue;
} }

View file

@ -1106,3 +1106,38 @@ name = "UseAlternateTitleMidAsmHook"
address = 0x82580F44 address = 0x82580F44
jump_address_on_true = 0x82580F48 jump_address_on_true = 0x82580F48
jump_address_on_false = 0x82580FA0 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"]

@ -1 +1 @@
Subproject commit 4897cf7ef2070120310c28a1a672b427d745dad8 Subproject commit 56738e5893ed7c4dc108996590475c52726623e3