summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-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 @@
+// SPDX-FileCopyrightText: Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
+// SPDX-License-Identifier: MIT
+#pragma once
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4324)
+#endif
+
+#include <atomic>
+#include <bit>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <stdexcept>
+#include <stop_token>
+#include <type_traits>
+#include <utility>
+
+namespace Common {
+namespace mpsc {
+#if defined(__cpp_lib_hardware_interference_size)
+constexpr size_t hardware_interference_size = std::hardware_destructive_interference_size;
+#else
+constexpr size_t hardware_interference_size = 64;
+#endif
+
+template <typename T>
+using AlignedAllocator = std::allocator<T>;
+
+template <typename T>
+struct Slot {
+ ~Slot() noexcept {
+ if (turn.test()) {
+ destroy();
+ }
+ }
+
+ template <typename... Args>
+ void construct(Args&&... args) noexcept {
+ static_assert(std::is_nothrow_constructible_v<T, Args&&...>,
+ "T must be nothrow constructible with Args&&...");
+ std::construct_at(reinterpret_cast<T*>(&storage), std::forward<Args>(args)...);
+ }
+
+ void destroy() noexcept {
+ static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible");
+ std::destroy_at(reinterpret_cast<T*>(&storage));
+ }
+
+ T&& move() noexcept {
+ return reinterpret_cast<T&&>(storage);
+ }
+
+ // Align to avoid false sharing between adjacent slots
+ alignas(hardware_interference_size) std::atomic_flag turn{};
+ struct aligned_store {
+ struct type {
+ alignas(T) unsigned char data[sizeof(T)];
+ };
+ };
+ typename aligned_store::type storage;
+};
+
+template <typename T, typename Allocator = AlignedAllocator<Slot<T>>>
+class Queue {
+public:
+ explicit Queue(const size_t capacity, const Allocator& allocator = Allocator())
+ : allocator_(allocator) {
+ if (capacity < 1) {
+ throw std::invalid_argument("capacity < 1");
+ }
+ // Ensure that the queue length is an integer power of 2
+ // This is so that idx(i) can be a simple i & mask_ insted of i % capacity
+ // https://github.com/rigtorp/MPMCQueue/pull/36
+ if (!std::has_single_bit(capacity)) {
+ throw std::invalid_argument("capacity must be an integer power of 2");
+ }
+
+ mask_ = capacity - 1;
+
+ // Allocate one extra slot to prevent false sharing on the last slot
+ slots_ = allocator_.allocate(mask_ + 2);
+ // Allocators are not required to honor alignment for over-aligned types
+ // (see http://eel.is/c++draft/allocator.requirements#10) so we verify
+ // alignment here
+ if (reinterpret_cast<uintptr_t>(slots_) % alignof(Slot<T>) != 0) {
+ allocator_.deallocate(slots_, mask_ + 2);
+ throw std::bad_alloc();
+ }
+ for (size_t i = 0; i < mask_ + 1; ++i) {
+ std::construct_at(&slots_[i]);
+ }
+ static_assert(alignof(Slot<T>) == hardware_interference_size,
+ "Slot must be aligned to cache line boundary to prevent false sharing");
+ static_assert(sizeof(Slot<T>) % hardware_interference_size == 0,
+ "Slot size must be a multiple of cache line size to prevent "
+ "false sharing between adjacent slots");
+ static_assert(sizeof(Queue) % hardware_interference_size == 0,
+ "Queue size must be a multiple of cache line size to "
+ "prevent false sharing between adjacent queues");
+ }
+
+ ~Queue() noexcept {
+ for (size_t i = 0; i < mask_ + 1; ++i) {
+ slots_[i].~Slot();
+ }
+ allocator_.deallocate(slots_, mask_ + 2);
+ }
+
+ // non-copyable and non-movable
+ Queue(const Queue&) = delete;
+ Queue& operator=(const Queue&) = delete;
+
+ void Push(const T& v) noexcept {
+ static_assert(std::is_nothrow_copy_constructible_v<T>,
+ "T must be nothrow copy constructible");
+ emplace(v);
+ }
+
+ template <typename P, typename = std::enable_if_t<std::is_nothrow_constructible_v<T, P&&>>>
+ void Push(P&& v) noexcept {
+ emplace(std::forward<P>(v));
+ }
+
+ void Pop(T& v, std::stop_token stop) noexcept {
+ auto const tail = tail_.fetch_add(1);
+ auto& slot = slots_[idx(tail)];
+ if (false == slot.turn.test()) {
+ std::unique_lock lock{cv_mutex};
+ cv.wait(lock, stop, [&slot] { return slot.turn.test(); });
+ }
+ v = slot.move();
+ slot.destroy();
+ slot.turn.clear();
+ slot.turn.notify_one();
+ }
+
+private:
+ template <typename... Args>
+ void emplace(Args&&... args) noexcept {
+ static_assert(std::is_nothrow_constructible_v<T, Args&&...>,
+ "T must be nothrow constructible with Args&&...");
+ auto const head = head_.fetch_add(1);
+ auto& slot = slots_[idx(head)];
+ slot.turn.wait(true);
+ slot.construct(std::forward<Args>(args)...);
+ slot.turn.test_and_set();
+ cv.notify_one();
+ }
+
+ constexpr size_t idx(size_t i) const noexcept {
+ return i & mask_;
+ }
+
+ std::conditional_t<true, std::condition_variable_any, std::condition_variable> cv;
+ std::mutex cv_mutex;
+ size_t mask_;
+ Slot<T>* slots_;
+ [[no_unique_address]] Allocator allocator_;
+
+ // Align to avoid false sharing between head_ and tail_
+ alignas(hardware_interference_size) std::atomic<size_t> head_{0};
+ alignas(hardware_interference_size) std::atomic<size_t> tail_{0};
+
+ static_assert(std::is_nothrow_copy_assignable_v<T> || std::is_nothrow_move_assignable_v<T>,
+ "T must be nothrow copy or move assignable");
+
+ static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible");
+};
+} // namespace mpsc
+
+template <typename T, typename Allocator = mpsc::AlignedAllocator<mpsc::Slot<T>>>
+using MPSCQueue = mpsc::Queue<T, Allocator>;
+
+} // namespace Common
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#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 {
// Renderer
RangedSetting<RendererBackend> renderer_backend{
- RendererBackend::OpenGL, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"};
+ RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"};
BasicSetting<bool> renderer_debug{false, "debug"};
BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"};
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,
VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
while (!stop_token.stop_requested()) {
- CommandDataContainer next = state.queue.PopWait(stop_token);
+ CommandDataContainer next;
+ state.queue.Pop(next, stop_token);
if (stop_token.stop_requested()) {
break;
}
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 @@
#include <thread>
#include <variant>
-#include "common/threadsafe_queue.h"
+#include "common/bounded_threadsafe_queue.h"
#include "video_core/framebuffer_config.h"
namespace Tegra {
@@ -96,9 +96,9 @@ struct CommandDataContainer {
/// Struct used to synchronize the GPU thread
struct SynchState final {
- using CommandQueue = Common::SPSCQueue<CommandDataContainer, true>;
+ using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
std::mutex write_lock;
- CommandQueue queue;
+ CommandQueue queue{512}; // size must be 2^n
u64 last_fence{};
std::atomic<u64> signaled_fence{};
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 @@
#include "common/dynamic_library.h"
#include "common/fs/path_util.h"
+#include "common/logging/log.h"
#include "video_core/vulkan_common/vulkan_library.h"
namespace Vulkan {
Common::DynamicLibrary OpenLibrary() {
+ LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library");
Common::DynamicLibrary library;
#ifdef __APPLE__
// Check if a path to a specific Vulkan library has been specified.
@@ -22,9 +24,11 @@ Common::DynamicLibrary OpenLibrary() {
}
#else
std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
+ LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename);
if (!library.Open(filename.c_str())) {
// Android devices may not have libvulkan.so.1, only libvulkan.so.
filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
+ LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename);
void(library.Open(filename.c_str()));
}
#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
applets/qt_web_browser_scripts.h
bootmanager.cpp
bootmanager.h
+ check_vulkan.cpp
+ check_vulkan.h
compatdb.ui
compatibility_list.cpp
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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+#include <filesystem>
+#include <fstream>
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "video_core/vulkan_common/vulkan_instance.h"
+#include "video_core/vulkan_common/vulkan_library.h"
+#include "yuzu/check_vulkan.h"
+#include "yuzu/uisettings.h"
+
+constexpr char TEMP_FILE_NAME[] = "vulkan_check";
+
+bool CheckVulkan() {
+ if (UISettings::values.has_broken_vulkan) {
+ return true;
+ }
+
+ LOG_DEBUG(Frontend, "Checking presence of Vulkan");
+
+ const auto fs_config_loc = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir);
+ const auto temp_file_loc = fs_config_loc / TEMP_FILE_NAME;
+
+ if (std::filesystem::exists(temp_file_loc)) {
+ LOG_WARNING(Frontend, "Detected recovery from previous failed Vulkan initialization");
+
+ UISettings::values.has_broken_vulkan = true;
+ std::filesystem::remove(temp_file_loc);
+ return false;
+ }
+
+ std::ofstream temp_file_handle(temp_file_loc);
+ temp_file_handle.close();
+
+ try {
+ Vulkan::vk::InstanceDispatch dld;
+ const Common::DynamicLibrary library = Vulkan::OpenLibrary();
+ const Vulkan::vk::Instance instance =
+ Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0);
+
+ } catch (const Vulkan::vk::Exception& exception) {
+ LOG_ERROR(Frontend, "Failed to initialize Vulkan: {}", exception.what());
+ // Don't set has_broken_vulkan to true here: we care when loading Vulkan crashes the
+ // application, not when we can handle it.
+ }
+
+ std::filesystem::remove(temp_file_loc);
+ return true;
+}
diff --git a/src/yuzu/check_vulkan.h b/src/yuzu/check_vulkan.h
new file mode 100644
index 000000000..e4ea93582
--- /dev/null
+++ b/src/yuzu/check_vulkan.h
@@ -0,0 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+bool CheckVulkan();
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index fffbfab02..9df4752be 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -682,6 +682,12 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
+ if (!global && UISettings::values.has_broken_vulkan &&
+ Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan &&
+ !Settings::values.renderer_backend.UsingGlobal()) {
+ Settings::values.renderer_backend.SetGlobal(true);
+ }
+
if (global) {
ReadBasicSetting(Settings::values.renderer_debug);
ReadBasicSetting(Settings::values.renderer_shader_feedback);
@@ -801,6 +807,7 @@ void Config::ReadUIValues() {
ReadBasicSetting(UISettings::values.pause_when_in_background);
ReadBasicSetting(UISettings::values.mute_when_in_background);
ReadBasicSetting(UISettings::values.hide_mouse);
+ ReadBasicSetting(UISettings::values.has_broken_vulkan);
ReadBasicSetting(UISettings::values.disable_web_applet);
qt_config->endGroup();
@@ -1348,6 +1355,7 @@ void Config::SaveUIValues() {
WriteBasicSetting(UISettings::values.pause_when_in_background);
WriteBasicSetting(UISettings::values.mute_when_in_background);
WriteBasicSetting(UISettings::values.hide_mouse);
+ WriteBasicSetting(UISettings::values.has_broken_vulkan);
WriteBasicSetting(UISettings::values.disable_web_applet);
qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 2f1435b10..85f34dc35 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -17,6 +17,7 @@
#include "video_core/vulkan_common/vulkan_library.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
+#include "yuzu/uisettings.h"
ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent)
: QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} {
@@ -57,6 +58,24 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
UpdateBackgroundColorButton(new_bg_color);
});
+ connect(ui->button_check_vulkan, &QAbstractButton::clicked, this, [this] {
+ UISettings::values.has_broken_vulkan = false;
+
+ if (RetrieveVulkanDevices()) {
+ ui->api->setEnabled(true);
+ ui->button_check_vulkan->hide();
+
+ for (const auto& device : vulkan_devices) {
+ ui->device->addItem(device);
+ }
+ } else {
+ UISettings::values.has_broken_vulkan = true;
+ }
+ });
+
+ ui->api->setEnabled(!UISettings::values.has_broken_vulkan.GetValue());
+ ui->button_check_vulkan->setVisible(UISettings::values.has_broken_vulkan.GetValue());
+
ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
}
@@ -296,7 +315,7 @@ void ConfigureGraphics::UpdateAPILayout() {
vulkan_device = Settings::values.vulkan_device.GetValue(true);
shader_backend = Settings::values.shader_backend.GetValue(true);
ui->device_widget->setEnabled(false);
- ui->backend_widget->setEnabled(false);
+ ui->backend_widget->setEnabled(UISettings::values.has_broken_vulkan.GetValue());
} else {
vulkan_device = Settings::values.vulkan_device.GetValue();
shader_backend = Settings::values.shader_backend.GetValue();
@@ -318,7 +337,11 @@ void ConfigureGraphics::UpdateAPILayout() {
}
}
-void ConfigureGraphics::RetrieveVulkanDevices() try {
+bool ConfigureGraphics::RetrieveVulkanDevices() try {
+ if (UISettings::values.has_broken_vulkan) {
+ return false;
+ }
+
using namespace Vulkan;
vk::InstanceDispatch dld;
@@ -333,8 +356,10 @@ void ConfigureGraphics::RetrieveVulkanDevices() try {
vulkan_devices.push_back(QString::fromStdString(name));
}
+ return true;
} catch (const Vulkan::vk::Exception& exception) {
LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
+ return false;
}
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
@@ -415,4 +440,11 @@ void ConfigureGraphics::SetupPerGameUI() {
ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
ConfigurationShared::InsertGlobalItem(
ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
+
+ if (UISettings::values.has_broken_vulkan) {
+ ui->backend_widget->setEnabled(true);
+ ConfigurationShared::SetColoredComboBox(
+ ui->backend, ui->backend_widget,
+ static_cast<int>(Settings::values.shader_backend.GetValue(true)));
+ }
}
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:
void UpdateDeviceSelection(int device);
void UpdateShaderBackendSelection(int backend);
- void RetrieveVulkanDevices();
+ bool RetrieveVulkanDevices();
void SetupPerGameUI();
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 @@
<rect>
<x>0</x>
<y>0</y>
- <width>437</width>
- <height>482</height>
+ <width>471</width>
+ <height>759</height>
</rect>
</property>
<property name="windowTitle">
@@ -171,11 +171,11 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="accelerate_astc">
- <property name="text">
- <string>Accelerate ASTC texture decoding</string>
- </property>
- </widget>
+ <widget class="QCheckBox" name="accelerate_astc">
+ <property name="text">
+ <string>Accelerate ASTC texture decoding</string>
+ </property>
+ </widget>
</item>
<item>
<widget class="QWidget" name="nvdec_emulation_widget" native="true">
@@ -438,43 +438,43 @@
</widget>
</item>
<item>
- <widget class="QWidget" name="anti_aliasing_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
+ <widget class="QWidget" name="anti_aliasing_layout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="anti_aliasing_label">
+ <property name="text">
+ <string>Anti-Aliasing Method:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="anti_aliasing_combobox">
+ <item>
+ <property name="text">
+ <string>None</string>
</property>
- <property name="bottomMargin">
- <number>0</number>
+ </item>
+ <item>
+ <property name="text">
+ <string>FXAA</string>
</property>
- <item>
- <widget class="QLabel" name="anti_aliasing_label">
- <property name="text">
- <string>Anti-Aliasing Method:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="anti_aliasing_combobox">
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>FXAA</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
<widget class="QWidget" name="bg_layout" native="true">
@@ -574,6 +574,13 @@
</property>
</spacer>
</item>
+ <item>
+ <widget class="QPushButton" name="button_check_vulkan">
+ <property name="text">
+ <string>Check for Working Vulkan</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<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}
layout->setAlignment(Qt::AlignCenter);
image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
- text->setText(tr("Double-click to add a new folder to the game list"));
+ RetranslateUI();
QFont font = text->font();
font.setPointSize(20);
text->setFont(font);
@@ -891,3 +891,15 @@ void GameListPlaceholder::onUpdateThemedIcons() {
void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
emit GameListPlaceholder::AddDirectory();
}
+
+void GameListPlaceholder::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void GameListPlaceholder::RetranslateUI() {
+ text->setText(tr("Double-click to add a new folder to the game list"));
+}
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:
void mouseDoubleClickEvent(QMouseEvent* event) override;
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
QVBoxLayout* layout = nullptr;
QLabel* image = nullptr;
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
#define QT_NO_OPENGL
#include <QClipboard>
#include <QDesktopServices>
-#include <QDesktopWidget>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
@@ -60,6 +59,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QProgressBar>
#include <QProgressDialog>
#include <QPushButton>
+#include <QScreen>
#include <QShortcut>
#include <QStatusBar>
#include <QString>
@@ -115,6 +115,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "video_core/shader_notify.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
+#include "yuzu/check_vulkan.h"
#include "yuzu/compatdb.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/configuration/config.h"
@@ -351,6 +352,23 @@ GMainWindow::GMainWindow()
MigrateConfigFiles();
+ if (!CheckVulkan()) {
+ config->Save();
+
+ QMessageBox::warning(
+ this, tr("Broken Vulkan Installation Detected"),
+ tr("Vulkan initialization failed on the previous boot.<br><br>Click <a "
+ "href='https://yuzu-emu.org/wiki/faq/"
+ "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>here for "
+ "instructions to fix the issue</a>."));
+ }
+ if (UISettings::values.has_broken_vulkan) {
+ Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
+
+ renderer_status_button->setDisabled(true);
+ renderer_status_button->setChecked(false);
+ }
+
#if defined(HAVE_SDL2) && !defined(_WIN32)
SDL_InitSubSystem(SDL_INIT_VIDEO);
// SDL disables the screen saver by default, and setting the hint
@@ -1055,7 +1073,7 @@ void GMainWindow::InitializeHotkeys() {
void GMainWindow::SetDefaultUIGeometry() {
// geometry: 53% of the window contents are in the upper screen half, 47% in the lower half
- const QRect screenRect = QApplication::desktop()->screenGeometry(this);
+ const QRect screenRect = QGuiApplication::primaryScreen()->geometry();
const int w = screenRect.width() * 2 / 3;
const int h = screenRect.height() * 2 / 3;
@@ -1620,7 +1638,7 @@ void GMainWindow::ShutdownGame() {
emu_speed_label->setVisible(false);
game_fps_label->setVisible(false);
emu_frametime_label->setVisible(false);
- renderer_status_button->setEnabled(true);
+ renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
game_path.clear();
@@ -2638,6 +2656,18 @@ void GMainWindow::ToggleFullscreen() {
}
}
+// We're going to return the screen that the given window has the most pixels on
+static QScreen* GuessCurrentScreen(QWidget* window) {
+ const QList<QScreen*> screens = QGuiApplication::screens();
+ return *std::max_element(
+ screens.cbegin(), screens.cend(), [window](const QScreen* left, const QScreen* right) {
+ const QSize left_size = left->geometry().intersected(window->geometry()).size();
+ const QSize right_size = right->geometry().intersected(window->geometry()).size();
+ return (left_size.height() * left_size.width()) <
+ (right_size.height() * right_size.width());
+ });
+}
+
void GMainWindow::ShowFullscreen() {
const auto show_fullscreen = [](QWidget* window) {
if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
@@ -2646,7 +2676,7 @@ void GMainWindow::ShowFullscreen() {
}
window->hide();
window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint);
- const auto screen_geometry = QApplication::desktop()->screenGeometry(window);
+ const auto screen_geometry = GuessCurrentScreen(window)->geometry();
window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(),
screen_geometry.height() + 1);
window->raise();
@@ -2830,6 +2860,10 @@ void GMainWindow::OnConfigure() {
mouse_hide_timer.start();
}
+ if (!UISettings::values.has_broken_vulkan) {
+ renderer_status_button->setEnabled(!emulation_running);
+ }
+
UpdateStatusButtons();
controller_dialog->refreshConfiguration();
}
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 {
Settings::BasicSetting<bool> pause_when_in_background{false, "pauseWhenInBackground"};
Settings::BasicSetting<bool> mute_when_in_background{false, "muteWhenInBackground"};
Settings::BasicSetting<bool> hide_mouse{true, "hideInactiveMouse"};
+ // Set when Vulkan is known to crash the application
+ Settings::BasicSetting<bool> has_broken_vulkan{false, "has_broken_vulkan"};
Settings::BasicSetting<bool> select_user_on_boot{false, "select_user_on_boot"};
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 =
[Renderer]
# Which backend API to use.
-# 0 (default): OpenGL, 1: Vulkan
+# 0: OpenGL, 1 (default): Vulkan
backend =
# Enable graphics API debugging mode.