diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 18fd1000..8d149288 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -336,6 +336,134 @@ 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 +#include "resource/IconsFontAwesome5.h" + +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) +{ + + // 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 + 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; + + 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; + + + 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.4 + if ((major <= 25 && minor < 3) || (major == 26 && (minor > 0 || patch >= 5)) || major > 26) + return; + + // 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 + { + 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"); + + // 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); + 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); + + LatteOverlay_pushNotification(std::string{(const char*)ICON_FA_EXCLAMATION_TRIANGLE} + "RADV_DEBUG=llvm set automatically to avoid crashing due to a driver bug. If possible update mesa to 26.0.5 or newer", 10'000); + +} + +#endif + VulkanRenderer::VulkanRenderer() { glslang::InitializeProcess(); @@ -395,6 +523,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) 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(); 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