From 75622d2da69a39dfd55b7e3997327d3d2a92dedc Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 5 Apr 2026 22:54:54 +0200 Subject: [PATCH 01/14] vulkan: Improve the Breath of the Wild RADV/LLVM workaround Use child process to detect the presence of the LLVM compiler and get MESA version only apply workaround on affected mesa versions --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 114 ++++++++++++++++++ src/gui/wxgui/MainWindow.cpp | 29 ----- 2 files changed, 114 insertions(+), 29 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 18fd1000..cae5410f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -336,10 +336,124 @@ void VulkanRenderer::GetDeviceFeatures() cemuLog_log(LogType::Force, fmt::format("VulkanLimits: UBAlignment {0} nonCoherentAtomSize {1}", prop2.properties.limits.minUniformBufferOffsetAlignment, prop2.properties.limits.nonCoherentAtomSize)); } +#if BOOST_OS_LINUX +#include + +void WorkaroundChildAbortHandler(int unused) +{ + _exit(2); +} + +void LinuxBreathOfTheWildWorkaround() +{ + + int subProcessPipes[2]{}; + pipe(subProcessPipes); + + int childID = fork(); + if (childID == 0) // inside this if statement runs in child + { + struct sigaction sa{.sa_handler = WorkaroundChildAbortHandler}; + sigaction(SIGABRT, &sa, nullptr); + + freopen("/dev/null", "w", stderr); + + setenv("RADV_DEBUG", "llvm", 1); + + VkInstanceCreateInfo instanceCreateInfo = {}; + instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + VkInstance instance = VK_NULL_HANDLE; + if (vkCreateInstance(&instanceCreateInfo, nullptr, &instance) != VK_SUCCESS) + _exit(1); + + InitializeInstanceVulkan(instance); + + uint32_t count = 0; + vkEnumeratePhysicalDevices(instance, &count, nullptr); + + std::vector physicalDevices{count}; + vkEnumeratePhysicalDevices(instance, &count, physicalDevices.data()); + + for (auto& i : physicalDevices) + { + VkPhysicalDeviceProperties prop{}; + vkGetPhysicalDeviceProperties(i, &prop); + if (prop.vendorID != 0x1002) + continue; + + std::string_view deviceName = prop.deviceName; + if (deviceName.find("RADV") != std::string_view::npos) + { + write(subProcessPipes[1], &prop.driverVersion, sizeof(uint32_t)); + _exit(0); + } + } + + // no appropriate device found to query version + _exit(1); + } + + int childStatus = 0; + waitpid(childID, &childStatus, 0); + + if (WEXITSTATUS(childStatus) == 2) + { + cemuLog_log(LogType::Force, "Breath of the Wild RADV workaround not applied because mesa was built without LLVM"); + } + + // only continue if the process exits with code zero, which means it determined the version and didn't crash. + if (WEXITSTATUS(childStatus) != 0) + return; + + uint32_t version = 0; + if (read(subProcessPipes[0], &version, sizeof(version)) == -1) + return; // if we fail to read bail. Should never happen if the subprocess exited with 0 + + int major = VK_API_VERSION_MAJOR(version); + int minor = VK_API_VERSION_MINOR(version); + int patch = VK_API_VERSION_PATCH(version); + + // If the driver is unaffected skip the workaround. + // affected drivers: + // 25.3.0 - 26.0.3 + if ((major <= 25 && minor < 3) || (major == 26 && (minor > 0 || patch >= 4)) || major > 26) + return; + + // if the variable is empty set it to llvm, otherwise check if it contains llvm/aco and if not append it + if (const char* value; (value = getenv("RADV_DEBUG")) != NULL && strlen(value) != 0) + { + std::string valueStr{value}; + // only append ,llvm when llvm or aco are not already passed as flags. + // "aco" is not a mesa flag (anymore, it used to be when llvm was default) + // but it will provide users with a way to override this workaround by setting RADV_DEBUG=aco + // should parse the flag list but there are currently no other flags containing llvm or aco as a substring + if (valueStr.find("llvm") == std::string::npos && valueStr.find("aco") == std::string::npos) + { + valueStr.append(",llvm"); + setenv("RADV_DEBUG", valueStr.c_str(), 1); + } + } + else + { + setenv("RADV_DEBUG", "llvm", 1); + } + +} +#endif + VulkanRenderer::VulkanRenderer() { glslang::InitializeProcess(); + // Workaround for BOTW + RADV. Runes like Magnesis and the camera cause GPU crashes. +#if BOOST_OS_LINUX + uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); + if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x00050000101c9300) + { + LinuxBreathOfTheWildWorkaround(); + } +#endif + cemuLog_log(LogType::Force, "------- Init Vulkan graphics backend -------"); const bool useValidationLayer = cemuLog_isLoggingEnabled(LogType::VulkanValidation); diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index 5de19f69..4ef4fe08 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -621,35 +621,6 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE cemuLog_log(LogType::Force, "GameMode has been started."); } } -#endif - - // Workaround for BOTW + Mesa. Runes like Magnesis and the camera cause GPU crashes. - // Using the LLVM shader compiler prevents crashes which points to an issue with the default ACO compiler. - // Either that or Cemu is violating a specification (GLSL?) causing the shaders to be broken after compilation. -#if BOOST_OS_LINUX - uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); - if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x00050000101c9300) - { - // if the variable is empty set it to llvm, otherwise check if it contains llvm/aco and if not append it - if (const char* value; (value = getenv("RADV_DEBUG")) != NULL && strlen(value) != 0) - { - std::string valueStr{value}; - // only append ,llvm when llvm or aco are not already passed as flags. - // "aco" is not a mesa flag (anymore, it used to be when llvm was default) - // but it will provide users with a way to override this workaround by setting RADV_DEBUG=aco - // should parse the flag list but there are currently no other flags containing llvm or aco as a substring - if (valueStr.find("llvm") == std::string::npos && valueStr.find("aco") == std::string::npos) - { - valueStr.append(",llvm"); - setenv("RADV_DEBUG", valueStr.c_str(), 1); - } - } - else - { - setenv("RADV_DEBUG", "llvm", 1); - } - } - #endif CreateCanvas(); From 0e42c3e48c3d1307c409968c63be582b050e3a39 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:00:11 +0200 Subject: [PATCH 02/14] move workaround after backend log print --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index cae5410f..a08bbe7e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -445,6 +445,8 @@ VulkanRenderer::VulkanRenderer() { glslang::InitializeProcess(); + cemuLog_log(LogType::Force, "------- Init Vulkan graphics backend -------"); + // Workaround for BOTW + RADV. Runes like Magnesis and the camera cause GPU crashes. #if BOOST_OS_LINUX uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); @@ -454,8 +456,6 @@ VulkanRenderer::VulkanRenderer() } #endif - cemuLog_log(LogType::Force, "------- Init Vulkan graphics backend -------"); - const bool useValidationLayer = cemuLog_isLoggingEnabled(LogType::VulkanValidation); if (useValidationLayer) cemuLog_log(LogType::Force, "Validation layer is enabled"); From 659cf4f9288da81f8be2749813e1b1446337aab8 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:08:26 +0200 Subject: [PATCH 03/14] correct projected fix version --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a08bbe7e..dc9c12a1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -416,7 +416,7 @@ void LinuxBreathOfTheWildWorkaround() // If the driver is unaffected skip the workaround. // affected drivers: // 25.3.0 - 26.0.3 - if ((major <= 25 && minor < 3) || (major == 26 && (minor > 0 || patch >= 4)) || major > 26) + if ((major <= 25 && minor < 3) || (major == 26 && (minor > 0 || patch >= 5)) || major > 26) return; // if the variable is empty set it to llvm, otherwise check if it contains llvm/aco and if not append it From fc7d10a4831fda46f69a3db227fe773608cd5ddc Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:09:52 +0200 Subject: [PATCH 04/14] correct projected fix version --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index dc9c12a1..4e8dd2fd 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -415,7 +415,7 @@ void LinuxBreathOfTheWildWorkaround() // If the driver is unaffected skip the workaround. // affected drivers: - // 25.3.0 - 26.0.3 + // 25.3.0 - 26.0.4 if ((major <= 25 && minor < 3) || (major == 26 && (minor > 0 || patch >= 5)) || major > 26) return; From 0cc843b7ea911cd16289487f62ee2655174cd4be Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:24:02 +0200 Subject: [PATCH 05/14] cleanup pipe handles after use --- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 4e8dd2fd..64a9b994 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -344,12 +344,8 @@ void WorkaroundChildAbortHandler(int unused) _exit(2); } -void LinuxBreathOfTheWildWorkaround() +void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) { - - int subProcessPipes[2]{}; - pipe(subProcessPipes); - int childID = fork(); if (childID == 0) // inside this if statement runs in child { @@ -439,6 +435,17 @@ void LinuxBreathOfTheWildWorkaround() } } + +void LinuxBreathOfTheWildWorkaround() +{ + int subProcessPipes[2]{}; + pipe(subProcessPipes); + + PerformBOTWLinuxWorkaround(subProcessPipes); + + close(subProcessPipes[0]); + close(subProcessPipes[1]); +} #endif VulkanRenderer::VulkanRenderer() From 62d46a62952a276da609b3093bd4c68d913f698f Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 6 Apr 2026 00:07:19 +0200 Subject: [PATCH 06/14] Destroy instance before child exit --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 64a9b994..4b8b8ac2 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -381,11 +381,13 @@ void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) if (deviceName.find("RADV") != std::string_view::npos) { write(subProcessPipes[1], &prop.driverVersion, sizeof(uint32_t)); + vkDestroyInstance(instance, nullptr); _exit(0); } } // no appropriate device found to query version + vkDestroyInstance(instance, nullptr); _exit(1); } From b403d50906948b165be075ce8e3e051165c957c2 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 6 Apr 2026 00:07:36 +0200 Subject: [PATCH 07/14] Add new log message and edit the other --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 4b8b8ac2..ffa7b62e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -396,7 +396,7 @@ void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) if (WEXITSTATUS(childStatus) == 2) { - cemuLog_log(LogType::Force, "Breath of the Wild RADV workaround not applied because mesa was built without LLVM"); + cemuLog_log(LogType::Force, "BOTW/RADV workaround not applied because mesa was built without LLVM"); } // only continue if the process exits with code zero, which means it determined the version and didn't crash. @@ -417,6 +417,7 @@ void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) if ((major <= 25 && minor < 3) || (major == 26 && (minor > 0 || patch >= 5)) || major > 26) return; + cemuLog_log(LogType::Force, "BOTW/RADV workaround active. Adding llvm to RADV_DEBUG environment variable"); // if the variable is empty set it to llvm, otherwise check if it contains llvm/aco and if not append it if (const char* value; (value = getenv("RADV_DEBUG")) != NULL && strlen(value) != 0) { From 21122d9907c14895e18dd80408f3985695ad2939 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 6 Apr 2026 00:10:44 +0200 Subject: [PATCH 08/14] these functions will not be called outside of this TU --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index ffa7b62e..20434e98 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -339,12 +339,12 @@ void VulkanRenderer::GetDeviceFeatures() #if BOOST_OS_LINUX #include -void WorkaroundChildAbortHandler(int unused) +static void WorkaroundChildAbortHandler(int unused) { _exit(2); } -void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) +static void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) { int childID = fork(); if (childID == 0) // inside this if statement runs in child @@ -439,7 +439,7 @@ void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) } -void LinuxBreathOfTheWildWorkaround() +static void LinuxBreathOfTheWildWorkaround() { int subProcessPipes[2]{}; pipe(subProcessPipes); From a52fe81add27a7eec27d108105417012d01e7595 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:02:25 +0200 Subject: [PATCH 09/14] only check LLVM crash on affected driver versions and eliminate IPC. --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 174 +++++++++--------- 1 file changed, 82 insertions(+), 92 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 20434e98..dda8c229 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -341,71 +341,44 @@ void VulkanRenderer::GetDeviceFeatures() static void WorkaroundChildAbortHandler(int unused) { - _exit(2); + _exit(1); } -static void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) +static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanceCreateInfo* create_info) { - int childID = fork(); - if (childID == 0) // inside this if statement runs in child - { - struct sigaction sa{.sa_handler = WorkaroundChildAbortHandler}; - sigaction(SIGABRT, &sa, nullptr); - freopen("/dev/null", "w", stderr); - - setenv("RADV_DEBUG", "llvm", 1); - - VkInstanceCreateInfo instanceCreateInfo = {}; - instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - VkInstance instance = VK_NULL_HANDLE; - if (vkCreateInstance(&instanceCreateInfo, nullptr, &instance) != VK_SUCCESS) - _exit(1); - - InitializeInstanceVulkan(instance); - - uint32_t count = 0; - vkEnumeratePhysicalDevices(instance, &count, nullptr); - - std::vector physicalDevices{count}; - vkEnumeratePhysicalDevices(instance, &count, physicalDevices.data()); - - for (auto& i : physicalDevices) - { - VkPhysicalDeviceProperties prop{}; - vkGetPhysicalDeviceProperties(i, &prop); - if (prop.vendorID != 0x1002) - continue; - - std::string_view deviceName = prop.deviceName; - if (deviceName.find("RADV") != std::string_view::npos) - { - write(subProcessPipes[1], &prop.driverVersion, sizeof(uint32_t)); - vkDestroyInstance(instance, nullptr); - _exit(0); - } - } - - // no appropriate device found to query version - vkDestroyInstance(instance, nullptr); - _exit(1); - } - - int childStatus = 0; - waitpid(childID, &childStatus, 0); - - if (WEXITSTATUS(childStatus) == 2) - { - cemuLog_log(LogType::Force, "BOTW/RADV workaround not applied because mesa was built without LLVM"); - } - - // only continue if the process exits with code zero, which means it determined the version and didn't crash. - if (WEXITSTATUS(childStatus) != 0) + // if the user specified either shader backend, do nothing. + // should parse the flag list but there are currently no other flags containing llvm or aco as a substring + std::string_view debugEnv = getenv("RADV_DEBUG"); + if (debugEnv.find("aco") != std::string_view::npos || debugEnv.find("llvm") != std::string_view::npos) + return; + + uint32_t count = 0; + vkEnumeratePhysicalDevices(instance, &count, nullptr); + + std::vector physicalDevices{count}; + vkEnumeratePhysicalDevices(instance, &count, physicalDevices.data()); + + // Find the first AMD device using a RADV driver and store its version + int version = 0; + for (auto& i : physicalDevices) + { + VkPhysicalDeviceDriverProperties driverProps{}; + driverProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; + VkPhysicalDeviceProperties2 prop{}; + prop.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + prop.pNext = &driverProps; + vkGetPhysicalDeviceProperties2(i, &prop); + if (prop.properties.vendorID != 0x1002 || driverProps.driverID != VK_DRIVER_ID_MESA_RADV) + continue; + + version = prop.properties.driverVersion; + break; + } + + if (version == 0) return; - uint32_t version = 0; - if (read(subProcessPipes[0], &version, sizeof(version)) == -1) - return; // if we fail to read bail. Should never happen if the subprocess exited with 0 int major = VK_API_VERSION_MAJOR(version); int minor = VK_API_VERSION_MINOR(version); @@ -417,38 +390,55 @@ static void PerformBOTWLinuxWorkaround(int subProcessPipes[2]) if ((major <= 25 && minor < 3) || (major == 26 && (minor > 0 || patch >= 5)) || major > 26) return; - cemuLog_log(LogType::Force, "BOTW/RADV workaround active. Adding llvm to RADV_DEBUG environment variable"); - // if the variable is empty set it to llvm, otherwise check if it contains llvm/aco and if not append it - if (const char* value; (value = getenv("RADV_DEBUG")) != NULL && strlen(value) != 0) + // check if running with LLVM would crash because mesa is LLVM-less. + int childID = fork(); + if (childID == 0) // inside this if statement runs in child { - std::string valueStr{value}; - // only append ,llvm when llvm or aco are not already passed as flags. - // "aco" is not a mesa flag (anymore, it used to be when llvm was default) - // but it will provide users with a way to override this workaround by setting RADV_DEBUG=aco - // should parse the flag list but there are currently no other flags containing llvm or aco as a substring - if (valueStr.find("llvm") == std::string::npos && valueStr.find("aco") == std::string::npos) - { - valueStr.append(",llvm"); - setenv("RADV_DEBUG", valueStr.c_str(), 1); - } + struct sigaction sa{.sa_handler = WorkaroundChildAbortHandler}; + sigaction(SIGABRT, &sa, nullptr); + + freopen("/dev/null", "w", stderr); + + setenv("RADV_DEBUG", "llvm", 1); + + VkInstance instance = VK_NULL_HANDLE; + vkCreateInstance(create_info, nullptr, &instance); + // this function will abort() when LLVM is absent + uint32_t count = 0; + vkEnumeratePhysicalDevices(instance, &count, nullptr); + vkDestroyInstance(instance, nullptr); + _exit(0); } - else + + int childStatus = 0; + waitpid(childID, &childStatus, 0); + + if (WEXITSTATUS(childStatus) == 1) + cemuLog_log(LogType::Force, "BOTW/RADV workaround not applied because mesa was built without LLVM"); + + // only continue if the process exits with code zero, which means it didn't crash + if (WEXITSTATUS(childStatus) != 0) + return; + + cemuLog_log(LogType::Force, "BOTW/RADV workaround active. Adding \"llvm\" to RADV_DEBUG environment variable"); + if (debugEnv.empty()) { setenv("RADV_DEBUG", "llvm", 1); } + else + { + std::string appendedDebugEnv{debugEnv}; + appendedDebugEnv.append(",llvm"); + setenv("RADV_DEBUG", appendedDebugEnv.c_str(), 1); + } + + // recreate the vulkan instance to update debug setting + vkDestroyInstance(instance, nullptr); + vkCreateInstance(create_info, nullptr, &instance); + InitializeInstanceVulkan(instance); } -static void LinuxBreathOfTheWildWorkaround() -{ - int subProcessPipes[2]{}; - pipe(subProcessPipes); - - PerformBOTWLinuxWorkaround(subProcessPipes); - - close(subProcessPipes[0]); - close(subProcessPipes[1]); -} #endif VulkanRenderer::VulkanRenderer() @@ -457,15 +447,6 @@ VulkanRenderer::VulkanRenderer() cemuLog_log(LogType::Force, "------- Init Vulkan graphics backend -------"); - // Workaround for BOTW + RADV. Runes like Magnesis and the camera cause GPU crashes. -#if BOOST_OS_LINUX - uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); - if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x00050000101c9300) - { - LinuxBreathOfTheWildWorkaround(); - } -#endif - const bool useValidationLayer = cemuLog_isLoggingEnabled(LogType::VulkanValidation); if (useValidationLayer) cemuLog_log(LogType::Force, "Validation layer is enabled"); @@ -519,6 +500,15 @@ VulkanRenderer::VulkanRenderer() if (!InitializeInstanceVulkan(m_instance)) throw std::runtime_error("Unable to load instanced Vulkan functions"); + // Workaround for BOTW + RADV. Runes like Magnesis and the camera cause GPU crashes. +#if BOOST_OS_LINUX + uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); + if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x00050000101c9300) + { + LinuxBreathOfTheWildWorkaround(m_instance, &create_info); + } +#endif + uint32_t device_count = 0; vkEnumeratePhysicalDevices(m_instance, &device_count, nullptr); if (device_count == 0) From e25c3bcee6be077248fe08675267eb0e1422e2ac Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:04:18 +0200 Subject: [PATCH 10/14] use lambda for signal handler --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index dda8c229..a5665579 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -339,11 +339,6 @@ void VulkanRenderer::GetDeviceFeatures() #if BOOST_OS_LINUX #include -static void WorkaroundChildAbortHandler(int unused) -{ - _exit(1); -} - static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanceCreateInfo* create_info) { @@ -394,7 +389,7 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc int childID = fork(); if (childID == 0) // inside this if statement runs in child { - struct sigaction sa{.sa_handler = WorkaroundChildAbortHandler}; + struct sigaction sa{.sa_handler = [](int unused){_exit(1);}}; sigaction(SIGABRT, &sa, nullptr); freopen("/dev/null", "w", stderr); From 42ef7657fbdb8e06fd5c1789f343ecfa655341d0 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:17:14 +0200 Subject: [PATCH 11/14] Check instance creation error just in case --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a5665579..65181506 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -429,7 +429,10 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc // recreate the vulkan instance to update debug setting vkDestroyInstance(instance, nullptr); - vkCreateInstance(create_info, nullptr, &instance); + VkResult err = vkCreateInstance(create_info, nullptr, &instance); + // re-check for errors just in case. + if (err != VK_SUCCESS) + throw std::runtime_error(fmt::format("Unable to re-create a Vulkan instance after RADV/LLVM workaround: {}", err)); InitializeInstanceVulkan(instance); } From 0ea89de990b9b944c2c06ee1bbb12028e8fa1911 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:44:10 +0200 Subject: [PATCH 12/14] Fix crash when RADV_DEBUG is not set --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 65181506..9af7c09d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -344,7 +344,8 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc // if the user specified either shader backend, do nothing. // should parse the flag list but there are currently no other flags containing llvm or aco as a substring - std::string_view debugEnv = getenv("RADV_DEBUG"); + const char* debugEnvC = getenv("RADV_DEBUG"); + std::string_view debugEnv = debugEnvC != nullptr ? debugEnvC : ""; if (debugEnv.find("aco") != std::string_view::npos || debugEnv.find("llvm") != std::string_view::npos) return; From 47948aa2d1c744cb2d761f17271660d69cbd66c1 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:55:22 +0200 Subject: [PATCH 13/14] Fix deadlocking issue --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 41 ++++++++++++------- src/main.cpp | 6 +++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 9af7c09d..a8a496c3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -339,6 +339,31 @@ void VulkanRenderer::GetDeviceFeatures() #if BOOST_OS_LINUX #include +int BreathOfTheWildChildProcessMain() +{ + InitializeGlobalVulkan(); + struct sigaction sa{.sa_handler = [](int unused){_exit(1);}}; + int ret = sigaction(SIGABRT, &sa, nullptr); + + freopen("/dev/null", "w", stderr); + + setenv("RADV_DEBUG", "llvm", 1); + + VkInstanceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + VkInstance instance = VK_NULL_HANDLE; + if (vkCreateInstance(&create_info, nullptr, &instance) != VK_SUCCESS) + return 1; + InitializeInstanceVulkan(instance); + + // this function will abort() when LLVM is absent + uint32_t count = 0; + vkEnumeratePhysicalDevices(instance, &count, nullptr); + + vkDestroyInstance(instance, nullptr); + return 0; +} + static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanceCreateInfo* create_info) { @@ -390,20 +415,8 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc int childID = fork(); if (childID == 0) // inside this if statement runs in child { - struct sigaction sa{.sa_handler = [](int unused){_exit(1);}}; - sigaction(SIGABRT, &sa, nullptr); - - freopen("/dev/null", "w", stderr); - - setenv("RADV_DEBUG", "llvm", 1); - - VkInstance instance = VK_NULL_HANDLE; - vkCreateInstance(create_info, nullptr, &instance); - // this function will abort() when LLVM is absent - uint32_t count = 0; - vkEnumeratePhysicalDevices(instance, &count, nullptr); - vkDestroyInstance(instance, nullptr); - _exit(0); + setenv("CEMU_DETECT_RADV","1", 1); + execl("/proc/self/exe", "/proc/self/exe", nullptr); } int childStatus = 0; diff --git a/src/main.cpp b/src/main.cpp index df2b57fb..adce2680 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -255,8 +255,14 @@ int main(int argc, char* argv[]) #else +int BreathOfTheWildChildProcessMain(); int main(int argc, char *argv[]) { +#if BOOST_OS_LINUX + if (getenv("CEMU_DETECT_RADV") != nullptr) + return BreathOfTheWildChildProcessMain(); +#endif + #if BOOST_OS_LINUX || BOOST_OS_BSD XInitThreads(); #endif From e5566d31fb97292c32349333406f3f24a43ed631 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:09:17 +0200 Subject: [PATCH 14/14] Detect exec failure and unexpected crashes in child process --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a8a496c3..dee9e704 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -417,11 +417,19 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc { setenv("CEMU_DETECT_RADV","1", 1); execl("/proc/self/exe", "/proc/self/exe", nullptr); + _exit(2); // exec failed so err on the safe side and signal failure } int childStatus = 0; waitpid(childID, &childStatus, 0); + // if the process didn't exit cleanly or failed to determine LLVM status + if (!WIFEXITED(childStatus) || WEXITSTATUS(childStatus) == 2) + { + cemuLog_log(LogType::Force, "BOTW/RADV workaround not applied because detecting LLVM presence failed unexpectedly"); + return; + } + if (WEXITSTATUS(childStatus) == 1) cemuLog_log(LogType::Force, "BOTW/RADV workaround not applied because mesa was built without LLVM");