summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/bounded_threadsafe_queue.h180
-rw-r--r--src/common/settings.h2
-rw-r--r--src/core/debugger/debugger.cpp2
-rw-r--r--src/core/debugger/gdbstub_arch.h1
-rw-r--r--src/core/hle/kernel/k_code_memory.cpp28
-rw-r--r--src/core/hle/kernel/k_page_table.cpp111
-rw-r--r--src/core/hle/kernel/k_page_table.h5
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp4
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h3
-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.txt21
-rw-r--r--src/yuzu/bootmanager.cpp2
-rw-r--r--src/yuzu/bootmanager.h2
-rw-r--r--src/yuzu/check_vulkan.cpp53
-rw-r--r--src/yuzu/check_vulkan.h6
-rw-r--r--src/yuzu/configuration/config.cpp52
-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/configuration/configure_hotkeys.cpp22
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp2
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui19
-rw-r--r--src/yuzu/configuration/configure_system.cpp6
-rw-r--r--src/yuzu/game_list.cpp16
-rw-r--r--src/yuzu/game_list.h3
-rw-r--r--src/yuzu/loading_screen.cpp2
-rw-r--r--src/yuzu/loading_screen.h3
-rw-r--r--src/yuzu/main.cpp90
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/uisettings.h2
-rw-r--r--src/yuzu_cmd/default_ini.h2
33 files changed, 629 insertions, 153 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/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index 1d7f9a775..edf991d71 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -110,7 +110,7 @@ private:
110 connection_thread = std::jthread([&, port](std::stop_token stop_token) { 110 connection_thread = std::jthread([&, port](std::stop_token stop_token) {
111 try { 111 try {
112 // Initialize the listening socket and accept a new client. 112 // Initialize the listening socket and accept a new client.
113 tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port}; 113 tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port};
114 tcp::acceptor acceptor{io_context, endpoint}; 114 tcp::acceptor acceptor{io_context, endpoint};
115 115
116 acceptor.async_accept(client_socket, [](const auto&) {}); 116 acceptor.async_accept(client_socket, [](const auto&) {});
diff --git a/src/core/debugger/gdbstub_arch.h b/src/core/debugger/gdbstub_arch.h
index 4d039a9f7..2540d6456 100644
--- a/src/core/debugger/gdbstub_arch.h
+++ b/src/core/debugger/gdbstub_arch.h
@@ -15,6 +15,7 @@ namespace Core {
15 15
16class GDBStubArch { 16class GDBStubArch {
17public: 17public:
18 virtual ~GDBStubArch() = default;
18 virtual std::string GetTargetXML() const = 0; 19 virtual std::string GetTargetXML() const = 0;
19 virtual std::string RegRead(const Kernel::KThread* thread, size_t id) const = 0; 20 virtual std::string RegRead(const Kernel::KThread* thread, size_t id) const = 0;
20 virtual void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const = 0; 21 virtual void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const = 0;
diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp
index fd3cbfd94..4ae40ec8e 100644
--- a/src/core/hle/kernel/k_code_memory.cpp
+++ b/src/core/hle/kernel/k_code_memory.cpp
@@ -27,23 +27,18 @@ ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr
27 auto& page_table = m_owner->PageTable(); 27 auto& page_table = m_owner->PageTable();
28 28
29 // Construct the page group. 29 // Construct the page group.
30 m_page_group = 30 m_page_group = {};
31 KPageLinkedList(page_table.GetPhysicalAddr(addr), Common::DivideUp(size, PageSize));
32 31
33 // Lock the memory. 32 // Lock the memory.
34 R_TRY(page_table.LockForCodeMemory(addr, size)) 33 R_TRY(page_table.LockForCodeMemory(&m_page_group, addr, size))
35 34
36 // Clear the memory. 35 // Clear the memory.
37 // 36 for (const auto& block : m_page_group.Nodes()) {
38 // FIXME: this ends up clobbering address ranges outside the scope of the mapping within 37 std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize());
39 // guest memory, and is not specifically required if the guest program is correctly 38 }
40 // written, so disable until this is further investigated.
41 //
42 // for (const auto& block : m_page_group.Nodes()) {
43 // std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize());
44 // }
45 39
46 // Set remaining tracking members. 40 // Set remaining tracking members.
41 m_owner->Open();
47 m_address = addr; 42 m_address = addr;
48 m_is_initialized = true; 43 m_is_initialized = true;
49 m_is_owner_mapped = false; 44 m_is_owner_mapped = false;
@@ -57,8 +52,14 @@ void KCodeMemory::Finalize() {
57 // Unlock. 52 // Unlock.
58 if (!m_is_mapped && !m_is_owner_mapped) { 53 if (!m_is_mapped && !m_is_owner_mapped) {
59 const size_t size = m_page_group.GetNumPages() * PageSize; 54 const size_t size = m_page_group.GetNumPages() * PageSize;
60 m_owner->PageTable().UnlockForCodeMemory(m_address, size); 55 m_owner->PageTable().UnlockForCodeMemory(m_address, size, m_page_group);
61 } 56 }
57
58 // Close the page group.
59 m_page_group = {};
60
61 // Close our reference to our owner.
62 m_owner->Close();
62} 63}
63 64
64ResultCode KCodeMemory::Map(VAddr address, size_t size) { 65ResultCode KCodeMemory::Map(VAddr address, size_t size) {
@@ -118,7 +119,8 @@ ResultCode KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermis
118 k_perm = KMemoryPermission::UserReadExecute; 119 k_perm = KMemoryPermission::UserReadExecute;
119 break; 120 break;
120 default: 121 default:
121 break; 122 // Already validated by ControlCodeMemory svc
123 UNREACHABLE();
122 } 124 }
123 125
124 // Map the memory. 126 // Map the memory.
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index b38ef333b..68867a2bb 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -542,6 +542,95 @@ ResultCode KPageTable::MakePageGroup(KPageLinkedList& pg, VAddr addr, size_t num
542 return ResultSuccess; 542 return ResultSuccess;
543} 543}
544 544
545bool KPageTable::IsValidPageGroup(const KPageLinkedList& pg_ll, VAddr addr, size_t num_pages) {
546 ASSERT(this->IsLockedByCurrentThread());
547
548 const size_t size = num_pages * PageSize;
549 const auto& pg = pg_ll.Nodes();
550 const auto& memory_layout = system.Kernel().MemoryLayout();
551
552 // Empty groups are necessarily invalid.
553 if (pg.empty()) {
554 return false;
555 }
556
557 // We're going to validate that the group we'd expect is the group we see.
558 auto cur_it = pg.begin();
559 PAddr cur_block_address = cur_it->GetAddress();
560 size_t cur_block_pages = cur_it->GetNumPages();
561
562 auto UpdateCurrentIterator = [&]() {
563 if (cur_block_pages == 0) {
564 if ((++cur_it) == pg.end()) {
565 return false;
566 }
567
568 cur_block_address = cur_it->GetAddress();
569 cur_block_pages = cur_it->GetNumPages();
570 }
571 return true;
572 };
573
574 // Begin traversal.
575 Common::PageTable::TraversalContext context;
576 Common::PageTable::TraversalEntry next_entry;
577 if (!page_table_impl.BeginTraversal(next_entry, context, addr)) {
578 return false;
579 }
580
581 // Prepare tracking variables.
582 PAddr cur_addr = next_entry.phys_addr;
583 size_t cur_size = next_entry.block_size - (cur_addr & (next_entry.block_size - 1));
584 size_t tot_size = cur_size;
585
586 // Iterate, comparing expected to actual.
587 while (tot_size < size) {
588 if (!page_table_impl.ContinueTraversal(next_entry, context)) {
589 return false;
590 }
591
592 if (next_entry.phys_addr != (cur_addr + cur_size)) {
593 const size_t cur_pages = cur_size / PageSize;
594
595 if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) {
596 return false;
597 }
598
599 if (!UpdateCurrentIterator()) {
600 return false;
601 }
602
603 if (cur_block_address != cur_addr || cur_block_pages < cur_pages) {
604 return false;
605 }
606
607 cur_block_address += cur_size;
608 cur_block_pages -= cur_pages;
609 cur_addr = next_entry.phys_addr;
610 cur_size = next_entry.block_size;
611 } else {
612 cur_size += next_entry.block_size;
613 }
614
615 tot_size += next_entry.block_size;
616 }
617
618 // Ensure we compare the right amount for the last block.
619 if (tot_size > size) {
620 cur_size -= (tot_size - size);
621 }
622
623 if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) {
624 return false;
625 }
626
627 if (!UpdateCurrentIterator()) {
628 return false;
629 }
630
631 return cur_block_address == cur_addr && cur_block_pages == (cur_size / PageSize);
632}
633
545ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size, 634ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size,
546 KPageTable& src_page_table, VAddr src_addr) { 635 KPageTable& src_page_table, VAddr src_addr) {
547 KScopedLightLock lk(general_lock); 636 KScopedLightLock lk(general_lock);
@@ -1687,22 +1776,22 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size)
1687 return ResultSuccess; 1776 return ResultSuccess;
1688} 1777}
1689 1778
1690ResultCode KPageTable::LockForCodeMemory(VAddr addr, std::size_t size) { 1779ResultCode KPageTable::LockForCodeMemory(KPageLinkedList* out, VAddr addr, std::size_t size) {
1691 return this->LockMemoryAndOpen( 1780 return this->LockMemoryAndOpen(
1692 nullptr, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, 1781 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
1693 KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, KMemoryPermission::UserReadWrite, 1782 KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
1694 KMemoryAttribute::All, KMemoryAttribute::None, 1783 KMemoryAttribute::None,
1695 static_cast<KMemoryPermission>(KMemoryPermission::NotMapped | 1784 static_cast<KMemoryPermission>(KMemoryPermission::NotMapped |
1696 KMemoryPermission::KernelReadWrite), 1785 KMemoryPermission::KernelReadWrite),
1697 KMemoryAttribute::Locked); 1786 KMemoryAttribute::Locked);
1698} 1787}
1699 1788
1700ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size) { 1789ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size,
1701 return this->UnlockMemory(addr, size, KMemoryState::FlagCanCodeMemory, 1790 const KPageLinkedList& pg) {
1702 KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, 1791 return this->UnlockMemory(
1703 KMemoryPermission::None, KMemoryAttribute::All, 1792 addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
1704 KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, 1793 KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All,
1705 KMemoryAttribute::Locked, nullptr); 1794 KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, KMemoryAttribute::Locked, &pg);
1706} 1795}
1707 1796
1708ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) { 1797ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
@@ -2125,7 +2214,7 @@ ResultCode KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_
2125 2214
2126 // Check the page group. 2215 // Check the page group.
2127 if (pg != nullptr) { 2216 if (pg != nullptr) {
2128 UNIMPLEMENTED_MSG("PageGroup support is unimplemented!"); 2217 R_UNLESS(this->IsValidPageGroup(*pg, addr, num_pages), ResultInvalidMemoryRegion);
2129 } 2218 }
2130 2219
2131 // Decide on new perm and attr. 2220 // Decide on new perm and attr.
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index 52a93ce86..6312eb682 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -72,8 +72,8 @@ public:
72 KMemoryPermission perm, PAddr map_addr = 0); 72 KMemoryPermission perm, PAddr map_addr = 0);
73 ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size); 73 ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size);
74 ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size); 74 ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
75 ResultCode LockForCodeMemory(VAddr addr, std::size_t size); 75 ResultCode LockForCodeMemory(KPageLinkedList* out, VAddr addr, std::size_t size);
76 ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size); 76 ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size, const KPageLinkedList& pg);
77 ResultCode MakeAndOpenPageGroup(KPageLinkedList* out, VAddr address, size_t num_pages, 77 ResultCode MakeAndOpenPageGroup(KPageLinkedList* out, VAddr address, size_t num_pages,
78 KMemoryState state_mask, KMemoryState state, 78 KMemoryState state_mask, KMemoryState state,
79 KMemoryPermission perm_mask, KMemoryPermission perm, 79 KMemoryPermission perm_mask, KMemoryPermission perm,
@@ -178,6 +178,7 @@ private:
178 const KPageLinkedList* pg); 178 const KPageLinkedList* pg);
179 179
180 ResultCode MakePageGroup(KPageLinkedList& pg, VAddr addr, size_t num_pages); 180 ResultCode MakePageGroup(KPageLinkedList& pg, VAddr addr, size_t num_pages);
181 bool IsValidPageGroup(const KPageLinkedList& pg, VAddr addr, size_t num_pages);
181 182
182 bool IsLockedByCurrentThread() const { 183 bool IsLockedByCurrentThread() const {
183 return general_lock.IsLockedByCurrentThread(); 184 return general_lock.IsLockedByCurrentThread();
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 3eae1ae35..32e0708ba 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -61,6 +61,7 @@ void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
61 } 61 }
62 62
63 last_update_timestamp = shared_memory->gesture_lifo.timestamp; 63 last_update_timestamp = shared_memory->gesture_lifo.timestamp;
64 UpdateGestureSharedMemory(gesture, time_difference);
64} 65}
65 66
66void Controller_Gesture::ReadTouchInput() { 67void Controller_Gesture::ReadTouchInput() {
@@ -94,8 +95,7 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
94 return false; 95 return false;
95} 96}
96 97
97void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size, 98void Controller_Gesture::UpdateGestureSharedMemory(GestureProperties& gesture,
98 GestureProperties& gesture,
99 f32 time_difference) { 99 f32 time_difference) {
100 GestureType type = GestureType::Idle; 100 GestureType type = GestureType::Idle;
101 GestureAttribute attributes{}; 101 GestureAttribute attributes{};
diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h
index c62a341bf..0d6099ea0 100644
--- a/src/core/hle/service/hid/controllers/gesture.h
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -107,8 +107,7 @@ private:
107 bool ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference); 107 bool ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference);
108 108
109 // Updates the shared memory to the next state 109 // Updates the shared memory to the next state
110 void UpdateGestureSharedMemory(u8* data, std::size_t size, GestureProperties& gesture, 110 void UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_difference);
111 f32 time_difference);
112 111
113 // Initializes new gesture 112 // Initializes new gesture
114 void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes); 113 void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes);
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 07df9675d..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
@@ -187,7 +189,7 @@ if (ENABLE_QT_TRANSLATION)
187 # Update source TS file if enabled 189 # Update source TS file if enabled
188 if (GENERATE_QT_TRANSLATION) 190 if (GENERATE_QT_TRANSLATION)
189 get_target_property(SRCS yuzu SOURCES) 191 get_target_property(SRCS yuzu SOURCES)
190 qt5_create_translation(QM_FILES 192 qt_create_translation(QM_FILES
191 ${SRCS} 193 ${SRCS}
192 ${UIS} 194 ${UIS}
193 ${YUZU_QT_LANGUAGES}/en.ts 195 ${YUZU_QT_LANGUAGES}/en.ts
@@ -203,7 +205,7 @@ if (ENABLE_QT_TRANSLATION)
203 list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts) 205 list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts)
204 206
205 # Compile TS files to QM files 207 # Compile TS files to QM files
206 qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) 208 qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
207 209
208 # Build a QRC file from the QM file list 210 # Build a QRC file from the QM file list
209 set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) 211 set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
@@ -215,7 +217,7 @@ if (ENABLE_QT_TRANSLATION)
215 file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>") 217 file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
216 218
217 # Add the QRC file to package in all QM files 219 # Add the QRC file to package in all QM files
218 qt5_add_resources(LANGUAGES ${LANGUAGES_QRC}) 220 qt_add_resources(LANGUAGES ${LANGUAGES_QRC})
219else() 221else()
220 set(LANGUAGES) 222 set(LANGUAGES)
221endif() 223endif()
@@ -236,8 +238,13 @@ if (APPLE)
236 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) 238 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
237elseif(WIN32) 239elseif(WIN32)
238 # compile as a win32 gui application instead of a console application 240 # compile as a win32 gui application instead of a console application
239 target_link_libraries(yuzu PRIVATE Qt5::WinMain) 241 if (QT_VERSION VERSION_GREATER 6)
242 target_link_libraries(yuzu PRIVATE Qt6::EntryPointPrivate)
243 else()
244 target_link_libraries(yuzu PRIVATE Qt5::WinMain)
245 endif()
240 if(MSVC) 246 if(MSVC)
247 target_link_libraries(yuzu PRIVATE version.lib)
241 set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") 248 set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
242 elseif(MINGW) 249 elseif(MINGW)
243 set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows") 250 set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows")
@@ -247,7 +254,7 @@ endif()
247create_target_directory_groups(yuzu) 254create_target_directory_groups(yuzu)
248 255
249target_link_libraries(yuzu PRIVATE common core input_common video_core) 256target_link_libraries(yuzu PRIVATE common core input_common video_core)
250target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets) 257target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets)
251target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) 258target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
252 259
253target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) 260target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
@@ -255,7 +262,7 @@ if (NOT WIN32)
255 target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) 262 target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
256endif() 263endif()
257if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 264if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
258 target_link_libraries(yuzu PRIVATE Qt5::DBus) 265 target_link_libraries(yuzu PRIVATE Qt::DBus)
259endif() 266endif()
260 267
261target_compile_definitions(yuzu PRIVATE 268target_compile_definitions(yuzu PRIVATE
@@ -291,7 +298,7 @@ if (USE_DISCORD_PRESENCE)
291endif() 298endif()
292 299
293if (YUZU_USE_QT_WEB_ENGINE) 300if (YUZU_USE_QT_WEB_ENGINE)
294 target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets) 301 target_link_libraries(yuzu PRIVATE Qt::WebEngineCore Qt::WebEngineWidgets)
295 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) 302 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
296endif () 303endif ()
297 304
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index aae2de2f8..bde465485 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -752,7 +752,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
752 input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, center_x, center_y); 752 input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, center_x, center_y);
753 753
754 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { 754 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
755 QCursor::setPos(mapToGlobal({center_x, center_y})); 755 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
756 } 756 }
757 757
758 emit MouseActivity(); 758 emit MouseActivity();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 87c559e7a..d01538039 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -10,6 +10,7 @@
10#include <mutex> 10#include <mutex>
11 11
12#include <QImage> 12#include <QImage>
13#include <QStringList>
13#include <QThread> 14#include <QThread>
14#include <QTouchEvent> 15#include <QTouchEvent>
15#include <QWidget> 16#include <QWidget>
@@ -20,7 +21,6 @@
20class GRenderWindow; 21class GRenderWindow;
21class GMainWindow; 22class GMainWindow;
22class QKeyEvent; 23class QKeyEvent;
23class QStringList;
24 24
25namespace Core { 25namespace Core {
26enum class SystemResultStatus : u32; 26enum class SystemResultStatus : u32;
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 583e9df24..9df4752be 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -71,28 +71,28 @@ const std::array<int, 2> Config::default_ringcon_analogs{{
71// UISetting::values.shortcuts, which is alphabetically ordered. 71// UISetting::values.shortcuts, which is alphabetically ordered.
72// clang-format off 72// clang-format off
73const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ 73const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
74 {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}}, 74 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}},
75 {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}}, 75 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}},
76 {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, 76 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}},
77 {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, 77 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}},
78 {QStringLiteral("Change Adapting Filter"), QStringLiteral("Main Window"), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}}, 78 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}},
79 {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, 79 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}},
80 {QStringLiteral("Change GPU Accuracy"), QStringLiteral("Main Window"), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}}, 80 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}},
81 {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}}, 81 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}},
82 {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}}, 82 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}},
83 {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}}, 83 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}},
84 {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}}, 84 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}},
85 {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, 85 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}},
86 {QStringLiteral("Load/Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}}, 86 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}},
87 {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}}, 87 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}},
88 {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}}, 88 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}},
89 {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}}, 89 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}},
90 {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}}, 90 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}},
91 {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}}, 91 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}},
92 {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}}, 92 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}},
93 {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}}, 93 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}},
94 {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}}, 94 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}},
95 {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}}, 95 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}},
96}}; 96}};
97// clang-format on 97// clang-format on
98 98
@@ -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/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 6679e9c53..edf0893c4 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -61,14 +61,18 @@ ConfigureHotkeys::~ConfigureHotkeys() = default;
61 61
62void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { 62void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
63 for (const auto& group : registry.hotkey_groups) { 63 for (const auto& group : registry.hotkey_groups) {
64 auto* parent_item = new QStandardItem(group.first); 64 auto* parent_item =
65 new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(group.first)));
65 parent_item->setEditable(false); 66 parent_item->setEditable(false);
67 parent_item->setData(group.first);
66 for (const auto& hotkey : group.second) { 68 for (const auto& hotkey : group.second) {
67 auto* action = new QStandardItem(hotkey.first); 69 auto* action =
70 new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(hotkey.first)));
68 auto* keyseq = 71 auto* keyseq =
69 new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText)); 72 new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
70 auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq); 73 auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
71 action->setEditable(false); 74 action->setEditable(false);
75 action->setData(hotkey.first);
72 keyseq->setEditable(false); 76 keyseq->setEditable(false);
73 controller_keyseq->setEditable(false); 77 controller_keyseq->setEditable(false);
74 parent_item->appendRow({action, keyseq, controller_keyseq}); 78 parent_item->appendRow({action, keyseq, controller_keyseq});
@@ -93,6 +97,16 @@ void ConfigureHotkeys::RetranslateUI() {
93 ui->retranslateUi(this); 97 ui->retranslateUi(this);
94 98
95 model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")}); 99 model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")});
100 for (int key_id = 0; key_id < model->rowCount(); key_id++) {
101 QStandardItem* parent = model->item(key_id, 0);
102 parent->setText(
103 QCoreApplication::translate("Hotkeys", qPrintable(parent->data().toString())));
104 for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
105 QStandardItem* action = parent->child(key_column_id, name_column);
106 action->setText(
107 QCoreApplication::translate("Hotkeys", qPrintable(action->data().toString())));
108 }
109 }
96} 110}
97 111
98void ConfigureHotkeys::Configure(QModelIndex index) { 112void ConfigureHotkeys::Configure(QModelIndex index) {
@@ -273,10 +287,10 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
273 const QStandardItem* controller_keyseq = 287 const QStandardItem* controller_keyseq =
274 parent->child(key_column_id, controller_column); 288 parent->child(key_column_id, controller_column);
275 for (auto& [group, sub_actions] : registry.hotkey_groups) { 289 for (auto& [group, sub_actions] : registry.hotkey_groups) {
276 if (group != parent->text()) 290 if (group != parent->data())
277 continue; 291 continue;
278 for (auto& [action_name, hotkey] : sub_actions) { 292 for (auto& [action_name, hotkey] : sub_actions) {
279 if (action_name != action->text()) 293 if (action_name != action->data())
280 continue; 294 continue;
281 hotkey.keyseq = QKeySequence(keyseq->text()); 295 hotkey.keyseq = QKeySequence(keyseq->text());
282 hotkey.controller_keyseq = controller_keyseq->text(); 296 hotkey.controller_keyseq = controller_keyseq->text();
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
index 27559c37b..c313b0919 100644
--- a/src/yuzu/configuration/configure_motion_touch.cpp
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -151,6 +151,8 @@ void ConfigureMotionTouch::ConnectEvents() {
151 &ConfigureMotionTouch::OnConfigureTouchCalibration); 151 &ConfigureMotionTouch::OnConfigureTouchCalibration);
152 connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this, 152 connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
153 &ConfigureMotionTouch::OnConfigureTouchFromButton); 153 &ConfigureMotionTouch::OnConfigureTouchFromButton);
154 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
155 &ConfigureMotionTouch::ApplyConfiguration);
154 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] { 156 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
155 if (CanCloseDialog()) { 157 if (CanCloseDialog()) {
156 reject(); 158 reject();
diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui
index c75a84ae4..0237fae54 100644
--- a/src/yuzu/configuration/configure_motion_touch.ui
+++ b/src/yuzu/configuration/configure_motion_touch.ui
@@ -293,22 +293,5 @@
293 </layout> 293 </layout>
294 </widget> 294 </widget>
295 <resources/> 295 <resources/>
296 <connections> 296 <connections/>
297 <connection>
298 <sender>buttonBox</sender>
299 <signal>accepted()</signal>
300 <receiver>ConfigureMotionTouch</receiver>
301 <slot>ApplyConfiguration()</slot>
302 <hints>
303 <hint type="sourcelabel">
304 <x>20</x>
305 <y>20</y>
306 </hint>
307 <hint type="destinationlabel">
308 <x>20</x>
309 <y>20</y>
310 </hint>
311 </hints>
312 </connection>
313 </connections>
314</ui> 297</ui>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 19aa589f9..ecebb0fb7 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -130,8 +130,7 @@ void ConfigureSystem::ApplyConfiguration() {
130 // Guard if during game and set to game-specific value 130 // Guard if during game and set to game-specific value
131 if (Settings::values.rng_seed.UsingGlobal()) { 131 if (Settings::values.rng_seed.UsingGlobal()) {
132 if (ui->rng_seed_checkbox->isChecked()) { 132 if (ui->rng_seed_checkbox->isChecked()) {
133 Settings::values.rng_seed.SetValue( 133 Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
134 ui->rng_seed_edit->text().toULongLong(nullptr, 16));
135 } else { 134 } else {
136 Settings::values.rng_seed.SetValue(std::nullopt); 135 Settings::values.rng_seed.SetValue(std::nullopt);
137 } 136 }
@@ -142,8 +141,7 @@ void ConfigureSystem::ApplyConfiguration() {
142 case ConfigurationShared::CheckState::Off: 141 case ConfigurationShared::CheckState::Off:
143 Settings::values.rng_seed.SetGlobal(false); 142 Settings::values.rng_seed.SetGlobal(false);
144 if (ui->rng_seed_checkbox->isChecked()) { 143 if (ui->rng_seed_checkbox->isChecked()) {
145 Settings::values.rng_seed.SetValue( 144 Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
146 ui->rng_seed_edit->text().toULongLong(nullptr, 16));
147 } else { 145 } else {
148 Settings::values.rng_seed.SetValue(std::nullopt); 146 Settings::values.rng_seed.SetValue(std::nullopt);
149 } 147 }
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 4a6d74a7e..6321afc83 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -483,7 +483,7 @@ void GameList::DonePopulating(const QStringList& watch_list) {
483 // Also artificially caps the watcher to a certain number of directories 483 // Also artificially caps the watcher to a certain number of directories
484 constexpr int LIMIT_WATCH_DIRECTORIES = 5000; 484 constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
485 constexpr int SLICE_SIZE = 25; 485 constexpr int SLICE_SIZE = 25;
486 int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES); 486 int len = std::min(static_cast<int>(watch_list.size()), LIMIT_WATCH_DIRECTORIES);
487 for (int i = 0; i < len; i += SLICE_SIZE) { 487 for (int i = 0; i < len; i += SLICE_SIZE) {
488 watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE)); 488 watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
489 QCoreApplication::processEvents(); 489 QCoreApplication::processEvents();
@@ -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/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index edfb946a8..e273744fd 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -183,7 +183,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
183 183
184void LoadingScreen::paintEvent(QPaintEvent* event) { 184void LoadingScreen::paintEvent(QPaintEvent* event) {
185 QStyleOption opt; 185 QStyleOption opt;
186 opt.init(this); 186 opt.initFrom(this);
187 QPainter p(this); 187 QPainter p(this);
188 style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); 188 style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
189 QWidget::paintEvent(event); 189 QWidget::paintEvent(event);
diff --git a/src/yuzu/loading_screen.h b/src/yuzu/loading_screen.h
index 7c960ee72..17045595d 100644
--- a/src/yuzu/loading_screen.h
+++ b/src/yuzu/loading_screen.h
@@ -7,6 +7,7 @@
7#include <memory> 7#include <memory>
8#include <QString> 8#include <QString>
9#include <QWidget> 9#include <QWidget>
10#include <QtGlobal>
10 11
11#if !QT_CONFIG(movie) 12#if !QT_CONFIG(movie)
12#define YUZU_QT_MOVIE_MISSING 1 13#define YUZU_QT_MOVIE_MISSING 1
@@ -88,4 +89,6 @@ private:
88 std::size_t slow_shader_first_value = 0; 89 std::size_t slow_shader_first_value = 0;
89}; 90};
90 91
92#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
91Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage); 93Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage);
94#endif
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index c9288b4fe..33886e50e 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"
@@ -198,6 +199,34 @@ static void RemoveCachedContents() {
198 Common::FS::RemoveDirRecursively(offline_system_data); 199 Common::FS::RemoveDirRecursively(offline_system_data);
199} 200}
200 201
202static void LogRuntimes() {
203#ifdef _MSC_VER
204 // It is possible that the name of the dll will change.
205 // vcruntime140.dll is for 2015 and onwards
206 constexpr char runtime_dll_name[] = "vcruntime140.dll";
207 UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
208 bool runtime_version_inspection_worked = false;
209 if (sz > 0) {
210 std::vector<u8> buf(sz);
211 if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
212 VS_FIXEDFILEINFO* pvi;
213 sz = sizeof(VS_FIXEDFILEINFO);
214 if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
215 if (pvi->dwSignature == VS_FFI_SIGNATURE) {
216 runtime_version_inspection_worked = true;
217 LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
218 pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
219 pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
220 }
221 }
222 }
223 }
224 if (!runtime_version_inspection_worked) {
225 LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
226 }
227#endif
228}
229
201static QString PrettyProductName() { 230static QString PrettyProductName() {
202#ifdef _WIN32 231#ifdef _WIN32
203 // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 232 // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
@@ -268,6 +297,7 @@ GMainWindow::GMainWindow()
268 const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; 297 const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
269 298
270 LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version); 299 LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version);
300 LogRuntimes();
271#ifdef ARCHITECTURE_x86_64 301#ifdef ARCHITECTURE_x86_64
272 const auto& caps = Common::GetCPUCaps(); 302 const auto& caps = Common::GetCPUCaps();
273 std::string cpu_string = caps.cpu_string; 303 std::string cpu_string = caps.cpu_string;
@@ -322,6 +352,23 @@ GMainWindow::GMainWindow()
322 352
323 MigrateConfigFiles(); 353 MigrateConfigFiles();
324 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
325#if defined(HAVE_SDL2) && !defined(_WIN32) 372#if defined(HAVE_SDL2) && !defined(_WIN32)
326 SDL_InitSubSystem(SDL_INIT_VIDEO); 373 SDL_InitSubSystem(SDL_INIT_VIDEO);
327 // SDL disables the screen saver by default, and setting the hint 374 // SDL disables the screen saver by default, and setting the hint
@@ -852,12 +899,11 @@ void GMainWindow::InitializeWidgets() {
852 899
853 // Setup Dock button 900 // Setup Dock button
854 dock_status_button = new QPushButton(); 901 dock_status_button = new QPushButton();
855 dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); 902 dock_status_button->setObjectName(QStringLiteral("DockingStatusBarButton"));
856 dock_status_button->setFocusPolicy(Qt::NoFocus); 903 dock_status_button->setFocusPolicy(Qt::NoFocus);
857 connect(dock_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleDockedMode); 904 connect(dock_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleDockedMode);
858 dock_status_button->setText(tr("DOCK"));
859 dock_status_button->setCheckable(true); 905 dock_status_button->setCheckable(true);
860 dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); 906 UpdateDockedButton();
861 statusBar()->insertPermanentWidget(0, dock_status_button); 907 statusBar()->insertPermanentWidget(0, dock_status_button);
862 908
863 gpu_accuracy_button = new QPushButton(); 909 gpu_accuracy_button = new QPushButton();
@@ -1027,7 +1073,7 @@ void GMainWindow::InitializeHotkeys() {
1027 1073
1028void GMainWindow::SetDefaultUIGeometry() { 1074void GMainWindow::SetDefaultUIGeometry() {
1029 // 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
1030 const QRect screenRect = QApplication::desktop()->screenGeometry(this); 1076 const QRect screenRect = QGuiApplication::primaryScreen()->geometry();
1031 1077
1032 const int w = screenRect.width() * 2 / 3; 1078 const int w = screenRect.width() * 2 / 3;
1033 const int h = screenRect.height() * 2 / 3; 1079 const int h = screenRect.height() * 2 / 3;
@@ -1593,7 +1639,7 @@ void GMainWindow::ShutdownGame() {
1593 emu_speed_label->setVisible(false); 1639 emu_speed_label->setVisible(false);
1594 game_fps_label->setVisible(false); 1640 game_fps_label->setVisible(false);
1595 emu_frametime_label->setVisible(false); 1641 emu_frametime_label->setVisible(false);
1596 renderer_status_button->setEnabled(true); 1642 renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
1597 1643
1598 game_path.clear(); 1644 game_path.clear();
1599 1645
@@ -1613,7 +1659,7 @@ void GMainWindow::StoreRecentFile(const QString& filename) {
1613 1659
1614void GMainWindow::UpdateRecentFiles() { 1660void GMainWindow::UpdateRecentFiles() {
1615 const int num_recent_files = 1661 const int num_recent_files =
1616 std::min(UISettings::values.recent_files.size(), max_recent_files_item); 1662 std::min(static_cast<int>(UISettings::values.recent_files.size()), max_recent_files_item);
1617 1663
1618 for (int i = 0; i < num_recent_files; i++) { 1664 for (int i = 0; i < num_recent_files; i++) {
1619 const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg( 1665 const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
@@ -2611,6 +2657,18 @@ void GMainWindow::ToggleFullscreen() {
2611 } 2657 }
2612} 2658}
2613 2659
2660// We're going to return the screen that the given window has the most pixels on
2661static QScreen* GuessCurrentScreen(QWidget* window) {
2662 const QList<QScreen*> screens = QGuiApplication::screens();
2663 return *std::max_element(
2664 screens.cbegin(), screens.cend(), [window](const QScreen* left, const QScreen* right) {
2665 const QSize left_size = left->geometry().intersected(window->geometry()).size();
2666 const QSize right_size = right->geometry().intersected(window->geometry()).size();
2667 return (left_size.height() * left_size.width()) <
2668 (right_size.height() * right_size.width());
2669 });
2670}
2671
2614void GMainWindow::ShowFullscreen() { 2672void GMainWindow::ShowFullscreen() {
2615 const auto show_fullscreen = [](QWidget* window) { 2673 const auto show_fullscreen = [](QWidget* window) {
2616 if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) { 2674 if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
@@ -2619,7 +2677,7 @@ void GMainWindow::ShowFullscreen() {
2619 } 2677 }
2620 window->hide(); 2678 window->hide();
2621 window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint); 2679 window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint);
2622 const auto screen_geometry = QApplication::desktop()->screenGeometry(window); 2680 const auto screen_geometry = GuessCurrentScreen(window)->geometry();
2623 window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(), 2681 window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(),
2624 screen_geometry.height() + 1); 2682 screen_geometry.height() + 1);
2625 window->raise(); 2683 window->raise();
@@ -2803,6 +2861,10 @@ void GMainWindow::OnConfigure() {
2803 mouse_hide_timer.start(); 2861 mouse_hide_timer.start();
2804 } 2862 }
2805 2863
2864 if (!UISettings::values.has_broken_vulkan) {
2865 renderer_status_button->setEnabled(!emulation_running);
2866 }
2867
2806 UpdateStatusButtons(); 2868 UpdateStatusButtons();
2807 controller_dialog->refreshConfiguration(); 2869 controller_dialog->refreshConfiguration();
2808} 2870}
@@ -2888,7 +2950,7 @@ void GMainWindow::OnToggleDockedMode() {
2888 } 2950 }
2889 2951
2890 Settings::values.use_docked_mode.SetValue(!is_docked); 2952 Settings::values.use_docked_mode.SetValue(!is_docked);
2891 dock_status_button->setChecked(!is_docked); 2953 UpdateDockedButton();
2892 OnDockedModeChanged(is_docked, !is_docked, *system); 2954 OnDockedModeChanged(is_docked, !is_docked, *system);
2893} 2955}
2894 2956
@@ -3254,6 +3316,12 @@ void GMainWindow::UpdateGPUAccuracyButton() {
3254 } 3316 }
3255} 3317}
3256 3318
3319void GMainWindow::UpdateDockedButton() {
3320 const bool is_docked = Settings::values.use_docked_mode.GetValue();
3321 dock_status_button->setChecked(is_docked);
3322 dock_status_button->setText(is_docked ? tr("DOCKED") : tr("HANDHELD"));
3323}
3324
3257void GMainWindow::UpdateFilterText() { 3325void GMainWindow::UpdateFilterText() {
3258 const auto filter = Settings::values.scaling_filter.GetValue(); 3326 const auto filter = Settings::values.scaling_filter.GetValue();
3259 switch (filter) { 3327 switch (filter) {
@@ -3297,10 +3365,10 @@ void GMainWindow::UpdateAAText() {
3297} 3365}
3298 3366
3299void GMainWindow::UpdateStatusButtons() { 3367void GMainWindow::UpdateStatusButtons() {
3300 dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
3301 renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == 3368 renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
3302 Settings::RendererBackend::Vulkan); 3369 Settings::RendererBackend::Vulkan);
3303 UpdateGPUAccuracyButton(); 3370 UpdateGPUAccuracyButton();
3371 UpdateDockedButton();
3304 UpdateFilterText(); 3372 UpdateFilterText();
3305 UpdateAAText(); 3373 UpdateAAText();
3306} 3374}
@@ -3351,7 +3419,7 @@ void GMainWindow::CenterMouseCursor() {
3351 const int center_x = render_window->width() / 2; 3419 const int center_x = render_window->width() / 2;
3352 const int center_y = render_window->height() / 2; 3420 const int center_y = render_window->height() / 2;
3353 3421
3354 QCursor::setPos(mapToGlobal({center_x, center_y})); 3422 QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
3355} 3423}
3356 3424
3357void GMainWindow::OnMouseActivity() { 3425void GMainWindow::OnMouseActivity() {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index b399e9b01..600647015 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -320,6 +320,7 @@ private:
320 void MigrateConfigFiles(); 320 void MigrateConfigFiles();
321 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, 321 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
322 std::string_view gpu_vendor = {}); 322 std::string_view gpu_vendor = {});
323 void UpdateDockedButton();
323 void UpdateFilterText(); 324 void UpdateFilterText();
324 void UpdateAAText(); 325 void UpdateAAText();
325 void UpdateStatusBar(); 326 void UpdateStatusBar();
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 d5281863f..a3b8432f5 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.