summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar ReinUsesLisp2020-01-21 16:40:53 -0300
committerGravatar ReinUsesLisp2020-01-29 17:53:11 -0300
commitf92cbc55018b5a3d98dd2093354f20c62ace5fda (patch)
tree7ec9af0d8820b45de580c2d608fdb1853485db83
parentweb_service/telemetry_json: Report USER_CONFIG (diff)
downloadyuzu-f92cbc55018b5a3d98dd2093354f20c62ace5fda.tar.gz
yuzu-f92cbc55018b5a3d98dd2093354f20c62ace5fda.tar.xz
yuzu-f92cbc55018b5a3d98dd2093354f20c62ace5fda.zip
yuzu: Implement Vulkan frontend
Adds a Qt and SDL2 frontend for Vulkan. It also finishes the missing bits on Vulkan initialization.
-rw-r--r--src/core/frontend/emu_window.h7
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp265
-rw-r--r--src/video_core/video_core.cpp15
-rw-r--r--src/yuzu/CMakeLists.txt5
-rw-r--r--src/yuzu/bootmanager.cpp282
-rw-r--r--src/yuzu/bootmanager.h30
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui116
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp94
-rw-r--r--src/yuzu/configuration/configure_graphics.h12
-rw-r--r--src/yuzu/configuration/configure_graphics.ui72
-rw-r--r--src/yuzu/main.cpp88
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu_cmd/CMakeLists.txt11
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp4
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h3
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp7
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h4
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp161
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h39
-rw-r--r--src/yuzu_cmd/yuzu.cpp18
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp15
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h7
24 files changed, 1089 insertions, 171 deletions
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 4a9912641..3376eedc5 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -75,6 +75,13 @@ public:
75 return nullptr; 75 return nullptr;
76 } 76 }
77 77
78 /// Returns if window is shown (not minimized)
79 virtual bool IsShown() const = 0;
80
81 /// Retrieves Vulkan specific handlers from the window
82 virtual void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
83 void* surface) const = 0;
84
78 /** 85 /**
79 * Signal that a touch pressed event has occurred (e.g. mouse click pressed) 86 * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
80 * @param framebuffer_x Framebuffer x-coordinate that was pressed 87 * @param framebuffer_x Framebuffer x-coordinate that was pressed
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index ccfed4f2e..8218b7cd2 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -154,6 +154,7 @@ if (ENABLE_VULKAN)
154 renderer_vulkan/maxwell_to_vk.cpp 154 renderer_vulkan/maxwell_to_vk.cpp
155 renderer_vulkan/maxwell_to_vk.h 155 renderer_vulkan/maxwell_to_vk.h
156 renderer_vulkan/renderer_vulkan.h 156 renderer_vulkan/renderer_vulkan.h
157 renderer_vulkan/renderer_vulkan.cpp
157 renderer_vulkan/vk_blit_screen.cpp 158 renderer_vulkan/vk_blit_screen.cpp
158 renderer_vulkan/vk_blit_screen.h 159 renderer_vulkan/vk_blit_screen.h
159 renderer_vulkan/vk_buffer_cache.cpp 160 renderer_vulkan/vk_buffer_cache.cpp
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
new file mode 100644
index 000000000..d5032b432
--- /dev/null
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -0,0 +1,265 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <memory>
6#include <optional>
7#include <vector>
8
9#include <fmt/format.h>
10
11#include "common/assert.h"
12#include "common/logging/log.h"
13#include "common/telemetry.h"
14#include "core/core.h"
15#include "core/core_timing.h"
16#include "core/frontend/emu_window.h"
17#include "core/memory.h"
18#include "core/perf_stats.h"
19#include "core/settings.h"
20#include "core/telemetry_session.h"
21#include "video_core/gpu.h"
22#include "video_core/renderer_vulkan/declarations.h"
23#include "video_core/renderer_vulkan/renderer_vulkan.h"
24#include "video_core/renderer_vulkan/vk_blit_screen.h"
25#include "video_core/renderer_vulkan/vk_device.h"
26#include "video_core/renderer_vulkan/vk_memory_manager.h"
27#include "video_core/renderer_vulkan/vk_rasterizer.h"
28#include "video_core/renderer_vulkan/vk_resource_manager.h"
29#include "video_core/renderer_vulkan/vk_scheduler.h"
30#include "video_core/renderer_vulkan/vk_swapchain.h"
31
32namespace Vulkan {
33
34namespace {
35
36VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity_,
37 VkDebugUtilsMessageTypeFlagsEXT type,
38 const VkDebugUtilsMessengerCallbackDataEXT* data,
39 [[maybe_unused]] void* user_data) {
40 const vk::DebugUtilsMessageSeverityFlagBitsEXT severity{severity_};
41 const char* message{data->pMessage};
42
43 if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
44 LOG_CRITICAL(Render_Vulkan, "{}", message);
45 } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
46 LOG_WARNING(Render_Vulkan, "{}", message);
47 } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
48 LOG_INFO(Render_Vulkan, "{}", message);
49 } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
50 LOG_DEBUG(Render_Vulkan, "{}", message);
51 }
52 return VK_FALSE;
53}
54
55std::string GetReadableVersion(u32 version) {
56 return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
57 VK_VERSION_PATCH(version));
58}
59
60std::string GetDriverVersion(const VKDevice& device) {
61 // Extracted from
62 // https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314
63 const u32 version = device.GetDriverVersion();
64
65 if (device.GetDriverID() == vk::DriverIdKHR::eNvidiaProprietary) {
66 const u32 major = (version >> 22) & 0x3ff;
67 const u32 minor = (version >> 14) & 0x0ff;
68 const u32 secondary = (version >> 6) & 0x0ff;
69 const u32 tertiary = version & 0x003f;
70 return fmt::format("{}.{}.{}.{}", major, minor, secondary, tertiary);
71 }
72 if (device.GetDriverID() == vk::DriverIdKHR::eIntelProprietaryWindows) {
73 const u32 major = version >> 14;
74 const u32 minor = version & 0x3fff;
75 return fmt::format("{}.{}", major, minor);
76 }
77
78 return GetReadableVersion(version);
79}
80
81std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_extensions) {
82 std::sort(std::begin(available_extensions), std::end(available_extensions));
83
84 static constexpr std::size_t AverageExtensionSize = 64;
85 std::string separated_extensions;
86 separated_extensions.reserve(available_extensions.size() * AverageExtensionSize);
87
88 const auto end = std::end(available_extensions);
89 for (auto extension = std::begin(available_extensions); extension != end; ++extension) {
90 if (const bool is_last = extension + 1 == end; is_last) {
91 separated_extensions += *extension;
92 } else {
93 separated_extensions += fmt::format("{},", *extension);
94 }
95 }
96 return separated_extensions;
97}
98
99} // Anonymous namespace
100
101RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system)
102 : RendererBase(window), system{system} {}
103
104RendererVulkan::~RendererVulkan() {
105 ShutDown();
106}
107
108void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
109 const auto& layout = render_window.GetFramebufferLayout();
110 if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) {
111 const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
112 const bool use_accelerated =
113 rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
114 const bool is_srgb = use_accelerated && screen_info.is_srgb;
115 if (swapchain->HasFramebufferChanged(layout) || swapchain->GetSrgbState() != is_srgb) {
116 swapchain->Create(layout.width, layout.height, is_srgb);
117 blit_screen->Recreate();
118 }
119
120 scheduler->WaitWorker();
121
122 swapchain->AcquireNextImage();
123 const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated);
124
125 scheduler->Flush(false, render_semaphore);
126
127 if (swapchain->Present(render_semaphore, fence)) {
128 blit_screen->Recreate();
129 }
130
131 render_window.SwapBuffers();
132 rasterizer->TickFrame();
133 }
134
135 render_window.PollEvents();
136}
137
138bool RendererVulkan::Init() {
139 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
140 render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface);
141 const vk::DispatchLoaderDynamic dldi(instance, vkGetInstanceProcAddr);
142
143 std::optional<vk::DebugUtilsMessengerEXT> callback;
144 if (Settings::values.renderer_debug && dldi.vkCreateDebugUtilsMessengerEXT) {
145 callback = CreateDebugCallback(dldi);
146 if (!callback) {
147 return false;
148 }
149 }
150
151 if (!PickDevices(dldi)) {
152 if (callback) {
153 instance.destroy(*callback, nullptr, dldi);
154 }
155 return false;
156 }
157 debug_callback = UniqueDebugUtilsMessengerEXT(
158 *callback, vk::ObjectDestroy<vk::Instance, vk::DispatchLoaderDynamic>(
159 instance, nullptr, device->GetDispatchLoader()));
160
161 Report();
162
163 memory_manager = std::make_unique<VKMemoryManager>(*device);
164
165 resource_manager = std::make_unique<VKResourceManager>(*device);
166
167 const auto& framebuffer = render_window.GetFramebufferLayout();
168 swapchain = std::make_unique<VKSwapchain>(surface, *device);
169 swapchain->Create(framebuffer.width, framebuffer.height, false);
170
171 scheduler = std::make_unique<VKScheduler>(*device, *resource_manager);
172
173 rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device,
174 *resource_manager, *memory_manager, *scheduler);
175
176 blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device,
177 *resource_manager, *memory_manager, *swapchain,
178 *scheduler, screen_info);
179
180 return true;
181}
182
183void RendererVulkan::ShutDown() {
184 if (!device) {
185 return;
186 }
187 const auto dev = device->GetLogical();
188 const auto& dld = device->GetDispatchLoader();
189 if (dev && dld.vkDeviceWaitIdle) {
190 dev.waitIdle(dld);
191 }
192
193 rasterizer.reset();
194 blit_screen.reset();
195 scheduler.reset();
196 swapchain.reset();
197 memory_manager.reset();
198 resource_manager.reset();
199 device.reset();
200}
201
202std::optional<vk::DebugUtilsMessengerEXT> RendererVulkan::CreateDebugCallback(
203 const vk::DispatchLoaderDynamic& dldi) {
204 const vk::DebugUtilsMessengerCreateInfoEXT callback_ci(
205 {},
206 vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
207 vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
208 vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
209 vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose,
210 vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
211 vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
212 vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
213 &DebugCallback, nullptr);
214 vk::DebugUtilsMessengerEXT callback;
215 if (instance.createDebugUtilsMessengerEXT(&callback_ci, nullptr, &callback, dldi) !=
216 vk::Result::eSuccess) {
217 LOG_ERROR(Render_Vulkan, "Failed to create debug callback");
218 return {};
219 }
220 return callback;
221}
222
223bool RendererVulkan::PickDevices(const vk::DispatchLoaderDynamic& dldi) {
224 const auto devices = instance.enumeratePhysicalDevices(dldi);
225
226 // TODO(Rodrigo): Choose device from config file
227 const s32 device_index = Settings::values.vulkan_device;
228 if (device_index < 0 || device_index >= static_cast<s32>(devices.size())) {
229 LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
230 return false;
231 }
232 const vk::PhysicalDevice physical_device = devices[device_index];
233
234 if (!VKDevice::IsSuitable(dldi, physical_device, surface)) {
235 return false;
236 }
237
238 device = std::make_unique<VKDevice>(dldi, physical_device, surface);
239 return device->Create(dldi, instance);
240}
241
242void RendererVulkan::Report() const {
243 const std::string vendor_name{device->GetVendorName()};
244 const std::string model_name{device->GetModelName()};
245 const std::string driver_version = GetDriverVersion(*device);
246 const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
247
248 const std::string api_version = GetReadableVersion(device->GetApiVersion());
249
250 const std::string extensions = BuildCommaSeparatedExtensions(device->GetAvailableExtensions());
251
252 LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);
253 LOG_INFO(Render_Vulkan, "Device: {}", model_name);
254 LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
255
256 auto& telemetry_session = system.TelemetrySession();
257 constexpr auto field = Telemetry::FieldType::UserSystem;
258 telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
259 telemetry_session.AddField(field, "GPU_Model", model_name);
260 telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
261 telemetry_session.AddField(field, "GPU_Vulkan_Version", api_version);
262 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
263}
264
265} // namespace Vulkan \ No newline at end of file
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 8e947394c..a5f81a8a0 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -3,19 +3,32 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <memory> 5#include <memory>
6#include "common/logging/log.h"
6#include "core/core.h" 7#include "core/core.h"
7#include "core/settings.h" 8#include "core/settings.h"
8#include "video_core/gpu_asynch.h" 9#include "video_core/gpu_asynch.h"
9#include "video_core/gpu_synch.h" 10#include "video_core/gpu_synch.h"
10#include "video_core/renderer_base.h" 11#include "video_core/renderer_base.h"
11#include "video_core/renderer_opengl/renderer_opengl.h" 12#include "video_core/renderer_opengl/renderer_opengl.h"
13#ifdef HAS_VULKAN
14#include "video_core/renderer_vulkan/renderer_vulkan.h"
15#endif
12#include "video_core/video_core.h" 16#include "video_core/video_core.h"
13 17
14namespace VideoCore { 18namespace VideoCore {
15 19
16std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window, 20std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
17 Core::System& system) { 21 Core::System& system) {
18 return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system); 22 switch (Settings::values.renderer_backend) {
23 case Settings::RendererBackend::OpenGL:
24 return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system);
25#ifdef HAS_VULKAN
26 case Settings::RendererBackend::Vulkan:
27 return std::make_unique<Vulkan::RendererVulkan>(emu_window, system);
28#endif
29 default:
30 return nullptr;
31 }
19} 32}
20 33
21std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system) { 34std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system) {
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index a3fb91d29..b841e63fa 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -200,3 +200,8 @@ if (MSVC)
200 copy_yuzu_SDL_deps(yuzu) 200 copy_yuzu_SDL_deps(yuzu)
201 copy_yuzu_unicorn_deps(yuzu) 201 copy_yuzu_unicorn_deps(yuzu)
202endif() 202endif()
203
204if (ENABLE_VULKAN)
205 target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
206 target_compile_definitions(yuzu PRIVATE HAS_VULKAN)
207endif()
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 7490fb718..67ca59035 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -2,19 +2,30 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <glad/glad.h>
6
5#include <QApplication> 7#include <QApplication>
6#include <QHBoxLayout> 8#include <QHBoxLayout>
7#include <QKeyEvent> 9#include <QKeyEvent>
10#include <QMessageBox>
8#include <QOffscreenSurface> 11#include <QOffscreenSurface>
9#include <QOpenGLWindow> 12#include <QOpenGLWindow>
10#include <QPainter> 13#include <QPainter>
11#include <QScreen> 14#include <QScreen>
15#include <QStringList>
12#include <QWindow> 16#include <QWindow>
17#ifdef HAS_VULKAN
18#include <QVulkanWindow>
19#endif
20
13#include <fmt/format.h> 21#include <fmt/format.h>
22
23#include "common/assert.h"
14#include "common/microprofile.h" 24#include "common/microprofile.h"
15#include "common/scm_rev.h" 25#include "common/scm_rev.h"
16#include "core/core.h" 26#include "core/core.h"
17#include "core/frontend/framebuffer_layout.h" 27#include "core/frontend/framebuffer_layout.h"
28#include "core/frontend/scope_acquire_window_context.h"
18#include "core/settings.h" 29#include "core/settings.h"
19#include "input_common/keyboard.h" 30#include "input_common/keyboard.h"
20#include "input_common/main.h" 31#include "input_common/main.h"
@@ -114,19 +125,10 @@ private:
114 QOpenGLContext context; 125 QOpenGLContext context;
115}; 126};
116 127
117// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL 128class GWidgetInternal : public QWindow {
118// context.
119// The corresponding functionality is handled in EmuThread instead
120class GGLWidgetInternal : public QOpenGLWindow {
121public: 129public:
122 GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) 130 GWidgetInternal(GRenderWindow* parent) : parent(parent) {}
123 : QOpenGLWindow(shared_context), parent(parent) {} 131 virtual ~GWidgetInternal() = default;
124
125 void paintEvent(QPaintEvent* ev) override {
126 if (do_painting) {
127 QPainter painter(this);
128 }
129 }
130 132
131 void resizeEvent(QResizeEvent* ev) override { 133 void resizeEvent(QResizeEvent* ev) override {
132 parent->OnClientAreaResized(ev->size().width(), ev->size().height()); 134 parent->OnClientAreaResized(ev->size().width(), ev->size().height());
@@ -182,9 +184,43 @@ public:
182 do_painting = true; 184 do_painting = true;
183 } 185 }
184 186
187 std::pair<unsigned, unsigned> GetSize() const {
188 return std::make_pair(width(), height());
189 }
190
191protected:
192 bool IsPaintingEnabled() const {
193 return do_painting;
194 }
195
185private: 196private:
186 GRenderWindow* parent; 197 GRenderWindow* parent;
187 bool do_painting; 198 bool do_painting = false;
199};
200
201// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
202// context.
203// The corresponding functionality is handled in EmuThread instead
204class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow {
205public:
206 GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
207 : GWidgetInternal(parent), QOpenGLWindow(shared_context) {}
208 ~GGLWidgetInternal() override = default;
209
210 void paintEvent(QPaintEvent* ev) override {
211 if (IsPaintingEnabled()) {
212 QPainter painter(this);
213 }
214 }
215};
216
217class GVKWidgetInternal final : public GWidgetInternal {
218public:
219 GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) {
220 setSurfaceType(QSurface::SurfaceType::VulkanSurface);
221 setVulkanInstance(instance);
222 }
223 ~GVKWidgetInternal() override = default;
188}; 224};
189 225
190GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) 226GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
@@ -201,9 +237,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
201 237
202GRenderWindow::~GRenderWindow() { 238GRenderWindow::~GRenderWindow() {
203 InputCommon::Shutdown(); 239 InputCommon::Shutdown();
240
241 // Avoid an unordered destruction that generates a segfault
242 delete child;
204} 243}
205 244
206void GRenderWindow::moveContext() { 245void GRenderWindow::moveContext() {
246 if (!context) {
247 return;
248 }
207 DoneCurrent(); 249 DoneCurrent();
208 250
209 // If the thread started running, move the GL Context to the new thread. Otherwise, move it 251 // If the thread started running, move the GL Context to the new thread. Otherwise, move it
@@ -215,8 +257,9 @@ void GRenderWindow::moveContext() {
215} 257}
216 258
217void GRenderWindow::SwapBuffers() { 259void GRenderWindow::SwapBuffers() {
218 context->swapBuffers(child); 260 if (context) {
219 261 context->swapBuffers(child);
262 }
220 if (!first_frame) { 263 if (!first_frame) {
221 first_frame = true; 264 first_frame = true;
222 emit FirstFrameDisplayed(); 265 emit FirstFrameDisplayed();
@@ -224,15 +267,38 @@ void GRenderWindow::SwapBuffers() {
224} 267}
225 268
226void GRenderWindow::MakeCurrent() { 269void GRenderWindow::MakeCurrent() {
227 context->makeCurrent(child); 270 if (context) {
271 context->makeCurrent(child);
272 }
228} 273}
229 274
230void GRenderWindow::DoneCurrent() { 275void GRenderWindow::DoneCurrent() {
231 context->doneCurrent(); 276 if (context) {
277 context->doneCurrent();
278 }
232} 279}
233 280
234void GRenderWindow::PollEvents() {} 281void GRenderWindow::PollEvents() {}
235 282
283bool GRenderWindow::IsShown() const {
284 return !isMinimized();
285}
286
287void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
288 void* surface) const {
289#ifdef HAS_VULKAN
290 const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
291 const VkInstance instance_copy = vk_instance->vkInstance();
292 const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child);
293
294 std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
295 std::memcpy(instance, &instance_copy, sizeof(instance_copy));
296 std::memcpy(surface, &surface_copy, sizeof(surface_copy));
297#else
298 UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
299#endif
300}
301
236// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). 302// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
237// 303//
238// Older versions get the window size (density independent pixels), 304// Older versions get the window size (density independent pixels),
@@ -241,10 +307,9 @@ void GRenderWindow::PollEvents() {}
241void GRenderWindow::OnFramebufferSizeChanged() { 307void GRenderWindow::OnFramebufferSizeChanged() {
242 // Screen changes potentially incur a change in screen DPI, hence we should update the 308 // Screen changes potentially incur a change in screen DPI, hence we should update the
243 // framebuffer size 309 // framebuffer size
244 const qreal pixel_ratio = GetWindowPixelRatio(); 310 const qreal pixelRatio{GetWindowPixelRatio()};
245 const u32 width = child->QPaintDevice::width() * pixel_ratio; 311 const auto size{child->GetSize()};
246 const u32 height = child->QPaintDevice::height() * pixel_ratio; 312 UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio);
247 UpdateCurrentFramebufferLayout(width, height);
248} 313}
249 314
250void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { 315void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
@@ -290,7 +355,7 @@ qreal GRenderWindow::GetWindowPixelRatio() const {
290} 355}
291 356
292std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { 357std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
293 const qreal pixel_ratio = GetWindowPixelRatio(); 358 const qreal pixel_ratio{GetWindowPixelRatio()};
294 return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), 359 return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
295 static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; 360 static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
296} 361}
@@ -356,50 +421,46 @@ std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedCont
356 return std::make_unique<GGLContext>(context.get()); 421 return std::make_unique<GGLContext>(context.get());
357} 422}
358 423
359void GRenderWindow::InitRenderTarget() { 424bool GRenderWindow::InitRenderTarget() {
360 shared_context.reset(); 425 shared_context.reset();
361 context.reset(); 426 context.reset();
362 427 if (child) {
363 delete child; 428 delete child;
364 child = nullptr; 429 }
365 430 if (container) {
366 delete container; 431 delete container;
367 container = nullptr; 432 }
368 433 if (layout()) {
369 delete layout(); 434 delete layout();
435 }
370 436
371 first_frame = false; 437 first_frame = false;
372 438
373 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, 439 switch (Settings::values.renderer_backend) {
374 // WA_DontShowOnScreen, WA_DeleteOnClose 440 case Settings::RendererBackend::OpenGL:
375 QSurfaceFormat fmt; 441 if (!InitializeOpenGL()) {
376 fmt.setVersion(4, 3); 442 return false;
377 fmt.setProfile(QSurfaceFormat::CompatibilityProfile); 443 }
378 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); 444 break;
379 // TODO: expose a setting for buffer value (ie default/single/double/triple) 445 case Settings::RendererBackend::Vulkan:
380 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); 446 if (!InitializeVulkan()) {
381 shared_context = std::make_unique<QOpenGLContext>(); 447 return false;
382 shared_context->setFormat(fmt); 448 }
383 shared_context->create(); 449 break;
384 context = std::make_unique<QOpenGLContext>(); 450 }
385 context->setShareContext(shared_context.get());
386 context->setFormat(fmt);
387 context->create();
388 fmt.setSwapInterval(0);
389 451
390 child = new GGLWidgetInternal(this, shared_context.get());
391 container = QWidget::createWindowContainer(child, this); 452 container = QWidget::createWindowContainer(child, this);
392
393 QBoxLayout* layout = new QHBoxLayout(this); 453 QBoxLayout* layout = new QHBoxLayout(this);
454
394 layout->addWidget(container); 455 layout->addWidget(container);
395 layout->setMargin(0); 456 layout->setMargin(0);
396 setLayout(layout); 457 setLayout(layout);
397 458
398 // Reset minimum size to avoid unwanted resizes when this function is called for a second time. 459 // Reset minimum required size to avoid resizing issues on the main window after restarting.
399 setMinimumSize(1, 1); 460 setMinimumSize(1, 1);
400 461
401 // Show causes the window to actually be created and the OpenGL context as well, but we don't 462 // Show causes the window to actually be created and the gl context as well, but we don't want
402 // want the widget to be shown yet, so immediately hide it. 463 // the widget to be shown yet, so immediately hide it.
403 show(); 464 show();
404 hide(); 465 hide();
405 466
@@ -410,9 +471,17 @@ void GRenderWindow::InitRenderTarget() {
410 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); 471 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
411 472
412 OnFramebufferSizeChanged(); 473 OnFramebufferSizeChanged();
413 NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height())); 474 NotifyClientAreaSizeChanged(child->GetSize());
414 475
415 BackupGeometry(); 476 BackupGeometry();
477
478 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
479 if (!LoadOpenGL()) {
480 return false;
481 }
482 }
483
484 return true;
416} 485}
417 486
418void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { 487void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
@@ -441,6 +510,113 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
441 setMinimumSize(minimal_size.first, minimal_size.second); 510 setMinimumSize(minimal_size.first, minimal_size.second);
442} 511}
443 512
513bool GRenderWindow::InitializeOpenGL() {
514 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
515 // WA_DontShowOnScreen, WA_DeleteOnClose
516 QSurfaceFormat fmt;
517 fmt.setVersion(4, 3);
518 fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
519 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
520 // TODO: expose a setting for buffer value (ie default/single/double/triple)
521 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
522 shared_context = std::make_unique<QOpenGLContext>();
523 shared_context->setFormat(fmt);
524 shared_context->create();
525 context = std::make_unique<QOpenGLContext>();
526 context->setShareContext(shared_context.get());
527 context->setFormat(fmt);
528 context->create();
529 fmt.setSwapInterval(false);
530
531 child = new GGLWidgetInternal(this, shared_context.get());
532 return true;
533}
534
535bool GRenderWindow::InitializeVulkan() {
536#ifdef HAS_VULKAN
537 vk_instance = std::make_unique<QVulkanInstance>();
538 vk_instance->setApiVersion(QVersionNumber(1, 1, 0));
539 vk_instance->setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect);
540 if (Settings::values.renderer_debug) {
541 const auto supported_layers{vk_instance->supportedLayers()};
542 const bool found =
543 std::find_if(supported_layers.begin(), supported_layers.end(), [](const auto& layer) {
544 constexpr const char searched_layer[] = "VK_LAYER_LUNARG_standard_validation";
545 return layer.name == searched_layer;
546 });
547 if (found) {
548 vk_instance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
549 vk_instance->setExtensions(QByteArrayList() << VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
550 }
551 }
552 if (!vk_instance->create()) {
553 QMessageBox::critical(
554 this, tr("Error while initializing Vulkan 1.1!"),
555 tr("Your OS doesn't seem to support Vulkan 1.1 instances, or you do not have the "
556 "latest graphics drivers."));
557 return false;
558 }
559
560 child = new GVKWidgetInternal(this, vk_instance.get());
561 return true;
562#else
563 QMessageBox::critical(this, tr("Vulkan not available!"),
564 tr("yuzu has not been compiled with Vulkan support."));
565 return false;
566#endif
567}
568
569bool GRenderWindow::LoadOpenGL() {
570 Core::Frontend::ScopeAcquireWindowContext acquire_context{*this};
571 if (!gladLoadGL()) {
572 QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
573 tr("Your GPU may not support OpenGL 4.3, or you do not have the "
574 "latest graphics driver."));
575 return false;
576 }
577
578 QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
579 if (!unsupported_gl_extensions.empty()) {
580 QMessageBox::critical(
581 this, tr("Error while initializing OpenGL!"),
582 tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
583 "have the latest graphics driver.<br><br>Unsupported extensions:<br>") +
584 unsupported_gl_extensions.join(QStringLiteral("<br>")));
585 return false;
586 }
587 return true;
588}
589
590QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
591 QStringList unsupported_ext;
592
593 if (!GLAD_GL_ARB_buffer_storage)
594 unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
595 if (!GLAD_GL_ARB_direct_state_access)
596 unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
597 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
598 unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
599 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
600 unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
601 if (!GLAD_GL_ARB_multi_bind)
602 unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
603 if (!GLAD_GL_ARB_clip_control)
604 unsupported_ext.append(QStringLiteral("ARB_clip_control"));
605
606 // Extensions required to support some texture formats.
607 if (!GLAD_GL_EXT_texture_compression_s3tc)
608 unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
609 if (!GLAD_GL_ARB_texture_compression_rgtc)
610 unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
611 if (!GLAD_GL_ARB_depth_buffer_float)
612 unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
613
614 for (const QString& ext : unsupported_ext)
615 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
616
617 return unsupported_ext;
618}
619
444void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { 620void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
445 this->emu_thread = emu_thread; 621 this->emu_thread = emu_thread;
446 child->DisablePainting(); 622 child->DisablePainting();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 2fc64895f..71a2fa321 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -7,17 +7,28 @@
7#include <atomic> 7#include <atomic>
8#include <condition_variable> 8#include <condition_variable>
9#include <mutex> 9#include <mutex>
10
10#include <QImage> 11#include <QImage>
11#include <QThread> 12#include <QThread>
12#include <QWidget> 13#include <QWidget>
14
15#include "common/thread.h"
13#include "core/core.h" 16#include "core/core.h"
14#include "core/frontend/emu_window.h" 17#include "core/frontend/emu_window.h"
15 18
16class QKeyEvent; 19class QKeyEvent;
17class QScreen; 20class QScreen;
18class QTouchEvent; 21class QTouchEvent;
22class QStringList;
23class QSurface;
24class QOpenGLContext;
25#ifdef HAS_VULKAN
26class QVulkanInstance;
27#endif
19 28
29class GWidgetInternal;
20class GGLWidgetInternal; 30class GGLWidgetInternal;
31class GVKWidgetInternal;
21class GMainWindow; 32class GMainWindow;
22class GRenderWindow; 33class GRenderWindow;
23class QSurface; 34class QSurface;
@@ -123,6 +134,9 @@ public:
123 void MakeCurrent() override; 134 void MakeCurrent() override;
124 void DoneCurrent() override; 135 void DoneCurrent() override;
125 void PollEvents() override; 136 void PollEvents() override;
137 bool IsShown() const override;
138 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
139 void* surface) const override;
126 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 140 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
127 141
128 void ForwardKeyPressEvent(QKeyEvent* event); 142 void ForwardKeyPressEvent(QKeyEvent* event);
@@ -142,7 +156,7 @@ public:
142 156
143 void OnClientAreaResized(u32 width, u32 height); 157 void OnClientAreaResized(u32 width, u32 height);
144 158
145 void InitRenderTarget(); 159 bool InitRenderTarget();
146 160
147 void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); 161 void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
148 162
@@ -165,10 +179,13 @@ private:
165 179
166 void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; 180 void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
167 181
168 QWidget* container = nullptr; 182 bool InitializeOpenGL();
169 GGLWidgetInternal* child = nullptr; 183 bool InitializeVulkan();
184 bool LoadOpenGL();
185 QStringList GetUnsupportedGLExtensions() const;
170 186
171 QByteArray geometry; 187 QWidget* container = nullptr;
188 GWidgetInternal* child = nullptr;
172 189
173 EmuThread* emu_thread; 190 EmuThread* emu_thread;
174 // Context that backs the GGLWidgetInternal (and will be used by core to render) 191 // Context that backs the GGLWidgetInternal (and will be used by core to render)
@@ -177,9 +194,14 @@ private:
177 // current 194 // current
178 std::unique_ptr<QOpenGLContext> shared_context; 195 std::unique_ptr<QOpenGLContext> shared_context;
179 196
197#ifdef HAS_VULKAN
198 std::unique_ptr<QVulkanInstance> vk_instance;
199#endif
200
180 /// Temporary storage of the screenshot taken 201 /// Temporary storage of the screenshot taken
181 QImage screenshot_image; 202 QImage screenshot_image;
182 203
204 QByteArray geometry;
183 bool first_frame = false; 205 bool first_frame = false;
184 206
185protected: 207protected:
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 90c1f9459..9631059c7 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -36,6 +36,8 @@ void ConfigureDebug::SetConfiguration() {
36 ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); 36 ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
37 ui->reporting_services->setChecked(Settings::values.reporting_services); 37 ui->reporting_services->setChecked(Settings::values.reporting_services);
38 ui->quest_flag->setChecked(Settings::values.quest_flag); 38 ui->quest_flag->setChecked(Settings::values.quest_flag);
39 ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn());
40 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug);
39} 41}
40 42
41void ConfigureDebug::ApplyConfiguration() { 43void ConfigureDebug::ApplyConfiguration() {
@@ -46,6 +48,7 @@ void ConfigureDebug::ApplyConfiguration() {
46 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); 48 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
47 Settings::values.reporting_services = ui->reporting_services->isChecked(); 49 Settings::values.reporting_services = ui->reporting_services->isChecked();
48 Settings::values.quest_flag = ui->quest_flag->isChecked(); 50 Settings::values.quest_flag = ui->quest_flag->isChecked();
51 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
49 Debugger::ToggleConsole(); 52 Debugger::ToggleConsole();
50 Log::Filter filter; 53 Log::Filter filter;
51 filter.ParseFilterString(Settings::values.log_filter); 54 filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index ce49569bb..e028c4c80 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -7,7 +7,7 @@
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>400</width> 9 <width>400</width>
10 <height>474</height> 10 <height>467</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -103,6 +103,80 @@
103 </item> 103 </item>
104 </layout> 104 </layout>
105 </item> 105 </item>
106 </layout>
107 </widget>
108 </item>
109 <item>
110 <widget class="QGroupBox" name="groupBox_3">
111 <property name="title">
112 <string>Homebrew</string>
113 </property>
114 <layout class="QVBoxLayout" name="verticalLayout_5">
115 <item>
116 <layout class="QHBoxLayout" name="horizontalLayout_4">
117 <item>
118 <widget class="QLabel" name="label_3">
119 <property name="text">
120 <string>Arguments String</string>
121 </property>
122 </widget>
123 </item>
124 <item>
125 <widget class="QLineEdit" name="homebrew_args_edit"/>
126 </item>
127 </layout>
128 </item>
129 </layout>
130 </widget>
131 </item>
132 <item>
133 <widget class="QGroupBox" name="groupBox_4">
134 <property name="title">
135 <string>Graphics</string>
136 </property>
137 <layout class="QVBoxLayout" name="verticalLayout_6">
138 <item>
139 <widget class="QCheckBox" name="enable_graphics_debugging">
140 <property name="enabled">
141 <bool>true</bool>
142 </property>
143 <property name="whatsThis">
144 <string>When checked, the graphics API enters in a slower debugging mode</string>
145 </property>
146 <property name="text">
147 <string>Enable Graphics Debugging</string>
148 </property>
149 </widget>
150 </item>
151 </layout>
152 </widget>
153 </item>
154 <item>
155 <widget class="QGroupBox" name="groupBox_5">
156 <property name="title">
157 <string>Dump</string>
158 </property>
159 <layout class="QVBoxLayout" name="verticalLayout_6">
160 <item>
161 <widget class="QCheckBox" name="dump_decompressed_nso">
162 <property name="whatsThis">
163 <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
164 </property>
165 <property name="text">
166 <string>Dump Decompressed NSOs</string>
167 </property>
168 </widget>
169 </item>
170 <item>
171 <widget class="QCheckBox" name="dump_exefs">
172 <property name="whatsThis">
173 <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
174 </property>
175 <property name="text">
176 <string>Dump ExeFS</string>
177 </property>
178 </widget>
179 </item>
106 <item> 180 <item>
107 <widget class="QCheckBox" name="reporting_services"> 181 <widget class="QCheckBox" name="reporting_services">
108 <property name="text"> 182 <property name="text">
@@ -129,11 +203,11 @@
129 </widget> 203 </widget>
130 </item> 204 </item>
131 <item> 205 <item>
132 <widget class="QGroupBox" name="groupBox_5"> 206 <widget class="QGroupBox" name="groupBox_6">
133 <property name="title"> 207 <property name="title">
134 <string>Advanced</string> 208 <string>Advanced</string>
135 </property> 209 </property>
136 <layout class="QVBoxLayout" name="verticalLayout"> 210 <layout class="QVBoxLayout" name="verticalLayout_7">
137 <item> 211 <item>
138 <widget class="QCheckBox" name="quest_flag"> 212 <widget class="QCheckBox" name="quest_flag">
139 <property name="text"> 213 <property name="text">
@@ -145,29 +219,6 @@
145 </widget> 219 </widget>
146 </item> 220 </item>
147 <item> 221 <item>
148 <widget class="QGroupBox" name="groupBox_3">
149 <property name="title">
150 <string>Homebrew</string>
151 </property>
152 <layout class="QVBoxLayout" name="verticalLayout_5">
153 <item>
154 <layout class="QHBoxLayout" name="horizontalLayout_4">
155 <item>
156 <widget class="QLabel" name="label_3">
157 <property name="text">
158 <string>Arguments String</string>
159 </property>
160 </widget>
161 </item>
162 <item>
163 <widget class="QLineEdit" name="homebrew_args_edit"/>
164 </item>
165 </layout>
166 </item>
167 </layout>
168 </widget>
169 </item>
170 <item>
171 <spacer name="verticalSpacer"> 222 <spacer name="verticalSpacer">
172 <property name="orientation"> 223 <property name="orientation">
173 <enum>Qt::Vertical</enum> 224 <enum>Qt::Vertical</enum>
@@ -185,6 +236,19 @@
185 </item> 236 </item>
186 </layout> 237 </layout>
187 </widget> 238 </widget>
239 <tabstops>
240 <tabstop>toggle_gdbstub</tabstop>
241 <tabstop>gdbport_spinbox</tabstop>
242 <tabstop>log_filter_edit</tabstop>
243 <tabstop>toggle_console</tabstop>
244 <tabstop>open_log_button</tabstop>
245 <tabstop>homebrew_args_edit</tabstop>
246 <tabstop>enable_graphics_debugging</tabstop>
247 <tabstop>dump_decompressed_nso</tabstop>
248 <tabstop>dump_exefs</tabstop>
249 <tabstop>reporting_services</tabstop>
250 <tabstop>quest_flag</tabstop>
251 </tabstops>
188 <resources/> 252 <resources/>
189 <connections> 253 <connections>
190 <connection> 254 <connection>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 2c9e322c9..f57a24e36 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -3,6 +3,13 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <QColorDialog> 5#include <QColorDialog>
6#include <QComboBox>
7#ifdef HAS_VULKAN
8#include <QVulkanInstance>
9#endif
10
11#include "common/common_types.h"
12#include "common/logging/log.h"
6#include "core/core.h" 13#include "core/core.h"
7#include "core/settings.h" 14#include "core/settings.h"
8#include "ui_configure_graphics.h" 15#include "ui_configure_graphics.h"
@@ -51,10 +58,18 @@ Resolution FromResolutionFactor(float factor) {
51 58
52ConfigureGraphics::ConfigureGraphics(QWidget* parent) 59ConfigureGraphics::ConfigureGraphics(QWidget* parent)
53 : QWidget(parent), ui(new Ui::ConfigureGraphics) { 60 : QWidget(parent), ui(new Ui::ConfigureGraphics) {
61 vulkan_device = Settings::values.vulkan_device;
62 RetrieveVulkanDevices();
63
54 ui->setupUi(this); 64 ui->setupUi(this);
55 65
56 SetConfiguration(); 66 SetConfiguration();
57 67
68 connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
69 [this] { UpdateDeviceComboBox(); });
70 connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
71 [this](int device) { UpdateDeviceSelection(device); });
72
58 connect(ui->bg_button, &QPushButton::clicked, this, [this] { 73 connect(ui->bg_button, &QPushButton::clicked, this, [this] {
59 const QColor new_bg_color = QColorDialog::getColor(bg_color); 74 const QColor new_bg_color = QColorDialog::getColor(bg_color);
60 if (!new_bg_color.isValid()) { 75 if (!new_bg_color.isValid()) {
@@ -64,11 +79,22 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
64 }); 79 });
65} 80}
66 81
82void ConfigureGraphics::UpdateDeviceSelection(int device) {
83 if (device == -1) {
84 return;
85 }
86 if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
87 vulkan_device = device;
88 }
89}
90
67ConfigureGraphics::~ConfigureGraphics() = default; 91ConfigureGraphics::~ConfigureGraphics() = default;
68 92
69void ConfigureGraphics::SetConfiguration() { 93void ConfigureGraphics::SetConfiguration() {
70 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); 94 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
71 95
96 ui->api->setEnabled(runtime_lock);
97 ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend));
72 ui->resolution_factor_combobox->setCurrentIndex( 98 ui->resolution_factor_combobox->setCurrentIndex(
73 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); 99 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
74 ui->use_disk_shader_cache->setEnabled(runtime_lock); 100 ui->use_disk_shader_cache->setEnabled(runtime_lock);
@@ -80,9 +106,12 @@ void ConfigureGraphics::SetConfiguration() {
80 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); 106 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
81 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, 107 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
82 Settings::values.bg_blue)); 108 Settings::values.bg_blue));
109 UpdateDeviceComboBox();
83} 110}
84 111
85void ConfigureGraphics::ApplyConfiguration() { 112void ConfigureGraphics::ApplyConfiguration() {
113 Settings::values.renderer_backend = GetCurrentGraphicsBackend();
114 Settings::values.vulkan_device = vulkan_device;
86 Settings::values.resolution_factor = 115 Settings::values.resolution_factor =
87 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); 116 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
88 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); 117 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
@@ -116,3 +145,68 @@ void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
116 const QIcon color_icon(pixmap); 145 const QIcon color_icon(pixmap);
117 ui->bg_button->setIcon(color_icon); 146 ui->bg_button->setIcon(color_icon);
118} 147}
148
149void ConfigureGraphics::UpdateDeviceComboBox() {
150 ui->device->clear();
151
152 bool enabled = false;
153 switch (GetCurrentGraphicsBackend()) {
154 case Settings::RendererBackend::OpenGL:
155 ui->device->addItem(tr("OpenGL Graphics Device"));
156 enabled = false;
157 break;
158 case Settings::RendererBackend::Vulkan:
159 for (const auto device : vulkan_devices) {
160 ui->device->addItem(device);
161 }
162 ui->device->setCurrentIndex(vulkan_device);
163 enabled = !vulkan_devices.empty();
164 break;
165 }
166 ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());
167}
168
169void ConfigureGraphics::RetrieveVulkanDevices() {
170#ifdef HAS_VULKAN
171 QVulkanInstance instance;
172 instance.setApiVersion(QVersionNumber(1, 1, 0));
173 if (!instance.create()) {
174 LOG_INFO(Frontend, "Vulkan 1.1 not available");
175 return;
176 }
177 const auto vkEnumeratePhysicalDevices{reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
178 instance.getInstanceProcAddr("vkEnumeratePhysicalDevices"))};
179 if (vkEnumeratePhysicalDevices == nullptr) {
180 LOG_INFO(Frontend, "Failed to get pointer to vkEnumeratePhysicalDevices");
181 return;
182 }
183 u32 physical_device_count;
184 if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count, nullptr) !=
185 VK_SUCCESS) {
186 LOG_INFO(Frontend, "Failed to get physical devices count");
187 return;
188 }
189 std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
190 if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count,
191 physical_devices.data()) != VK_SUCCESS) {
192 LOG_INFO(Frontend, "Failed to get physical devices");
193 return;
194 }
195
196 const auto vkGetPhysicalDeviceProperties{reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
197 instance.getInstanceProcAddr("vkGetPhysicalDeviceProperties"))};
198 if (vkGetPhysicalDeviceProperties == nullptr) {
199 LOG_INFO(Frontend, "Failed to get pointer to vkGetPhysicalDeviceProperties");
200 return;
201 }
202 for (const auto physical_device : physical_devices) {
203 VkPhysicalDeviceProperties properties;
204 vkGetPhysicalDeviceProperties(physical_device, &properties);
205 vulkan_devices.push_back(QString::fromUtf8(properties.deviceName));
206 }
207#endif
208}
209
210Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
211 return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
212}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index fae28d98e..7e0596d9c 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -5,7 +5,10 @@
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include <vector>
9#include <QString>
8#include <QWidget> 10#include <QWidget>
11#include "core/settings.h"
9 12
10namespace Ui { 13namespace Ui {
11class ConfigureGraphics; 14class ConfigureGraphics;
@@ -27,7 +30,16 @@ private:
27 void SetConfiguration(); 30 void SetConfiguration();
28 31
29 void UpdateBackgroundColorButton(QColor color); 32 void UpdateBackgroundColorButton(QColor color);
33 void UpdateDeviceComboBox();
34 void UpdateDeviceSelection(int device);
35
36 void RetrieveVulkanDevices();
37
38 Settings::RendererBackend GetCurrentGraphicsBackend() const;
30 39
31 std::unique_ptr<Ui::ConfigureGraphics> ui; 40 std::unique_ptr<Ui::ConfigureGraphics> ui;
32 QColor bg_color; 41 QColor bg_color;
42
43 std::vector<QString> vulkan_devices;
44 u32 vulkan_device{};
33}; 45};
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 0309ee300..e24372204 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -7,21 +7,69 @@
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>400</width> 9 <width>400</width>
10 <height>300</height> 10 <height>321</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
14 <string>Form</string> 14 <string>Form</string>
15 </property> 15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout"> 16 <layout class="QVBoxLayout" name="verticalLayout_1">
17 <item> 17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3"> 18 <layout class="QVBoxLayout" name="verticalLayout_2">
19 <item>
20 <widget class="QGroupBox" name="groupBox_2">
21 <property name="title">
22 <string>API Settings</string>
23 </property>
24 <layout class="QVBoxLayout" name="verticalLayout_3">
25 <item>
26 <layout class="QHBoxLayout" name="horizontalLayout_4">
27 <item>
28 <widget class="QLabel" name="label_2">
29 <property name="text">
30 <string>API:</string>
31 </property>
32 </widget>
33 </item>
34 <item>
35 <widget class="QComboBox" name="api">
36 <item>
37 <property name="text">
38 <string notr="true">OpenGL</string>
39 </property>
40 </item>
41 <item>
42 <property name="text">
43 <string notr="true">Vulkan</string>
44 </property>
45 </item>
46 </widget>
47 </item>
48 </layout>
49 </item>
50 <item>
51 <layout class="QHBoxLayout" name="horizontalLayout_5">
52 <item>
53 <widget class="QLabel" name="label_3">
54 <property name="text">
55 <string>Device:</string>
56 </property>
57 </widget>
58 </item>
59 <item>
60 <widget class="QComboBox" name="device"/>
61 </item>
62 </layout>
63 </item>
64 </layout>
65 </widget>
66 </item>
19 <item> 67 <item>
20 <widget class="QGroupBox" name="groupBox"> 68 <widget class="QGroupBox" name="groupBox">
21 <property name="title"> 69 <property name="title">
22 <string>Graphics</string> 70 <string>Graphics Settings</string>
23 </property> 71 </property>
24 <layout class="QVBoxLayout" name="verticalLayout_2"> 72 <layout class="QVBoxLayout" name="verticalLayout_4">
25 <item> 73 <item>
26 <widget class="QCheckBox" name="use_disk_shader_cache"> 74 <widget class="QCheckBox" name="use_disk_shader_cache">
27 <property name="text"> 75 <property name="text">
@@ -30,16 +78,16 @@
30 </widget> 78 </widget>
31 </item> 79 </item>
32 <item> 80 <item>
33 <widget class="QCheckBox" name="use_accurate_gpu_emulation"> 81 <widget class="QCheckBox" name="use_asynchronous_gpu_emulation">
34 <property name="text"> 82 <property name="text">
35 <string>Use accurate GPU emulation (slow)</string> 83 <string>Use asynchronous GPU emulation</string>
36 </property> 84 </property>
37 </widget> 85 </widget>
38 </item> 86 </item>
39 <item> 87 <item>
40 <widget class="QCheckBox" name="use_asynchronous_gpu_emulation"> 88 <widget class="QCheckBox" name="use_accurate_gpu_emulation">
41 <property name="text"> 89 <property name="text">
42 <string>Use asynchronous GPU emulation</string> 90 <string>Use accurate GPU emulation (slow)</string>
43 </property> 91 </property>
44 </widget> 92 </widget>
45 </item> 93 </item>
@@ -51,11 +99,11 @@
51 </widget> 99 </widget>
52 </item> 100 </item>
53 <item> 101 <item>
54 <layout class="QHBoxLayout" name="horizontalLayout"> 102 <layout class="QHBoxLayout" name="horizontalLayout_2">
55 <item> 103 <item>
56 <widget class="QLabel" name="label"> 104 <widget class="QLabel" name="label">
57 <property name="text"> 105 <property name="text">
58 <string>Internal Resolution</string> 106 <string>Internal Resolution:</string>
59 </property> 107 </property>
60 </widget> 108 </widget>
61 </item> 109 </item>
@@ -91,7 +139,7 @@
91 </layout> 139 </layout>
92 </item> 140 </item>
93 <item> 141 <item>
94 <layout class="QHBoxLayout" name="horizontalLayout_6"> 142 <layout class="QHBoxLayout" name="horizontalLayout_3">
95 <item> 143 <item>
96 <widget class="QLabel" name="bg_label"> 144 <widget class="QLabel" name="bg_label">
97 <property name="text"> 145 <property name="text">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index b5dd3e0d6..4000bf44a 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -806,70 +806,12 @@ void GMainWindow::AllowOSSleep() {
806#endif 806#endif
807} 807}
808 808
809QStringList GMainWindow::GetUnsupportedGLExtensions() {
810 QStringList unsupported_ext;
811
812 if (!GLAD_GL_ARB_buffer_storage) {
813 unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
814 }
815 if (!GLAD_GL_ARB_direct_state_access) {
816 unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
817 }
818 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) {
819 unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
820 }
821 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) {
822 unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
823 }
824 if (!GLAD_GL_ARB_multi_bind) {
825 unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
826 }
827 if (!GLAD_GL_ARB_clip_control) {
828 unsupported_ext.append(QStringLiteral("ARB_clip_control"));
829 }
830
831 // Extensions required to support some texture formats.
832 if (!GLAD_GL_EXT_texture_compression_s3tc) {
833 unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
834 }
835 if (!GLAD_GL_ARB_texture_compression_rgtc) {
836 unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
837 }
838 if (!GLAD_GL_ARB_depth_buffer_float) {
839 unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
840 }
841
842 for (const QString& ext : unsupported_ext) {
843 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
844 }
845
846 return unsupported_ext;
847}
848
849bool GMainWindow::LoadROM(const QString& filename) { 809bool GMainWindow::LoadROM(const QString& filename) {
850 // Shutdown previous session if the emu thread is still active... 810 // Shutdown previous session if the emu thread is still active...
851 if (emu_thread != nullptr) 811 if (emu_thread != nullptr)
852 ShutdownGame(); 812 ShutdownGame();
853 813
854 render_window->InitRenderTarget(); 814 if (!render_window->InitRenderTarget()) {
855
856 {
857 Core::Frontend::ScopeAcquireWindowContext acquire_context{*render_window};
858 if (!gladLoadGL()) {
859 QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3 Core!"),
860 tr("Your GPU may not support OpenGL 4.3, or you do not "
861 "have the latest graphics driver."));
862 return false;
863 }
864 }
865
866 const QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
867 if (!unsupported_gl_extensions.empty()) {
868 QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"),
869 tr("Your GPU may not support one or more required OpenGL"
870 "extensions. Please ensure you have the latest graphics "
871 "driver.<br><br>Unsupported extensions:<br>") +
872 unsupported_gl_extensions.join(QStringLiteral("<br>")));
873 return false; 815 return false;
874 } 816 }
875 817
@@ -980,7 +922,9 @@ void GMainWindow::BootGame(const QString& filename) {
980 // Create and start the emulation thread 922 // Create and start the emulation thread
981 emu_thread = std::make_unique<EmuThread>(render_window); 923 emu_thread = std::make_unique<EmuThread>(render_window);
982 emit EmulationStarting(emu_thread.get()); 924 emit EmulationStarting(emu_thread.get());
983 render_window->moveContext(); 925 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
926 render_window->moveContext();
927 }
984 emu_thread->start(); 928 emu_thread->start();
985 929
986 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); 930 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -2195,6 +2139,18 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
2195 QWidget::closeEvent(event); 2139 QWidget::closeEvent(event);
2196} 2140}
2197 2141
2142void GMainWindow::keyPressEvent(QKeyEvent* event) {
2143 if (render_window) {
2144 render_window->ForwardKeyPressEvent(event);
2145 }
2146}
2147
2148void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
2149 if (render_window) {
2150 render_window->ForwardKeyReleaseEvent(event);
2151 }
2152}
2153
2198static bool IsSingleFileDropEvent(QDropEvent* event) { 2154static bool IsSingleFileDropEvent(QDropEvent* event) {
2199 const QMimeData* mimeData = event->mimeData(); 2155 const QMimeData* mimeData = event->mimeData();
2200 return mimeData->hasUrls() && mimeData->urls().length() == 1; 2156 return mimeData->hasUrls() && mimeData->urls().length() == 1;
@@ -2227,18 +2183,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
2227 event->acceptProposedAction(); 2183 event->acceptProposedAction();
2228} 2184}
2229 2185
2230void GMainWindow::keyPressEvent(QKeyEvent* event) {
2231 if (render_window) {
2232 render_window->ForwardKeyPressEvent(event);
2233 }
2234}
2235
2236void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
2237 if (render_window) {
2238 render_window->ForwardKeyReleaseEvent(event);
2239 }
2240}
2241
2242bool GMainWindow::ConfirmChangeGame() { 2186bool GMainWindow::ConfirmChangeGame() {
2243 if (emu_thread == nullptr) 2187 if (emu_thread == nullptr)
2244 return true; 2188 return true;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index a56f9a981..65d4f50bb 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -130,7 +130,6 @@ private:
130 void PreventOSSleep(); 130 void PreventOSSleep();
131 void AllowOSSleep(); 131 void AllowOSSleep();
132 132
133 QStringList GetUnsupportedGLExtensions();
134 bool LoadROM(const QString& filename); 133 bool LoadROM(const QString& filename);
135 void BootGame(const QString& filename); 134 void BootGame(const QString& filename);
136 void ShutdownGame(); 135 void ShutdownGame();
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index b5f06ab9e..a15719a0f 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -8,11 +8,22 @@ add_executable(yuzu-cmd
8 emu_window/emu_window_sdl2_gl.h 8 emu_window/emu_window_sdl2_gl.h
9 emu_window/emu_window_sdl2.cpp 9 emu_window/emu_window_sdl2.cpp
10 emu_window/emu_window_sdl2.h 10 emu_window/emu_window_sdl2.h
11 emu_window/emu_window_sdl2_gl.cpp
12 emu_window/emu_window_sdl2_gl.h
11 resource.h 13 resource.h
12 yuzu.cpp 14 yuzu.cpp
13 yuzu.rc 15 yuzu.rc
14) 16)
15 17
18if (ENABLE_VULKAN)
19 target_sources(yuzu-cmd PRIVATE
20 emu_window/emu_window_sdl2_vk.cpp
21 emu_window/emu_window_sdl2_vk.h)
22
23 target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)
24 target_compile_definitions(yuzu-cmd PRIVATE HAS_VULKAN)
25endif()
26
16create_target_directory_groups(yuzu-cmd) 27create_target_directory_groups(yuzu-cmd)
17 28
18target_link_libraries(yuzu-cmd PRIVATE common core input_common) 29target_link_libraries(yuzu-cmd PRIVATE common core input_common)
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index b1c512db1..e96139885 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -89,6 +89,10 @@ bool EmuWindow_SDL2::IsOpen() const {
89 return is_open; 89 return is_open;
90} 90}
91 91
92bool EmuWindow_SDL2::IsShown() const {
93 return is_shown;
94}
95
92void EmuWindow_SDL2::OnResize() { 96void EmuWindow_SDL2::OnResize() {
93 int width, height; 97 int width, height;
94 SDL_GetWindowSize(render_window, &width, &height); 98 SDL_GetWindowSize(render_window, &width, &height);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index eaa971f77..b38f56661 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -21,6 +21,9 @@ public:
21 /// Whether the window is still open, and a close request hasn't yet been sent 21 /// Whether the window is still open, and a close request hasn't yet been sent
22 bool IsOpen() const; 22 bool IsOpen() const;
23 23
24 /// Returns if window is shown (not minimized)
25 bool IsShown() const override;
26
24protected: 27protected:
25 /// Called by PollEvents when a key is pressed or released. 28 /// Called by PollEvents when a key is pressed or released.
26 void OnKeyEvent(int key, u8 state); 29 void OnKeyEvent(int key, u8 state);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 6fde694a2..7ffa0ac09 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -9,6 +9,7 @@
9#include <SDL.h> 9#include <SDL.h>
10#include <fmt/format.h> 10#include <fmt/format.h>
11#include <glad/glad.h> 11#include <glad/glad.h>
12#include "common/assert.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "common/scm_rev.h" 14#include "common/scm_rev.h"
14#include "common/string_util.h" 15#include "common/string_util.h"
@@ -151,6 +152,12 @@ void EmuWindow_SDL2_GL::DoneCurrent() {
151 SDL_GL_MakeCurrent(render_window, nullptr); 152 SDL_GL_MakeCurrent(render_window, nullptr);
152} 153}
153 154
155void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
156 void* surface) const {
157 // Should not have been called from OpenGL
158 UNREACHABLE();
159}
160
154std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { 161std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
155 return std::make_unique<SDLGLContext>(); 162 return std::make_unique<SDLGLContext>();
156} 163}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index 630deba93..c753085a8 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -22,6 +22,10 @@ public:
22 /// Releases the GL context from the caller thread 22 /// Releases the GL context from the caller thread
23 void DoneCurrent() override; 23 void DoneCurrent() override;
24 24
25 /// Ignored in OpenGL
26 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
27 void* surface) const override;
28
25 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 29 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
26 30
27private: 31private:
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
new file mode 100644
index 000000000..89e736ef6
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -0,0 +1,161 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <string>
7#include <vector>
8#include <SDL.h>
9#include <SDL_vulkan.h>
10#include <fmt/format.h>
11#include <vulkan/vulkan.h>
12#include "common/assert.h"
13#include "common/logging/log.h"
14#include "common/scm_rev.h"
15#include "core/settings.h"
16#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
17
18EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
19 if (SDL_Vulkan_LoadLibrary(nullptr) != 0) {
20 LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError());
21 exit(EXIT_FAILURE);
22 }
23
24 vkGetInstanceProcAddr =
25 reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
26 if (vkGetInstanceProcAddr == nullptr) {
27 LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
28 exit(EXIT_FAILURE);
29 }
30
31 const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
32 Common::g_scm_branch, Common::g_scm_desc);
33 render_window =
34 SDL_CreateWindow(window_title.c_str(),
35 SDL_WINDOWPOS_UNDEFINED, // x position
36 SDL_WINDOWPOS_UNDEFINED, // y position
37 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
38 SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_VULKAN);
39
40 const bool use_standard_layers = UseStandardLayers(vkGetInstanceProcAddr);
41
42 u32 extra_ext_count{};
43 if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, NULL)) {
44 LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions count from SDL! {}",
45 SDL_GetError());
46 exit(1);
47 }
48
49 auto extra_ext_names = std::make_unique<const char* []>(extra_ext_count);
50 if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, extra_ext_names.get())) {
51 LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions from SDL! {}", SDL_GetError());
52 exit(1);
53 }
54 std::vector<const char*> enabled_extensions;
55 enabled_extensions.insert(enabled_extensions.begin(), extra_ext_names.get(),
56 extra_ext_names.get() + extra_ext_count);
57
58 std::vector<const char*> enabled_layers;
59 if (use_standard_layers) {
60 enabled_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
61 enabled_layers.push_back("VK_LAYER_LUNARG_standard_validation");
62 }
63
64 VkApplicationInfo app_info{};
65 app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
66 app_info.apiVersion = VK_API_VERSION_1_1;
67 app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
68 app_info.pApplicationName = "yuzu-emu";
69 app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
70 app_info.pEngineName = "yuzu-emu";
71
72 VkInstanceCreateInfo instance_ci{};
73 instance_ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
74 instance_ci.pApplicationInfo = &app_info;
75 instance_ci.enabledExtensionCount = static_cast<u32>(enabled_extensions.size());
76 instance_ci.ppEnabledExtensionNames = enabled_extensions.data();
77 if (Settings::values.renderer_debug) {
78 instance_ci.enabledLayerCount = static_cast<u32>(enabled_layers.size());
79 instance_ci.ppEnabledLayerNames = enabled_layers.data();
80 }
81
82 const auto vkCreateInstance =
83 reinterpret_cast<PFN_vkCreateInstance>(vkGetInstanceProcAddr(nullptr, "vkCreateInstance"));
84 if (vkCreateInstance == nullptr ||
85 vkCreateInstance(&instance_ci, nullptr, &instance) != VK_SUCCESS) {
86 LOG_CRITICAL(Frontend, "Failed to create Vulkan instance!");
87 exit(EXIT_FAILURE);
88 }
89
90 vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(
91 vkGetInstanceProcAddr(instance, "vkDestroyInstance"));
92 if (vkDestroyInstance == nullptr) {
93 LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
94 exit(EXIT_FAILURE);
95 }
96
97 if (!SDL_Vulkan_CreateSurface(render_window, instance, &surface)) {
98 LOG_CRITICAL(Frontend, "Failed to create Vulkan surface! {}", SDL_GetError());
99 exit(EXIT_FAILURE);
100 }
101
102 OnResize();
103 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
104 SDL_PumpEvents();
105 LOG_INFO(Frontend, "yuzu Version: {} | {}-{} (Vulkan)", Common::g_build_name,
106 Common::g_scm_branch, Common::g_scm_desc);
107}
108
109EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() {
110 vkDestroyInstance(instance, nullptr);
111}
112
113void EmuWindow_SDL2_VK::SwapBuffers() {}
114
115void EmuWindow_SDL2_VK::MakeCurrent() {
116 // Unused on Vulkan
117}
118
119void EmuWindow_SDL2_VK::DoneCurrent() {
120 // Unused on Vulkan
121}
122
123void EmuWindow_SDL2_VK::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
124 void* surface) const {
125 std::memcpy(get_instance_proc_addr, vkGetInstanceProcAddr, sizeof(vkGetInstanceProcAddr));
126 std::memcpy(instance, &this->instance, sizeof(this->instance));
127 std::memcpy(surface, &this->surface, sizeof(this->surface));
128}
129
130std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
131 return nullptr;
132}
133
134bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const {
135 if (!Settings::values.renderer_debug) {
136 return false;
137 }
138
139 const auto vkEnumerateInstanceLayerProperties =
140 reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
141 vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceLayerProperties"));
142 if (vkEnumerateInstanceLayerProperties == nullptr) {
143 LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
144 return false;
145 }
146
147 u32 available_layers_count{};
148 if (vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr) != VK_SUCCESS) {
149 LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
150 return false;
151 }
152 std::vector<VkLayerProperties> layers(available_layers_count);
153 if (vkEnumerateInstanceLayerProperties(&available_layers_count, layers.data()) != VK_SUCCESS) {
154 LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
155 return false;
156 }
157
158 return std::find_if(layers.begin(), layers.end(), [&](const auto& layer) {
159 return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation");
160 }) != layers.end();
161}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
new file mode 100644
index 000000000..f7234841b
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -0,0 +1,39 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vulkan/vulkan.h>
8#include "core/frontend/emu_window.h"
9#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
10
11class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
12public:
13 explicit EmuWindow_SDL2_VK(bool fullscreen);
14 ~EmuWindow_SDL2_VK();
15
16 /// Swap buffers to display the next frame
17 void SwapBuffers() override;
18
19 /// Makes the graphics context current for the caller thread
20 void MakeCurrent() override;
21
22 /// Releases the GL context from the caller thread
23 void DoneCurrent() override;
24
25 /// Retrieves Vulkan specific handlers from the window
26 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
27 void* surface) const override;
28
29 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
30
31private:
32 bool UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const;
33
34 VkInstance instance{};
35 VkSurfaceKHR surface{};
36
37 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
38 PFN_vkDestroyInstance vkDestroyInstance{};
39};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 3ee088a91..325795321 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -32,6 +32,9 @@
32#include "yuzu_cmd/config.h" 32#include "yuzu_cmd/config.h"
33#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 33#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
34#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" 34#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
35#ifdef HAS_VULKAN
36#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
37#endif
35 38
36#include "core/file_sys/registered_cache.h" 39#include "core/file_sys/registered_cache.h"
37 40
@@ -174,7 +177,20 @@ int main(int argc, char** argv) {
174 Settings::values.use_gdbstub = use_gdbstub; 177 Settings::values.use_gdbstub = use_gdbstub;
175 Settings::Apply(); 178 Settings::Apply();
176 179
177 std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2_GL>(fullscreen)}; 180 std::unique_ptr<EmuWindow_SDL2> emu_window;
181 switch (Settings::values.renderer_backend) {
182 case Settings::RendererBackend::OpenGL:
183 emu_window = std::make_unique<EmuWindow_SDL2_GL>(fullscreen);
184 break;
185 case Settings::RendererBackend::Vulkan:
186#ifdef HAS_VULKAN
187 emu_window = std::make_unique<EmuWindow_SDL2_VK>(fullscreen);
188 break;
189#else
190 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
191 return 1;
192#endif
193 }
178 194
179 if (!Settings::values.use_multi_core) { 195 if (!Settings::values.use_multi_core) {
180 // Single core mode must acquire OpenGL context for entire emulation session 196 // Single core mode must acquire OpenGL context for entire emulation session
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
index e7fe8decf..f2cc4a797 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
@@ -5,10 +5,15 @@
5#include <algorithm> 5#include <algorithm>
6#include <cstdlib> 6#include <cstdlib>
7#include <string> 7#include <string>
8
9#include <fmt/format.h>
10
8#define SDL_MAIN_HANDLED 11#define SDL_MAIN_HANDLED
9#include <SDL.h> 12#include <SDL.h>
10#include <fmt/format.h> 13
11#include <glad/glad.h> 14#include <glad/glad.h>
15
16#include "common/assert.h"
12#include "common/logging/log.h" 17#include "common/logging/log.h"
13#include "common/scm_rev.h" 18#include "common/scm_rev.h"
14#include "core/settings.h" 19#include "core/settings.h"
@@ -120,3 +125,11 @@ void EmuWindow_SDL2_Hide::MakeCurrent() {
120void EmuWindow_SDL2_Hide::DoneCurrent() { 125void EmuWindow_SDL2_Hide::DoneCurrent() {
121 SDL_GL_MakeCurrent(render_window, nullptr); 126 SDL_GL_MakeCurrent(render_window, nullptr);
122} 127}
128
129bool EmuWindow_SDL2_Hide::IsShown() const {
130 return false;
131}
132
133void EmuWindow_SDL2_Hide::RetrieveVulkanHandlers(void*, void*, void*) const {
134 UNREACHABLE();
135}
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
index 1a8953c75..c7fccc002 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
@@ -25,6 +25,13 @@ public:
25 /// Releases the GL context from the caller thread 25 /// Releases the GL context from the caller thread
26 void DoneCurrent() override; 26 void DoneCurrent() override;
27 27
28 /// Whether the screen is being shown or not.
29 bool IsShown() const override;
30
31 /// Retrieves Vulkan specific handlers from the window
32 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
33 void* surface) const override;
34
28 /// Whether the window is still open, and a close request hasn't yet been sent 35 /// Whether the window is still open, and a close request hasn't yet been sent
29 bool IsOpen() const; 36 bool IsOpen() const;
30 37