diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index a11a3b9084..57e0e70253 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -30,8 +30,6 @@ add_executable(yuzu applets/qt_web_browser_scripts.h bootmanager.cpp bootmanager.h - check_vulkan.cpp - check_vulkan.h compatdb.ui compatibility_list.cpp compatibility_list.h @@ -158,6 +156,8 @@ add_executable(yuzu main.cpp main.h main.ui + startup_checks.cpp + startup_checks.h uisettings.cpp uisettings.h util/controller_navigation.cpp diff --git a/src/yuzu/check_vulkan.cpp b/src/yuzu/check_vulkan.cpp deleted file mode 100644 index e6d66ab349..0000000000 --- a/src/yuzu/check_vulkan.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "video_core/vulkan_common/vulkan_wrapper.h" - -#include -#include -#include "common/fs/fs.h" -#include "common/fs/path_util.h" -#include "common/logging/log.h" -#include "video_core/vulkan_common/vulkan_instance.h" -#include "video_core/vulkan_common/vulkan_library.h" -#include "yuzu/check_vulkan.h" -#include "yuzu/uisettings.h" - -constexpr char TEMP_FILE_NAME[] = "vulkan_check"; - -bool CheckVulkan() { - if (UISettings::values.has_broken_vulkan) { - return true; - } - - LOG_DEBUG(Frontend, "Checking presence of Vulkan"); - - const auto fs_config_loc = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir); - const auto temp_file_loc = fs_config_loc / TEMP_FILE_NAME; - - if (std::filesystem::exists(temp_file_loc)) { - LOG_WARNING(Frontend, "Detected recovery from previous failed Vulkan initialization"); - - UISettings::values.has_broken_vulkan = true; - std::filesystem::remove(temp_file_loc); - return false; - } - - std::ofstream temp_file_handle(temp_file_loc); - temp_file_handle.close(); - - try { - Vulkan::vk::InstanceDispatch dld; - const Common::DynamicLibrary library = Vulkan::OpenLibrary(); - const Vulkan::vk::Instance instance = - Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0); - - } catch (const Vulkan::vk::Exception& exception) { - LOG_ERROR(Frontend, "Failed to initialize Vulkan: {}", exception.what()); - // Don't set has_broken_vulkan to true here: we care when loading Vulkan crashes the - // application, not when we can handle it. - } - - std::filesystem::remove(temp_file_loc); - return true; -} diff --git a/src/yuzu/check_vulkan.h b/src/yuzu/check_vulkan.h deleted file mode 100644 index e4ea935822..0000000000 --- a/src/yuzu/check_vulkan.h +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -bool CheckVulkan(); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1f76e86b9a..c841843f00 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -688,12 +688,6 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.bg_green); ReadGlobalSetting(Settings::values.bg_blue); - if (!global && UISettings::values.has_broken_vulkan && - Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan && - !Settings::values.renderer_backend.UsingGlobal()) { - Settings::values.renderer_backend.SetGlobal(true); - } - if (global) { ReadBasicSetting(Settings::values.renderer_debug); ReadBasicSetting(Settings::values.renderer_shader_feedback); @@ -813,7 +807,6 @@ void Config::ReadUIValues() { ReadBasicSetting(UISettings::values.pause_when_in_background); ReadBasicSetting(UISettings::values.mute_when_in_background); ReadBasicSetting(UISettings::values.hide_mouse); - ReadBasicSetting(UISettings::values.has_broken_vulkan); ReadBasicSetting(UISettings::values.disable_web_applet); qt_config->endGroup(); @@ -1367,7 +1360,6 @@ void Config::SaveUIValues() { WriteBasicSetting(UISettings::values.pause_when_in_background); WriteBasicSetting(UISettings::values.mute_when_in_background); WriteBasicSetting(UISettings::values.hide_mouse); - WriteBasicSetting(UISettings::values.has_broken_vulkan); WriteBasicSetting(UISettings::values.disable_web_applet); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 85f34dc35f..6b33c45351 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -58,24 +58,9 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren UpdateBackgroundColorButton(new_bg_color); }); - connect(ui->button_check_vulkan, &QAbstractButton::clicked, this, [this] { - UISettings::values.has_broken_vulkan = false; - - if (RetrieveVulkanDevices()) { - ui->api->setEnabled(true); - ui->button_check_vulkan->hide(); - - for (const auto& device : vulkan_devices) { - ui->device->addItem(device); - } - } else { - UISettings::values.has_broken_vulkan = true; - } - }); - - ui->api->setEnabled(!UISettings::values.has_broken_vulkan.GetValue()); - ui->button_check_vulkan->setVisible(UISettings::values.has_broken_vulkan.GetValue()); - + ui->api->setEnabled(!UISettings::values.has_broken_vulkan); + ui->api_widget->setEnabled(!UISettings::values.has_broken_vulkan || + Settings::IsConfiguringGlobal()); ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal()); } @@ -315,7 +300,7 @@ void ConfigureGraphics::UpdateAPILayout() { vulkan_device = Settings::values.vulkan_device.GetValue(true); shader_backend = Settings::values.shader_backend.GetValue(true); ui->device_widget->setEnabled(false); - ui->backend_widget->setEnabled(UISettings::values.has_broken_vulkan.GetValue()); + ui->backend_widget->setEnabled(false); } else { vulkan_device = Settings::values.vulkan_device.GetValue(); shader_backend = Settings::values.shader_backend.GetValue(); @@ -337,9 +322,9 @@ void ConfigureGraphics::UpdateAPILayout() { } } -bool ConfigureGraphics::RetrieveVulkanDevices() try { +void ConfigureGraphics::RetrieveVulkanDevices() try { if (UISettings::values.has_broken_vulkan) { - return false; + return; } using namespace Vulkan; @@ -355,11 +340,8 @@ bool ConfigureGraphics::RetrieveVulkanDevices() try { const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName; vulkan_devices.push_back(QString::fromStdString(name)); } - - return true; } catch (const Vulkan::vk::Exception& exception) { LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); - return false; } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { @@ -440,11 +422,4 @@ void ConfigureGraphics::SetupPerGameUI() { ui->api, static_cast(Settings::values.renderer_backend.GetValue(true))); ConfigurationShared::InsertGlobalItem( ui->nvdec_emulation, static_cast(Settings::values.nvdec_emulation.GetValue(true))); - - if (UISettings::values.has_broken_vulkan) { - ui->backend_widget->setEnabled(true); - ConfigurationShared::SetColoredComboBox( - ui->backend, ui->backend_widget, - static_cast(Settings::values.shader_backend.GetValue(true))); - } } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 8438f01876..1b101c9405 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -41,7 +41,7 @@ private: void UpdateDeviceSelection(int device); void UpdateShaderBackendSelection(int backend); - bool RetrieveVulkanDevices(); + void RetrieveVulkanDevices(); void SetupPerGameUI(); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 2f94c94bca..1e4f74704b 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -6,7 +6,7 @@ 0 0 - 471 + 541 759 @@ -574,13 +574,6 @@ - - - - Check for Working Vulkan - - - diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 08ccc1555e..2814548eb9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -115,7 +115,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "video_core/shader_notify.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" -#include "yuzu/check_vulkan.h" #include "yuzu/compatdb.h" #include "yuzu/compatibility_list.h" #include "yuzu/configuration/config.h" @@ -131,6 +130,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/install_dialog.h" #include "yuzu/loading_screen.h" #include "yuzu/main.h" +#include "yuzu/startup_checks.h" #include "yuzu/uisettings.h" using namespace Common::Literals; @@ -252,7 +252,7 @@ static QString PrettyProductName() { return QSysInfo::prettyProductName(); } -GMainWindow::GMainWindow() +GMainWindow::GMainWindow(bool has_broken_vulkan) : ui{std::make_unique()}, system{std::make_unique()}, input_subsystem{std::make_shared()}, config{std::make_unique(*system)}, @@ -352,17 +352,15 @@ GMainWindow::GMainWindow() MigrateConfigFiles(); - if (!CheckVulkan()) { - config->Save(); + if (has_broken_vulkan) { + UISettings::values.has_broken_vulkan = true; + + QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"), + tr("Vulkan initialization failed during boot.

Click " + "here for instructions to fix the issue.")); - QMessageBox::warning( - this, tr("Broken Vulkan Installation Detected"), - tr("Vulkan initialization failed on the previous boot.

Click here for " - "instructions to fix the issue.")); - } - if (UISettings::values.has_broken_vulkan) { Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; renderer_status_button->setDisabled(true); @@ -3879,6 +3877,11 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #endif int main(int argc, char* argv[]) { + bool has_broken_vulkan = false; + if (StartupChecks(argv[0], &has_broken_vulkan)) { + return 0; + } + Common::DetachedTasks detached_tasks; MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -3918,7 +3921,7 @@ int main(int argc, char* argv[]) { // generating shaders setlocale(LC_ALL, "C"); - GMainWindow main_window{}; + GMainWindow main_window{has_broken_vulkan}; // After settings have been loaded by GMainWindow, apply the filter main_window.show(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 09e37f1528..27204f5a2c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -118,7 +118,7 @@ class GMainWindow : public QMainWindow { public: void filterBarSetChecked(bool state); void UpdateUITheme(); - explicit GMainWindow(); + explicit GMainWindow(bool has_broken_vulkan); ~GMainWindow() override; bool DropAction(QDropEvent* event); diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp new file mode 100644 index 0000000000..8421280bf2 --- /dev/null +++ b/src/yuzu/startup_checks.cpp @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/vulkan_common/vulkan_wrapper.h" + +#ifdef _WIN32 +#include // for memset, strncpy +#include +#include +#elif defined(YUZU_UNIX) +#include +#include +#include +#endif + +#include +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_library.h" +#include "yuzu/startup_checks.h" + +void CheckVulkan() { + // Just start the Vulkan loader, this will crash if something is wrong + try { + Vulkan::vk::InstanceDispatch dld; + const Common::DynamicLibrary library = Vulkan::OpenLibrary(); + const Vulkan::vk::Instance instance = + Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0); + + } catch (const Vulkan::vk::Exception& exception) { + std::fprintf(stderr, "Failed to initialize Vulkan: %s\n", exception.what()); + } +} + +bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { +#ifdef _WIN32 + // Check environment variable to see if we are the child + char variable_contents[8]; + const DWORD startup_check_var = + GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8); + if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) { + CheckVulkan(); + return true; + } + + // Set the startup variable for child processes + const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON"); + if (!env_var_set) { + std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", + STARTUP_CHECK_ENV_VAR, GetLastError()); + return false; + } + + PROCESS_INFORMATION process_info; + std::memset(&process_info, '\0', sizeof(process_info)); + + if (!SpawnChild(arg0, &process_info)) { + return false; + } + + // Wait until the processs exits and get exit code from it + WaitForSingleObject(process_info.hProcess, INFINITE); + DWORD exit_code = STILL_ACTIVE; + const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); + if (err == 0) { + std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError()); + } + + // Vulkan is broken if the child crashed (return value is not zero) + *has_broken_vulkan = (exit_code != 0); + + if (CloseHandle(process_info.hProcess) == 0) { + std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + } + if (CloseHandle(process_info.hThread) == 0) { + std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + } + + if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) { + std::fprintf(stderr, "SetEnvironmentVariableA failed to clear %s with error %d\n", + STARTUP_CHECK_ENV_VAR, GetLastError()); + } + +#elif defined(YUZU_UNIX) + const pid_t pid = fork(); + if (pid == 0) { + CheckVulkan(); + return true; + } else if (pid == -1) { + const int err = errno; + std::fprintf(stderr, "fork failed with error %d\n", err); + return false; + } + + // Get exit code from child process + int status; + const int r_val = wait(&status); + if (r_val == -1) { + const int err = errno; + std::fprintf(stderr, "wait failed with error %d\n", err); + return false; + } + // Vulkan is broken if the child crashed (return value is not zero) + *has_broken_vulkan = (status != 0); +#endif + return false; +} + +#ifdef _WIN32 +bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { + STARTUPINFOA startup_info; + + std::memset(&startup_info, '\0', sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + + char p_name[255]; + std::strncpy(p_name, arg0, 255); + + const bool process_created = CreateProcessA(nullptr, // lpApplicationName + p_name, // lpCommandLine + nullptr, // lpProcessAttributes + nullptr, // lpThreadAttributes + false, // bInheritHandles + 0, // dwCreationFlags + nullptr, // lpEnvironment + nullptr, // lpCurrentDirectory + &startup_info, // lpStartupInfo + pi // lpProcessInformation + ); + if (!process_created) { + std::fprintf(stderr, "CreateProcessA failed with error %d\n", GetLastError()); + return false; + } + + return true; +} +#endif diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h new file mode 100644 index 0000000000..096dd54a89 --- /dev/null +++ b/src/yuzu/startup_checks.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef _WIN32 +#include +#endif + +constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; + +void CheckVulkan(); +bool StartupChecks(const char* arg0, bool* has_broken_vulkan); + +#ifdef _WIN32 +bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi); +#endif diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 044d88ca6a..2f6948243d 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -78,7 +78,7 @@ struct Values { Settings::Setting mute_when_in_background{false, "muteWhenInBackground"}; Settings::Setting hide_mouse{true, "hideInactiveMouse"}; // Set when Vulkan is known to crash the application - Settings::Setting has_broken_vulkan{false, "has_broken_vulkan"}; + bool has_broken_vulkan = false; Settings::Setting select_user_on_boot{false, "select_user_on_boot"};