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 <sdl_listener.h>
#include <xxHashMap.h>
#include <os/process.h>
#if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING)
#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::atomic<bool> 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<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++)
g_inputSlots[i].index = i;
@ -1672,17 +1679,39 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver)
std::vector<RenderInterfaceFunction *> 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<bool> g_executedCommandList;
void Video::Present()
{
g_readyForCommands = false;
RenderCommand cmd;
cmd.type = RenderCommandType::ExecutePendingStretchRectCommands;
g_renderQueue.enqueue(cmd);

View file

@ -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();

View file

@ -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." } },

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::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:" }
}

View file

@ -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);

View file

@ -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<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_MAJOR=1
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')
str++;
if (strncmp(str, "\u200B", 3) == 0)
{
str += 3;
}
lineStart = str;
continue;
}

View file

@ -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"]

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