Implement a mechanism to survive from GPU driver crashes on Windows.

This commit is contained in:
Skyth 2025-03-27 15:38:11 +03:00
parent 4b4c439709
commit 7726afeb29
3 changed files with 47 additions and 6 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>
@ -1651,7 +1652,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;
@ -1671,6 +1672,12 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver)
std::vector<RenderInterfaceFunction *> interfaceFunctions; std::vector<RenderInterfaceFunction *> interfaceFunctions;
#ifdef UNLEASHED_RECOMP_D3D12 #ifdef UNLEASHED_RECOMP_D3D12
if (graphicsApiRetry)
{
// If we are attempting to create again after a reboot due to a crash, swap the order.
g_vulkan = !g_vulkan;
}
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
@ -1679,9 +1686,17 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver)
for (RenderInterfaceFunction *interfaceFunction : interfaceFunctions) for (RenderInterfaceFunction *interfaceFunction : interfaceFunctions)
{ {
g_interface = interfaceFunction(); #ifdef UNLEASHED_RECOMP_D3D12
if (g_interface != nullptr) // 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)
{ {
@ -1718,6 +1733,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)
@ -1725,6 +1756,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();

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

@ -200,6 +200,7 @@ int main(int argc, char *argv[])
bool forceInstaller = false; bool forceInstaller = false;
bool forceDLCInstaller = false; bool forceDLCInstaller = false;
bool useDefaultWorkingDirectory = false; bool useDefaultWorkingDirectory = 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++)
@ -207,6 +208,7 @@ int main(int argc, char *argv[])
forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0); forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0);
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);
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)
{ {
@ -261,7 +263,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);
@ -281,7 +283,7 @@ int main(int argc, char *argv[])
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);