summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/bounded_threadsafe_queue.h180
-rw-r--r--src/common/settings.h2
-rw-r--r--src/video_core/gpu_thread.cpp3
-rw-r--r--src/video_core/gpu_thread.h6
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp4
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/check_vulkan.cpp53
-rw-r--r--src/yuzu/check_vulkan.h6
-rw-r--r--src/yuzu/configuration/config.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp36
-rw-r--r--src/yuzu/configuration/configure_graphics.h2
-rw-r--r--src/yuzu/configuration/configure_graphics.ui91
-rw-r--r--src/yuzu/game_list.cpp14
-rw-r--r--src/yuzu/game_list.h3
-rw-r--r--src/yuzu/main.cpp42
-rw-r--r--src/yuzu/uisettings.h2
-rw-r--r--src/yuzu_cmd/default_ini.h2
17 files changed, 400 insertions, 56 deletions
diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h
new file mode 100644
index 000000000..e83064c7f
--- /dev/null
+++ b/src/common/bounded_threadsafe_queue.h
@@ -0,0 +1,180 @@
1// SPDX-FileCopyrightText: Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
2// SPDX-License-Identifier: MIT
3#pragma once
4#ifdef _MSC_VER
5#pragma warning(push)
6#pragma warning(disable : 4324)
7#endif
8
9#include <atomic>
10#include <bit>
11#include <condition_variable>
12#include <memory>
13#include <mutex>
14#include <new>
15#include <stdexcept>
16#include <stop_token>
17#include <type_traits>
18#include <utility>
19
20namespace Common {
21namespace mpsc {
22#if defined(__cpp_lib_hardware_interference_size)
23constexpr size_t hardware_interference_size = std::hardware_destructive_interference_size;
24#else
25constexpr size_t hardware_interference_size = 64;
26#endif
27
28template <typename T>
29using AlignedAllocator = std::allocator<T>;
30
31template <typename T>
32struct Slot {
33 ~Slot() noexcept {
34 if (turn.test()) {
35 destroy();
36 }
37 }
38
39 template <typename... Args>
40 void construct(Args&&... args) noexcept {
41 static_assert(std::is_nothrow_constructible_v<T, Args&&...>,
42 "T must be nothrow constructible with Args&&...");
43 std::construct_at(reinterpret_cast<T*>(&storage), std::forward<Args>(args)...);
44 }
45
46 void destroy() noexcept {
47 static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible");
48 std::destroy_at(reinterpret_cast<T*>(&storage));
49 }
50
51 T&& move() noexcept {
52 return reinterpret_cast<T&&>(storage);
53 }
54
55 // Align to avoid false sharing between adjacent slots
56 alignas(hardware_interference_size) std::atomic_flag turn{};
57 struct aligned_store {
58 struct type {
59 alignas(T) unsigned char data[sizeof(T)];
60 };
61 };
62 typename aligned_store::type storage;
63};
64
65template <typename T, typename Allocator = AlignedAllocator<Slot<T>>>
66class Queue {
67public:
68 explicit Queue(const size_t capacity, const Allocator& allocator = Allocator())
69 : allocator_(allocator) {
70 if (capacity < 1) {
71 throw std::invalid_argument("capacity < 1");
72 }
73 // Ensure that the queue length is an integer power of 2
74 // This is so that idx(i) can be a simple i & mask_ insted of i % capacity
75 // https://github.com/rigtorp/MPMCQueue/pull/36
76 if (!std::has_single_bit(capacity)) {
77 throw std::invalid_argument("capacity must be an integer power of 2");
78 }
79
80 mask_ = capacity - 1;
81
82 // Allocate one extra slot to prevent false sharing on the last slot
83 slots_ = allocator_.allocate(mask_ + 2);
84 // Allocators are not required to honor alignment for over-aligned types
85 // (see http://eel.is/c++draft/allocator.requirements#10) so we verify
86 // alignment here
87 if (reinterpret_cast<uintptr_t>(slots_) % alignof(Slot<T>) != 0) {
88 allocator_.deallocate(slots_, mask_ + 2);
89 throw std::bad_alloc();
90 }
91 for (size_t i = 0; i < mask_ + 1; ++i) {
92 std::construct_at(&slots_[i]);
93 }
94 static_assert(alignof(Slot<T>) == hardware_interference_size,
95 "Slot must be aligned to cache line boundary to prevent false sharing");
96 static_assert(sizeof(Slot<T>) % hardware_interference_size == 0,
97 "Slot size must be a multiple of cache line size to prevent "
98 "false sharing between adjacent slots");
99 static_assert(sizeof(Queue) % hardware_interference_size == 0,
100 "Queue size must be a multiple of cache line size to "
101 "prevent false sharing between adjacent queues");
102 }
103
104 ~Queue() noexcept {
105 for (size_t i = 0; i < mask_ + 1; ++i) {
106 slots_[i].~Slot();
107 }
108 allocator_.deallocate(slots_, mask_ + 2);
109 }
110
111 // non-copyable and non-movable
112 Queue(const Queue&) = delete;
113 Queue& operator=(const Queue&) = delete;
114
115 void Push(const T& v) noexcept {
116 static_assert(std::is_nothrow_copy_constructible_v<T>,
117 "T must be nothrow copy constructible");
118 emplace(v);
119 }
120
121 template <typename P, typename = std::enable_if_t<std::is_nothrow_constructible_v<T, P&&>>>
122 void Push(P&& v) noexcept {
123 emplace(std::forward<P>(v));
124 }
125
126 void Pop(T& v, std::stop_token stop) noexcept {
127 auto const tail = tail_.fetch_add(1);
128 auto& slot = slots_[idx(tail)];
129 if (false == slot.turn.test()) {
130 std::unique_lock lock{cv_mutex};
131 cv.wait(lock, stop, [&slot] { return slot.turn.test(); });
132 }
133 v = slot.move();
134 slot.destroy();
135 slot.turn.clear();
136 slot.turn.notify_one();
137 }
138
139private:
140 template <typename... Args>
141 void emplace(Args&&... args) noexcept {
142 static_assert(std::is_nothrow_constructible_v<T, Args&&...>,
143 "T must be nothrow constructible with Args&&...");
144 auto const head = head_.fetch_add(1);
145 auto& slot = slots_[idx(head)];
146 slot.turn.wait(true);
147 slot.construct(std::forward<Args>(args)...);
148 slot.turn.test_and_set();
149 cv.notify_one();
150 }
151
152 constexpr size_t idx(size_t i) const noexcept {
153 return i & mask_;
154 }
155
156 std::conditional_t<true, std::condition_variable_any, std::condition_variable> cv;
157 std::mutex cv_mutex;
158 size_t mask_;
159 Slot<T>* slots_;
160 [[no_unique_address]] Allocator allocator_;
161
162 // Align to avoid false sharing between head_ and tail_
163 alignas(hardware_interference_size) std::atomic<size_t> head_{0};
164 alignas(hardware_interference_size) std::atomic<size_t> tail_{0};
165
166 static_assert(std::is_nothrow_copy_assignable_v<T> || std::is_nothrow_move_assignable_v<T>,
167 "T must be nothrow copy or move assignable");
168
169 static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible");
170};
171} // namespace mpsc
172
173template <typename T, typename Allocator = mpsc::AlignedAllocator<mpsc::Slot<T>>>
174using MPSCQueue = mpsc::Queue<T, Allocator>;
175
176} // namespace Common
177
178#ifdef _MSC_VER
179#pragma warning(pop)
180#endif
diff --git a/src/common/settings.h b/src/common/settings.h
index a7bbfb0da..a507744a2 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -496,7 +496,7 @@ struct Values {
496 496
497 // Renderer 497 // Renderer
498 RangedSetting<RendererBackend> renderer_backend{ 498 RangedSetting<RendererBackend> renderer_backend{
499 RendererBackend::OpenGL, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"}; 499 RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"};
500 BasicSetting<bool> renderer_debug{false, "debug"}; 500 BasicSetting<bool> renderer_debug{false, "debug"};
501 BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"}; 501 BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"};
502 BasicSetting<bool> enable_nsight_aftermath{false, "nsight_aftermath"}; 502 BasicSetting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index b79a73132..8479dc6d2 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -31,7 +31,8 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
31 VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer(); 31 VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
32 32
33 while (!stop_token.stop_requested()) { 33 while (!stop_token.stop_requested()) {
34 CommandDataContainer next = state.queue.PopWait(stop_token); 34 CommandDataContainer next;
35 state.queue.Pop(next, stop_token);
35 if (stop_token.stop_requested()) { 36 if (stop_token.stop_requested()) {
36 break; 37 break;
37 } 38 }
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index 71cd35756..ad9fd5eff 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -10,7 +10,7 @@
10#include <thread> 10#include <thread>
11#include <variant> 11#include <variant>
12 12
13#include "common/threadsafe_queue.h" 13#include "common/bounded_threadsafe_queue.h"
14#include "video_core/framebuffer_config.h" 14#include "video_core/framebuffer_config.h"
15 15
16namespace Tegra { 16namespace Tegra {
@@ -96,9 +96,9 @@ struct CommandDataContainer {
96 96
97/// Struct used to synchronize the GPU thread 97/// Struct used to synchronize the GPU thread
98struct SynchState final { 98struct SynchState final {
99 using CommandQueue = Common::SPSCQueue<CommandDataContainer, true>; 99 using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
100 std::mutex write_lock; 100 std::mutex write_lock;
101 CommandQueue queue; 101 CommandQueue queue{512}; // size must be 2^n
102 u64 last_fence{}; 102 u64 last_fence{};
103 std::atomic<u64> signaled_fence{}; 103 std::atomic<u64> signaled_fence{};
104 std::condition_variable_any cv; 104 std::condition_variable_any cv;
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp
index a5dd33fb2..4eb3913ee 100644
--- a/src/video_core/vulkan_common/vulkan_library.cpp
+++ b/src/video_core/vulkan_common/vulkan_library.cpp
@@ -5,11 +5,13 @@
5 5
6#include "common/dynamic_library.h" 6#include "common/dynamic_library.h"
7#include "common/fs/path_util.h" 7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
8#include "video_core/vulkan_common/vulkan_library.h" 9#include "video_core/vulkan_common/vulkan_library.h"
9 10
10namespace Vulkan { 11namespace Vulkan {
11 12
12Common::DynamicLibrary OpenLibrary() { 13Common::DynamicLibrary OpenLibrary() {
14 LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library");
13 Common::DynamicLibrary library; 15 Common::DynamicLibrary library;
14#ifdef __APPLE__ 16#ifdef __APPLE__
15 // Check if a path to a specific Vulkan library has been specified. 17 // Check if a path to a specific Vulkan library has been specified.
@@ -22,9 +24,11 @@ Common::DynamicLibrary OpenLibrary() {
22 } 24 }
23#else 25#else
24 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); 26 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
27 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename);
25 if (!library.Open(filename.c_str())) { 28 if (!library.Open(filename.c_str())) {
26 // Android devices may not have libvulkan.so.1, only libvulkan.so. 29 // Android devices may not have libvulkan.so.1, only libvulkan.so.
27 filename = Common::DynamicLibrary::GetVersionedFilename("vulkan"); 30 filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
31 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename);
28 void(library.Open(filename.c_str())); 32 void(library.Open(filename.c_str()));
29 } 33 }
30#endif 34#endif
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 69b46ddd9..242867a4f 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -30,6 +30,8 @@ add_executable(yuzu
30 applets/qt_web_browser_scripts.h 30 applets/qt_web_browser_scripts.h
31 bootmanager.cpp 31 bootmanager.cpp
32 bootmanager.h 32 bootmanager.h
33 check_vulkan.cpp
34 check_vulkan.h
33 compatdb.ui 35 compatdb.ui
34 compatibility_list.cpp 36 compatibility_list.cpp
35 compatibility_list.h 37 compatibility_list.h
diff --git a/src/yuzu/check_vulkan.cpp b/src/yuzu/check_vulkan.cpp
new file mode 100644
index 000000000..e6d66ab34
--- /dev/null
+++ b/src/yuzu/check_vulkan.cpp
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "video_core/vulkan_common/vulkan_wrapper.h"
5
6#include <filesystem>
7#include <fstream>
8#include "common/fs/fs.h"
9#include "common/fs/path_util.h"
10#include "common/logging/log.h"
11#include "video_core/vulkan_common/vulkan_instance.h"
12#include "video_core/vulkan_common/vulkan_library.h"
13#include "yuzu/check_vulkan.h"
14#include "yuzu/uisettings.h"
15
16constexpr char TEMP_FILE_NAME[] = "vulkan_check";
17
18bool CheckVulkan() {
19 if (UISettings::values.has_broken_vulkan) {
20 return true;
21 }
22
23 LOG_DEBUG(Frontend, "Checking presence of Vulkan");
24
25 const auto fs_config_loc = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir);
26 const auto temp_file_loc = fs_config_loc / TEMP_FILE_NAME;
27
28 if (std::filesystem::exists(temp_file_loc)) {
29 LOG_WARNING(Frontend, "Detected recovery from previous failed Vulkan initialization");
30
31 UISettings::values.has_broken_vulkan = true;
32 std::filesystem::remove(temp_file_loc);
33 return false;
34 }
35
36 std::ofstream temp_file_handle(temp_file_loc);
37 temp_file_handle.close();
38
39 try {
40 Vulkan::vk::InstanceDispatch dld;
41 const Common::DynamicLibrary library = Vulkan::OpenLibrary();
42 const Vulkan::vk::Instance instance =
43 Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0);
44
45 } catch (const Vulkan::vk::Exception& exception) {
46 LOG_ERROR(Frontend, "Failed to initialize Vulkan: {}", exception.what());
47 // Don't set has_broken_vulkan to true here: we care when loading Vulkan crashes the
48 // application, not when we can handle it.
49 }
50
51 std::filesystem::remove(temp_file_loc);
52 return true;
53}
diff --git a/src/yuzu/check_vulkan.h b/src/yuzu/check_vulkan.h
new file mode 100644
index 000000000..e4ea93582
--- /dev/null
+++ b/src/yuzu/check_vulkan.h
@@ -0,0 +1,6 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6bool CheckVulkan();
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index fffbfab02..9df4752be 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -682,6 +682,12 @@ void Config::ReadRendererValues() {
682 ReadGlobalSetting(Settings::values.bg_green); 682 ReadGlobalSetting(Settings::values.bg_green);
683 ReadGlobalSetting(Settings::values.bg_blue); 683 ReadGlobalSetting(Settings::values.bg_blue);
684 684
685 if (!global && UISettings::values.has_broken_vulkan &&
686 Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan &&
687 !Settings::values.renderer_backend.UsingGlobal()) {
688 Settings::values.renderer_backend.SetGlobal(true);
689 }
690
685 if (global) { 691 if (global) {
686 ReadBasicSetting(Settings::values.renderer_debug); 692 ReadBasicSetting(Settings::values.renderer_debug);
687 ReadBasicSetting(Settings::values.renderer_shader_feedback); 693 ReadBasicSetting(Settings::values.renderer_shader_feedback);
@@ -801,6 +807,7 @@ void Config::ReadUIValues() {
801 ReadBasicSetting(UISettings::values.pause_when_in_background); 807 ReadBasicSetting(UISettings::values.pause_when_in_background);
802 ReadBasicSetting(UISettings::values.mute_when_in_background); 808 ReadBasicSetting(UISettings::values.mute_when_in_background);
803 ReadBasicSetting(UISettings::values.hide_mouse); 809 ReadBasicSetting(UISettings::values.hide_mouse);
810 ReadBasicSetting(UISettings::values.has_broken_vulkan);
804 ReadBasicSetting(UISettings::values.disable_web_applet); 811 ReadBasicSetting(UISettings::values.disable_web_applet);
805 812
806 qt_config->endGroup(); 813 qt_config->endGroup();
@@ -1348,6 +1355,7 @@ void Config::SaveUIValues() {
1348 WriteBasicSetting(UISettings::values.pause_when_in_background); 1355 WriteBasicSetting(UISettings::values.pause_when_in_background);
1349 WriteBasicSetting(UISettings::values.mute_when_in_background); 1356 WriteBasicSetting(UISettings::values.mute_when_in_background);
1350 WriteBasicSetting(UISettings::values.hide_mouse); 1357 WriteBasicSetting(UISettings::values.hide_mouse);
1358 WriteBasicSetting(UISettings::values.has_broken_vulkan);
1351 WriteBasicSetting(UISettings::values.disable_web_applet); 1359 WriteBasicSetting(UISettings::values.disable_web_applet);
1352 1360
1353 qt_config->endGroup(); 1361 qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 2f1435b10..85f34dc35 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -17,6 +17,7 @@
17#include "video_core/vulkan_common/vulkan_library.h" 17#include "video_core/vulkan_common/vulkan_library.h"
18#include "yuzu/configuration/configuration_shared.h" 18#include "yuzu/configuration/configuration_shared.h"
19#include "yuzu/configuration/configure_graphics.h" 19#include "yuzu/configuration/configure_graphics.h"
20#include "yuzu/uisettings.h"
20 21
21ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent) 22ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent)
22 : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} { 23 : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} {
@@ -57,6 +58,24 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
57 UpdateBackgroundColorButton(new_bg_color); 58 UpdateBackgroundColorButton(new_bg_color);
58 }); 59 });
59 60
61 connect(ui->button_check_vulkan, &QAbstractButton::clicked, this, [this] {
62 UISettings::values.has_broken_vulkan = false;
63
64 if (RetrieveVulkanDevices()) {
65 ui->api->setEnabled(true);
66 ui->button_check_vulkan->hide();
67
68 for (const auto& device : vulkan_devices) {
69 ui->device->addItem(device);
70 }
71 } else {
72 UISettings::values.has_broken_vulkan = true;
73 }
74 });
75
76 ui->api->setEnabled(!UISettings::values.has_broken_vulkan.GetValue());
77 ui->button_check_vulkan->setVisible(UISettings::values.has_broken_vulkan.GetValue());
78
60 ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); 79 ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
61 ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal()); 80 ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
62} 81}
@@ -296,7 +315,7 @@ void ConfigureGraphics::UpdateAPILayout() {
296 vulkan_device = Settings::values.vulkan_device.GetValue(true); 315 vulkan_device = Settings::values.vulkan_device.GetValue(true);
297 shader_backend = Settings::values.shader_backend.GetValue(true); 316 shader_backend = Settings::values.shader_backend.GetValue(true);
298 ui->device_widget->setEnabled(false); 317 ui->device_widget->setEnabled(false);
299 ui->backend_widget->setEnabled(false); 318 ui->backend_widget->setEnabled(UISettings::values.has_broken_vulkan.GetValue());
300 } else { 319 } else {
301 vulkan_device = Settings::values.vulkan_device.GetValue(); 320 vulkan_device = Settings::values.vulkan_device.GetValue();
302 shader_backend = Settings::values.shader_backend.GetValue(); 321 shader_backend = Settings::values.shader_backend.GetValue();
@@ -318,7 +337,11 @@ void ConfigureGraphics::UpdateAPILayout() {
318 } 337 }
319} 338}
320 339
321void ConfigureGraphics::RetrieveVulkanDevices() try { 340bool ConfigureGraphics::RetrieveVulkanDevices() try {
341 if (UISettings::values.has_broken_vulkan) {
342 return false;
343 }
344
322 using namespace Vulkan; 345 using namespace Vulkan;
323 346
324 vk::InstanceDispatch dld; 347 vk::InstanceDispatch dld;
@@ -333,8 +356,10 @@ void ConfigureGraphics::RetrieveVulkanDevices() try {
333 vulkan_devices.push_back(QString::fromStdString(name)); 356 vulkan_devices.push_back(QString::fromStdString(name));
334 } 357 }
335 358
359 return true;
336} catch (const Vulkan::vk::Exception& exception) { 360} catch (const Vulkan::vk::Exception& exception) {
337 LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); 361 LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
362 return false;
338} 363}
339 364
340Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { 365Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
@@ -415,4 +440,11 @@ void ConfigureGraphics::SetupPerGameUI() {
415 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); 440 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
416 ConfigurationShared::InsertGlobalItem( 441 ConfigurationShared::InsertGlobalItem(
417 ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true))); 442 ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
443
444 if (UISettings::values.has_broken_vulkan) {
445 ui->backend_widget->setEnabled(true);
446 ConfigurationShared::SetColoredComboBox(
447 ui->backend, ui->backend_widget,
448 static_cast<int>(Settings::values.shader_backend.GetValue(true)));
449 }
418} 450}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 1b101c940..8438f0187 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -41,7 +41,7 @@ private:
41 void UpdateDeviceSelection(int device); 41 void UpdateDeviceSelection(int device);
42 void UpdateShaderBackendSelection(int backend); 42 void UpdateShaderBackendSelection(int backend);
43 43
44 void RetrieveVulkanDevices(); 44 bool RetrieveVulkanDevices();
45 45
46 void SetupPerGameUI(); 46 void SetupPerGameUI();
47 47
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 74f0e0b79..2f94c94bc 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -6,8 +6,8 @@
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>437</width> 9 <width>471</width>
10 <height>482</height> 10 <height>759</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -171,11 +171,11 @@
171 </widget> 171 </widget>
172 </item> 172 </item>
173 <item> 173 <item>
174 <widget class="QCheckBox" name="accelerate_astc"> 174 <widget class="QCheckBox" name="accelerate_astc">
175 <property name="text"> 175 <property name="text">
176 <string>Accelerate ASTC texture decoding</string> 176 <string>Accelerate ASTC texture decoding</string>
177 </property> 177 </property>
178 </widget> 178 </widget>
179 </item> 179 </item>
180 <item> 180 <item>
181 <widget class="QWidget" name="nvdec_emulation_widget" native="true"> 181 <widget class="QWidget" name="nvdec_emulation_widget" native="true">
@@ -438,43 +438,43 @@
438 </widget> 438 </widget>
439 </item> 439 </item>
440 <item> 440 <item>
441 <widget class="QWidget" name="anti_aliasing_layout" native="true"> 441 <widget class="QWidget" name="anti_aliasing_layout" native="true">
442 <layout class="QHBoxLayout" name="horizontalLayout_7"> 442 <layout class="QHBoxLayout" name="horizontalLayout_7">
443 <property name="leftMargin"> 443 <property name="leftMargin">
444 <number>0</number> 444 <number>0</number>
445 </property> 445 </property>
446 <property name="topMargin"> 446 <property name="topMargin">
447 <number>0</number> 447 <number>0</number>
448 </property> 448 </property>
449 <property name="rightMargin"> 449 <property name="rightMargin">
450 <number>0</number> 450 <number>0</number>
451 </property>
452 <property name="bottomMargin">
453 <number>0</number>
454 </property>
455 <item>
456 <widget class="QLabel" name="anti_aliasing_label">
457 <property name="text">
458 <string>Anti-Aliasing Method:</string>
459 </property>
460 </widget>
461 </item>
462 <item>
463 <widget class="QComboBox" name="anti_aliasing_combobox">
464 <item>
465 <property name="text">
466 <string>None</string>
451 </property> 467 </property>
452 <property name="bottomMargin"> 468 </item>
453 <number>0</number> 469 <item>
470 <property name="text">
471 <string>FXAA</string>
454 </property> 472 </property>
455 <item> 473 </item>
456 <widget class="QLabel" name="anti_aliasing_label"> 474 </widget>
457 <property name="text"> 475 </item>
458 <string>Anti-Aliasing Method:</string> 476 </layout>
459 </property> 477 </widget>
460 </widget>
461 </item>
462 <item>
463 <widget class="QComboBox" name="anti_aliasing_combobox">
464 <item>
465 <property name="text">
466 <string>None</string>
467 </property>
468 </item>
469 <item>
470 <property name="text">
471 <string>FXAA</string>
472 </property>
473 </item>
474 </widget>
475 </item>
476 </layout>
477 </widget>
478 </item> 478 </item>
479 <item> 479 <item>
480 <widget class="QWidget" name="bg_layout" native="true"> 480 <widget class="QWidget" name="bg_layout" native="true">
@@ -574,6 +574,13 @@
574 </property> 574 </property>
575 </spacer> 575 </spacer>
576 </item> 576 </item>
577 <item>
578 <widget class="QPushButton" name="button_check_vulkan">
579 <property name="text">
580 <string>Check for Working Vulkan</string>
581 </property>
582 </widget>
583 </item>
577 </layout> 584 </layout>
578 </widget> 585 </widget>
579 <resources/> 586 <resources/>
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index d13530a5b..6321afc83 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -870,7 +870,7 @@ GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent}
870 layout->setAlignment(Qt::AlignCenter); 870 layout->setAlignment(Qt::AlignCenter);
871 image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); 871 image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
872 872
873 text->setText(tr("Double-click to add a new folder to the game list")); 873 RetranslateUI();
874 QFont font = text->font(); 874 QFont font = text->font();
875 font.setPointSize(20); 875 font.setPointSize(20);
876 text->setFont(font); 876 text->setFont(font);
@@ -891,3 +891,15 @@ void GameListPlaceholder::onUpdateThemedIcons() {
891void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { 891void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
892 emit GameListPlaceholder::AddDirectory(); 892 emit GameListPlaceholder::AddDirectory();
893} 893}
894
895void GameListPlaceholder::changeEvent(QEvent* event) {
896 if (event->type() == QEvent::LanguageChange) {
897 RetranslateUI();
898 }
899
900 QWidget::changeEvent(event);
901}
902
903void GameListPlaceholder::RetranslateUI() {
904 text->setText(tr("Double-click to add a new folder to the game list"));
905}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index d19dbe4b0..464da98ad 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -166,6 +166,9 @@ protected:
166 void mouseDoubleClickEvent(QMouseEvent* event) override; 166 void mouseDoubleClickEvent(QMouseEvent* event) override;
167 167
168private: 168private:
169 void changeEvent(QEvent* event) override;
170 void RetranslateUI();
171
169 QVBoxLayout* layout = nullptr; 172 QVBoxLayout* layout = nullptr;
170 QLabel* image = nullptr; 173 QLabel* image = nullptr;
171 QLabel* text = nullptr; 174 QLabel* text = nullptr;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index c57bac2a7..27f23bcb0 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -52,7 +52,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
52#define QT_NO_OPENGL 52#define QT_NO_OPENGL
53#include <QClipboard> 53#include <QClipboard>
54#include <QDesktopServices> 54#include <QDesktopServices>
55#include <QDesktopWidget>
56#include <QFile> 55#include <QFile>
57#include <QFileDialog> 56#include <QFileDialog>
58#include <QInputDialog> 57#include <QInputDialog>
@@ -60,6 +59,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
60#include <QProgressBar> 59#include <QProgressBar>
61#include <QProgressDialog> 60#include <QProgressDialog>
62#include <QPushButton> 61#include <QPushButton>
62#include <QScreen>
63#include <QShortcut> 63#include <QShortcut>
64#include <QStatusBar> 64#include <QStatusBar>
65#include <QString> 65#include <QString>
@@ -115,6 +115,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
115#include "video_core/shader_notify.h" 115#include "video_core/shader_notify.h"
116#include "yuzu/about_dialog.h" 116#include "yuzu/about_dialog.h"
117#include "yuzu/bootmanager.h" 117#include "yuzu/bootmanager.h"
118#include "yuzu/check_vulkan.h"
118#include "yuzu/compatdb.h" 119#include "yuzu/compatdb.h"
119#include "yuzu/compatibility_list.h" 120#include "yuzu/compatibility_list.h"
120#include "yuzu/configuration/config.h" 121#include "yuzu/configuration/config.h"
@@ -351,6 +352,23 @@ GMainWindow::GMainWindow()
351 352
352 MigrateConfigFiles(); 353 MigrateConfigFiles();
353 354
355 if (!CheckVulkan()) {
356 config->Save();
357
358 QMessageBox::warning(
359 this, tr("Broken Vulkan Installation Detected"),
360 tr("Vulkan initialization failed on the previous boot.<br><br>Click <a "
361 "href='https://yuzu-emu.org/wiki/faq/"
362 "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>here for "
363 "instructions to fix the issue</a>."));
364 }
365 if (UISettings::values.has_broken_vulkan) {
366 Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
367
368 renderer_status_button->setDisabled(true);
369 renderer_status_button->setChecked(false);
370 }
371
354#if defined(HAVE_SDL2) && !defined(_WIN32) 372#if defined(HAVE_SDL2) && !defined(_WIN32)
355 SDL_InitSubSystem(SDL_INIT_VIDEO); 373 SDL_InitSubSystem(SDL_INIT_VIDEO);
356 // SDL disables the screen saver by default, and setting the hint 374 // SDL disables the screen saver by default, and setting the hint
@@ -1055,7 +1073,7 @@ void GMainWindow::InitializeHotkeys() {
1055 1073
1056void GMainWindow::SetDefaultUIGeometry() { 1074void GMainWindow::SetDefaultUIGeometry() {
1057 // geometry: 53% of the window contents are in the upper screen half, 47% in the lower half 1075 // geometry: 53% of the window contents are in the upper screen half, 47% in the lower half
1058 const QRect screenRect = QApplication::desktop()->screenGeometry(this); 1076 const QRect screenRect = QGuiApplication::primaryScreen()->geometry();
1059 1077
1060 const int w = screenRect.width() * 2 / 3; 1078 const int w = screenRect.width() * 2 / 3;
1061 const int h = screenRect.height() * 2 / 3; 1079 const int h = screenRect.height() * 2 / 3;
@@ -1620,7 +1638,7 @@ void GMainWindow::ShutdownGame() {
1620 emu_speed_label->setVisible(false); 1638 emu_speed_label->setVisible(false);
1621 game_fps_label->setVisible(false); 1639 game_fps_label->setVisible(false);
1622 emu_frametime_label->setVisible(false); 1640 emu_frametime_label->setVisible(false);
1623 renderer_status_button->setEnabled(true); 1641 renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
1624 1642
1625 game_path.clear(); 1643 game_path.clear();
1626 1644
@@ -2638,6 +2656,18 @@ void GMainWindow::ToggleFullscreen() {
2638 } 2656 }
2639} 2657}
2640 2658
2659// We're going to return the screen that the given window has the most pixels on
2660static QScreen* GuessCurrentScreen(QWidget* window) {
2661 const QList<QScreen*> screens = QGuiApplication::screens();
2662 return *std::max_element(
2663 screens.cbegin(), screens.cend(), [window](const QScreen* left, const QScreen* right) {
2664 const QSize left_size = left->geometry().intersected(window->geometry()).size();
2665 const QSize right_size = right->geometry().intersected(window->geometry()).size();
2666 return (left_size.height() * left_size.width()) <
2667 (right_size.height() * right_size.width());
2668 });
2669}
2670
2641void GMainWindow::ShowFullscreen() { 2671void GMainWindow::ShowFullscreen() {
2642 const auto show_fullscreen = [](QWidget* window) { 2672 const auto show_fullscreen = [](QWidget* window) {
2643 if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) { 2673 if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
@@ -2646,7 +2676,7 @@ void GMainWindow::ShowFullscreen() {
2646 } 2676 }
2647 window->hide(); 2677 window->hide();
2648 window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint); 2678 window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint);
2649 const auto screen_geometry = QApplication::desktop()->screenGeometry(window); 2679 const auto screen_geometry = GuessCurrentScreen(window)->geometry();
2650 window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(), 2680 window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(),
2651 screen_geometry.height() + 1); 2681 screen_geometry.height() + 1);
2652 window->raise(); 2682 window->raise();
@@ -2830,6 +2860,10 @@ void GMainWindow::OnConfigure() {
2830 mouse_hide_timer.start(); 2860 mouse_hide_timer.start();
2831 } 2861 }
2832 2862
2863 if (!UISettings::values.has_broken_vulkan) {
2864 renderer_status_button->setEnabled(!emulation_running);
2865 }
2866
2833 UpdateStatusButtons(); 2867 UpdateStatusButtons();
2834 controller_dialog->refreshConfiguration(); 2868 controller_dialog->refreshConfiguration();
2835} 2869}
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 15ba9ea17..c64d87ace 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -77,6 +77,8 @@ struct Values {
77 Settings::BasicSetting<bool> pause_when_in_background{false, "pauseWhenInBackground"}; 77 Settings::BasicSetting<bool> pause_when_in_background{false, "pauseWhenInBackground"};
78 Settings::BasicSetting<bool> mute_when_in_background{false, "muteWhenInBackground"}; 78 Settings::BasicSetting<bool> mute_when_in_background{false, "muteWhenInBackground"};
79 Settings::BasicSetting<bool> hide_mouse{true, "hideInactiveMouse"}; 79 Settings::BasicSetting<bool> hide_mouse{true, "hideInactiveMouse"};
80 // Set when Vulkan is known to crash the application
81 Settings::BasicSetting<bool> has_broken_vulkan{false, "has_broken_vulkan"};
80 82
81 Settings::BasicSetting<bool> select_user_on_boot{false, "select_user_on_boot"}; 83 Settings::BasicSetting<bool> select_user_on_boot{false, "select_user_on_boot"};
82 84
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index f34d6b728..39063e32b 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -218,7 +218,7 @@ cpuopt_unsafe_ignore_global_monitor =
218 218
219[Renderer] 219[Renderer]
220# Which backend API to use. 220# Which backend API to use.
221# 0 (default): OpenGL, 1: Vulkan 221# 0: OpenGL, 1 (default): Vulkan
222backend = 222backend =
223 223
224# Enable graphics API debugging mode. 224# Enable graphics API debugging mode.