summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2020-02-03 16:56:25 -0500
committerGravatar GitHub2020-02-03 16:56:25 -0500
commitc31ec00d676f6dda0bebee70d7a0b230e5babee9 (patch)
tree52c674cace5652f5d359dedd02ee01f911559192 /src
parentMerge pull request #3374 from lioncash/udp (diff)
parentci: Disable Vulkan for Windows MinGW builds (diff)
downloadyuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar.gz
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar.xz
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.zip
Merge pull request #3337 from ReinUsesLisp/vulkan-staged
yuzu: Implement Vulkan frontend
Diffstat (limited to 'src')
-rw-r--r--src/core/core.cpp4
-rw-r--r--src/core/frontend/emu_window.h7
-rw-r--r--src/core/settings.h9
-rw-r--r--src/core/telemetry_session.cpp12
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp265
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp6
-rw-r--r--src/video_core/shader/decode/other.cpp9
-rw-r--r--src/video_core/video_core.cpp15
-rw-r--r--src/web_service/telemetry_json.cpp1
-rw-r--r--src/yuzu/CMakeLists.txt5
-rw-r--r--src/yuzu/bootmanager.cpp284
-rw-r--r--src/yuzu/bootmanager.h30
-rw-r--r--src/yuzu/configuration/config.cpp7
-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/config.cpp6
-rw-r--r--src/yuzu_cmd/default_ini.h11
-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.cpp162
-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
34 files changed, 1154 insertions, 179 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp
index c53d122be..0eb0c0dca 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -268,7 +268,9 @@ struct System::Impl {
268 is_powered_on = false; 268 is_powered_on = false;
269 exit_lock = false; 269 exit_lock = false;
270 270
271 gpu_core->WaitIdle(); 271 if (gpu_core) {
272 gpu_core->WaitIdle();
273 }
272 274
273 // Shutdown emulation session 275 // Shutdown emulation session
274 renderer.reset(); 276 renderer.reset();
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/core/settings.h b/src/core/settings.h
index 421e76f5f..e1a9a0ffa 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -371,6 +371,11 @@ enum class SDMCSize : u64 {
371 S1TB = 0x10000000000ULL, 371 S1TB = 0x10000000000ULL,
372}; 372};
373 373
374enum class RendererBackend {
375 OpenGL = 0,
376 Vulkan = 1,
377};
378
374struct Values { 379struct Values {
375 // System 380 // System
376 bool use_docked_mode; 381 bool use_docked_mode;
@@ -419,6 +424,10 @@ struct Values {
419 SDMCSize sdmc_size; 424 SDMCSize sdmc_size;
420 425
421 // Renderer 426 // Renderer
427 RendererBackend renderer_backend;
428 bool renderer_debug;
429 int vulkan_device;
430
422 float resolution_factor; 431 float resolution_factor;
423 bool use_frame_limit; 432 bool use_frame_limit;
424 u16 frame_limit; 433 u16 frame_limit;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 320e8ad73..0e72d31cd 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -46,6 +46,16 @@ static u64 GenerateTelemetryId() {
46 return telemetry_id; 46 return telemetry_id;
47} 47}
48 48
49static const char* TranslateRenderer(Settings::RendererBackend backend) {
50 switch (backend) {
51 case Settings::RendererBackend::OpenGL:
52 return "OpenGL";
53 case Settings::RendererBackend::Vulkan:
54 return "Vulkan";
55 }
56 return "Unknown";
57}
58
49u64 GetTelemetryId() { 59u64 GetTelemetryId() {
50 u64 telemetry_id{}; 60 u64 telemetry_id{};
51 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + 61 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
@@ -169,7 +179,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
169 AddField(field_type, "Audio_SinkId", Settings::values.sink_id); 179 AddField(field_type, "Audio_SinkId", Settings::values.sink_id);
170 AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); 180 AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
171 AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core); 181 AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core);
172 AddField(field_type, "Renderer_Backend", "OpenGL"); 182 AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend));
173 AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor); 183 AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor);
174 AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit); 184 AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit);
175 AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit); 185 AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit);
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 04a25da4f..db9332d00 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -156,6 +156,7 @@ if (ENABLE_VULKAN)
156 renderer_vulkan/maxwell_to_vk.cpp 156 renderer_vulkan/maxwell_to_vk.cpp
157 renderer_vulkan/maxwell_to_vk.h 157 renderer_vulkan/maxwell_to_vk.h
158 renderer_vulkan/renderer_vulkan.h 158 renderer_vulkan/renderer_vulkan.h
159 renderer_vulkan/renderer_vulkan.cpp
159 renderer_vulkan/vk_blit_screen.cpp 160 renderer_vulkan/vk_blit_screen.cpp
160 renderer_vulkan/vk_blit_screen.h 161 renderer_vulkan/vk_blit_screen.h
161 renderer_vulkan/vk_buffer_cache.cpp 162 renderer_vulkan/vk_buffer_cache.cpp
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 0510ed777..186aca61d 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -101,7 +101,10 @@ public:
101 void TickFrame() { 101 void TickFrame() {
102 ++epoch; 102 ++epoch;
103 while (!pending_destruction.empty()) { 103 while (!pending_destruction.empty()) {
104 if (pending_destruction.front()->GetEpoch() + 1 > epoch) { 104 // Delay at least 4 frames before destruction.
105 // This is due to triple buffering happening on some drivers.
106 static constexpr u64 epochs_to_destroy = 5;
107 if (pending_destruction.front()->GetEpoch() + epochs_to_destroy > epoch) {
105 break; 108 break;
106 } 109 }
107 pending_destruction.pop_front(); 110 pending_destruction.pop_front();
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/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 939eebe83..9840f26e5 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -400,8 +400,10 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
400 VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true); 400 VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true);
401 Test(extension, ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, 401 Test(extension, ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME,
402 false); 402 false);
403 Test(extension, nv_device_diagnostic_checkpoints, 403 if (Settings::values.renderer_debug) {
404 VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true); 404 Test(extension, nv_device_diagnostic_checkpoints,
405 VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true);
406 }
405 } 407 }
406 408
407 if (khr_shader_float16_int8) { 409 if (khr_shader_float16_int8) {
diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp
index 7321698b2..4944e9d69 100644
--- a/src/video_core/shader/decode/other.cpp
+++ b/src/video_core/shader/decode/other.cpp
@@ -69,13 +69,16 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
69 case OpCode::Id::MOV_SYS: { 69 case OpCode::Id::MOV_SYS: {
70 const Node value = [this, instr] { 70 const Node value = [this, instr] {
71 switch (instr.sys20) { 71 switch (instr.sys20) {
72 case SystemVariable::LaneId:
73 LOG_WARNING(HW_GPU, "MOV_SYS instruction with LaneId is incomplete");
74 return Immediate(0U);
72 case SystemVariable::InvocationId: 75 case SystemVariable::InvocationId:
73 return Operation(OperationCode::InvocationId); 76 return Operation(OperationCode::InvocationId);
74 case SystemVariable::Ydirection: 77 case SystemVariable::Ydirection:
75 return Operation(OperationCode::YNegate); 78 return Operation(OperationCode::YNegate);
76 case SystemVariable::InvocationInfo: 79 case SystemVariable::InvocationInfo:
77 LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete"); 80 LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
78 return Immediate(0u); 81 return Immediate(0U);
79 case SystemVariable::Tid: { 82 case SystemVariable::Tid: {
80 Node value = Immediate(0); 83 Node value = Immediate(0);
81 value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9); 84 value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9);
@@ -188,7 +191,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
188 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}", 191 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}",
189 static_cast<u32>(cc)); 192 static_cast<u32>(cc));
190 193
191 if (disable_flow_stack) { 194 if (decompiled) {
192 break; 195 break;
193 } 196 }
194 197
@@ -200,7 +203,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
200 const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; 203 const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
201 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}", 204 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}",
202 static_cast<u32>(cc)); 205 static_cast<u32>(cc));
203 if (disable_flow_stack) { 206 if (decompiled) {
204 break; 207 break;
205 } 208 }
206 209
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/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 9156ce802..7538389bf 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -117,6 +117,7 @@ bool TelemetryJson::SubmitTestcase() {
117 impl->SerializeSection(Telemetry::FieldType::Session, "Session"); 117 impl->SerializeSection(Telemetry::FieldType::Session, "Session");
118 impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); 118 impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
119 impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); 119 impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
120 impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
120 121
121 auto content = impl->TopSection().dump(); 122 auto content = impl->TopSection().dump();
122 Client client(impl->host, impl->username, impl->token); 123 Client client(impl->host, impl->username, impl->token);
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..55a37fffa 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,11 +184,47 @@ 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 }
188}; 215};
189 216
217#ifdef HAS_VULKAN
218class GVKWidgetInternal final : public GWidgetInternal {
219public:
220 GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) {
221 setSurfaceType(QSurface::SurfaceType::VulkanSurface);
222 setVulkanInstance(instance);
223 }
224 ~GVKWidgetInternal() override = default;
225};
226#endif
227
190GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) 228GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
191 : QWidget(parent), emu_thread(emu_thread) { 229 : QWidget(parent), emu_thread(emu_thread) {
192 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") 230 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
@@ -201,9 +239,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
201 239
202GRenderWindow::~GRenderWindow() { 240GRenderWindow::~GRenderWindow() {
203 InputCommon::Shutdown(); 241 InputCommon::Shutdown();
242
243 // Avoid an unordered destruction that generates a segfault
244 delete child;
204} 245}
205 246
206void GRenderWindow::moveContext() { 247void GRenderWindow::moveContext() {
248 if (!context) {
249 return;
250 }
207 DoneCurrent(); 251 DoneCurrent();
208 252
209 // If the thread started running, move the GL Context to the new thread. Otherwise, move it 253 // If the thread started running, move the GL Context to the new thread. Otherwise, move it
@@ -215,8 +259,9 @@ void GRenderWindow::moveContext() {
215} 259}
216 260
217void GRenderWindow::SwapBuffers() { 261void GRenderWindow::SwapBuffers() {
218 context->swapBuffers(child); 262 if (context) {
219 263 context->swapBuffers(child);
264 }
220 if (!first_frame) { 265 if (!first_frame) {
221 first_frame = true; 266 first_frame = true;
222 emit FirstFrameDisplayed(); 267 emit FirstFrameDisplayed();
@@ -224,15 +269,38 @@ void GRenderWindow::SwapBuffers() {
224} 269}
225 270
226void GRenderWindow::MakeCurrent() { 271void GRenderWindow::MakeCurrent() {
227 context->makeCurrent(child); 272 if (context) {
273 context->makeCurrent(child);
274 }
228} 275}
229 276
230void GRenderWindow::DoneCurrent() { 277void GRenderWindow::DoneCurrent() {
231 context->doneCurrent(); 278 if (context) {
279 context->doneCurrent();
280 }
232} 281}
233 282
234void GRenderWindow::PollEvents() {} 283void GRenderWindow::PollEvents() {}
235 284
285bool GRenderWindow::IsShown() const {
286 return !isMinimized();
287}
288
289void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
290 void* surface) const {
291#ifdef HAS_VULKAN
292 const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
293 const VkInstance instance_copy = vk_instance->vkInstance();
294 const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child);
295
296 std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
297 std::memcpy(instance, &instance_copy, sizeof(instance_copy));
298 std::memcpy(surface, &surface_copy, sizeof(surface_copy));
299#else
300 UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
301#endif
302}
303
236// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). 304// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
237// 305//
238// Older versions get the window size (density independent pixels), 306// Older versions get the window size (density independent pixels),
@@ -241,10 +309,9 @@ void GRenderWindow::PollEvents() {}
241void GRenderWindow::OnFramebufferSizeChanged() { 309void GRenderWindow::OnFramebufferSizeChanged() {
242 // Screen changes potentially incur a change in screen DPI, hence we should update the 310 // Screen changes potentially incur a change in screen DPI, hence we should update the
243 // framebuffer size 311 // framebuffer size
244 const qreal pixel_ratio = GetWindowPixelRatio(); 312 const qreal pixelRatio{GetWindowPixelRatio()};
245 const u32 width = child->QPaintDevice::width() * pixel_ratio; 313 const auto size{child->GetSize()};
246 const u32 height = child->QPaintDevice::height() * pixel_ratio; 314 UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio);
247 UpdateCurrentFramebufferLayout(width, height);
248} 315}
249 316
250void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { 317void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
@@ -290,7 +357,7 @@ qreal GRenderWindow::GetWindowPixelRatio() const {
290} 357}
291 358
292std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { 359std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
293 const qreal pixel_ratio = GetWindowPixelRatio(); 360 const qreal pixel_ratio{GetWindowPixelRatio()};
294 return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), 361 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}))}; 362 static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
296} 363}
@@ -356,50 +423,46 @@ std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedCont
356 return std::make_unique<GGLContext>(context.get()); 423 return std::make_unique<GGLContext>(context.get());
357} 424}
358 425
359void GRenderWindow::InitRenderTarget() { 426bool GRenderWindow::InitRenderTarget() {
360 shared_context.reset(); 427 shared_context.reset();
361 context.reset(); 428 context.reset();
362 429 if (child) {
363 delete child; 430 delete child;
364 child = nullptr; 431 }
365 432 if (container) {
366 delete container; 433 delete container;
367 container = nullptr; 434 }
368 435 if (layout()) {
369 delete layout(); 436 delete layout();
437 }
370 438
371 first_frame = false; 439 first_frame = false;
372 440
373 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, 441 switch (Settings::values.renderer_backend) {
374 // WA_DontShowOnScreen, WA_DeleteOnClose 442 case Settings::RendererBackend::OpenGL:
375 QSurfaceFormat fmt; 443 if (!InitializeOpenGL()) {
376 fmt.setVersion(4, 3); 444 return false;
377 fmt.setProfile(QSurfaceFormat::CompatibilityProfile); 445 }
378 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); 446 break;
379 // TODO: expose a setting for buffer value (ie default/single/double/triple) 447 case Settings::RendererBackend::Vulkan:
380 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); 448 if (!InitializeVulkan()) {
381 shared_context = std::make_unique<QOpenGLContext>(); 449 return false;
382 shared_context->setFormat(fmt); 450 }
383 shared_context->create(); 451 break;
384 context = std::make_unique<QOpenGLContext>(); 452 }
385 context->setShareContext(shared_context.get());
386 context->setFormat(fmt);
387 context->create();
388 fmt.setSwapInterval(0);
389 453
390 child = new GGLWidgetInternal(this, shared_context.get());
391 container = QWidget::createWindowContainer(child, this); 454 container = QWidget::createWindowContainer(child, this);
392
393 QBoxLayout* layout = new QHBoxLayout(this); 455 QBoxLayout* layout = new QHBoxLayout(this);
456
394 layout->addWidget(container); 457 layout->addWidget(container);
395 layout->setMargin(0); 458 layout->setMargin(0);
396 setLayout(layout); 459 setLayout(layout);
397 460
398 // Reset minimum size to avoid unwanted resizes when this function is called for a second time. 461 // Reset minimum required size to avoid resizing issues on the main window after restarting.
399 setMinimumSize(1, 1); 462 setMinimumSize(1, 1);
400 463
401 // Show causes the window to actually be created and the OpenGL context as well, but we don't 464 // 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. 465 // the widget to be shown yet, so immediately hide it.
403 show(); 466 show();
404 hide(); 467 hide();
405 468
@@ -410,9 +473,17 @@ void GRenderWindow::InitRenderTarget() {
410 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); 473 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
411 474
412 OnFramebufferSizeChanged(); 475 OnFramebufferSizeChanged();
413 NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height())); 476 NotifyClientAreaSizeChanged(child->GetSize());
414 477
415 BackupGeometry(); 478 BackupGeometry();
479
480 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
481 if (!LoadOpenGL()) {
482 return false;
483 }
484 }
485
486 return true;
416} 487}
417 488
418void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { 489void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
@@ -441,6 +512,113 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
441 setMinimumSize(minimal_size.first, minimal_size.second); 512 setMinimumSize(minimal_size.first, minimal_size.second);
442} 513}
443 514
515bool GRenderWindow::InitializeOpenGL() {
516 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
517 // WA_DontShowOnScreen, WA_DeleteOnClose
518 QSurfaceFormat fmt;
519 fmt.setVersion(4, 3);
520 fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
521 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
522 // TODO: expose a setting for buffer value (ie default/single/double/triple)
523 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
524 shared_context = std::make_unique<QOpenGLContext>();
525 shared_context->setFormat(fmt);
526 shared_context->create();
527 context = std::make_unique<QOpenGLContext>();
528 context->setShareContext(shared_context.get());
529 context->setFormat(fmt);
530 context->create();
531 fmt.setSwapInterval(false);
532
533 child = new GGLWidgetInternal(this, shared_context.get());
534 return true;
535}
536
537bool GRenderWindow::InitializeVulkan() {
538#ifdef HAS_VULKAN
539 vk_instance = std::make_unique<QVulkanInstance>();
540 vk_instance->setApiVersion(QVersionNumber(1, 1, 0));
541 vk_instance->setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect);
542 if (Settings::values.renderer_debug) {
543 const auto supported_layers{vk_instance->supportedLayers()};
544 const bool found =
545 std::find_if(supported_layers.begin(), supported_layers.end(), [](const auto& layer) {
546 constexpr const char searched_layer[] = "VK_LAYER_LUNARG_standard_validation";
547 return layer.name == searched_layer;
548 });
549 if (found) {
550 vk_instance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
551 vk_instance->setExtensions(QByteArrayList() << VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
552 }
553 }
554 if (!vk_instance->create()) {
555 QMessageBox::critical(
556 this, tr("Error while initializing Vulkan 1.1!"),
557 tr("Your OS doesn't seem to support Vulkan 1.1 instances, or you do not have the "
558 "latest graphics drivers."));
559 return false;
560 }
561
562 child = new GVKWidgetInternal(this, vk_instance.get());
563 return true;
564#else
565 QMessageBox::critical(this, tr("Vulkan not available!"),
566 tr("yuzu has not been compiled with Vulkan support."));
567 return false;
568#endif
569}
570
571bool GRenderWindow::LoadOpenGL() {
572 Core::Frontend::ScopeAcquireWindowContext acquire_context{*this};
573 if (!gladLoadGL()) {
574 QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
575 tr("Your GPU may not support OpenGL 4.3, or you do not have the "
576 "latest graphics driver."));
577 return false;
578 }
579
580 QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
581 if (!unsupported_gl_extensions.empty()) {
582 QMessageBox::critical(
583 this, tr("Error while initializing OpenGL!"),
584 tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
585 "have the latest graphics driver.<br><br>Unsupported extensions:<br>") +
586 unsupported_gl_extensions.join(QStringLiteral("<br>")));
587 return false;
588 }
589 return true;
590}
591
592QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
593 QStringList unsupported_ext;
594
595 if (!GLAD_GL_ARB_buffer_storage)
596 unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
597 if (!GLAD_GL_ARB_direct_state_access)
598 unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
599 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
600 unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
601 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
602 unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
603 if (!GLAD_GL_ARB_multi_bind)
604 unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
605 if (!GLAD_GL_ARB_clip_control)
606 unsupported_ext.append(QStringLiteral("ARB_clip_control"));
607
608 // Extensions required to support some texture formats.
609 if (!GLAD_GL_EXT_texture_compression_s3tc)
610 unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
611 if (!GLAD_GL_ARB_texture_compression_rgtc)
612 unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
613 if (!GLAD_GL_ARB_depth_buffer_float)
614 unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
615
616 for (const QString& ext : unsupported_ext)
617 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
618
619 return unsupported_ext;
620}
621
444void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { 622void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
445 this->emu_thread = emu_thread; 623 this->emu_thread = emu_thread;
446 child->DisablePainting(); 624 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/config.cpp b/src/yuzu/configuration/config.cpp
index 59918847a..280d81ba9 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -624,6 +624,10 @@ void Config::ReadPathValues() {
624void Config::ReadRendererValues() { 624void Config::ReadRendererValues() {
625 qt_config->beginGroup(QStringLiteral("Renderer")); 625 qt_config->beginGroup(QStringLiteral("Renderer"));
626 626
627 Settings::values.renderer_backend =
628 static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt());
629 Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool();
630 Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt();
627 Settings::values.resolution_factor = 631 Settings::values.resolution_factor =
628 ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat(); 632 ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat();
629 Settings::values.use_frame_limit = 633 Settings::values.use_frame_limit =
@@ -1056,6 +1060,9 @@ void Config::SavePathValues() {
1056void Config::SaveRendererValues() { 1060void Config::SaveRendererValues() {
1057 qt_config->beginGroup(QStringLiteral("Renderer")); 1061 qt_config->beginGroup(QStringLiteral("Renderer"));
1058 1062
1063 WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0);
1064 WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false);
1065 WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0);
1059 WriteSetting(QStringLiteral("resolution_factor"), 1066 WriteSetting(QStringLiteral("resolution_factor"),
1060 static_cast<double>(Settings::values.resolution_factor), 1.0); 1067 static_cast<double>(Settings::values.resolution_factor), 1.0);
1061 WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); 1068 WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true);
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/config.cpp b/src/yuzu_cmd/config.cpp
index 161583b54..b01a36023 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -371,6 +371,12 @@ void Config::ReadValues() {
371 Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); 371 Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
372 372
373 // Renderer 373 // Renderer
374 const int renderer_backend = sdl2_config->GetInteger(
375 "Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL));
376 Settings::values.renderer_backend = static_cast<Settings::RendererBackend>(renderer_backend);
377 Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "debug", false);
378 Settings::values.vulkan_device = sdl2_config->GetInteger("Renderer", "vulkan_device", 0);
379
374 Settings::values.resolution_factor = 380 Settings::values.resolution_factor =
375 static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0)); 381 static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
376 Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); 382 Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index e829f8695..00fd88279 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -98,6 +98,17 @@ udp_pad_index=
98use_multi_core= 98use_multi_core=
99 99
100[Renderer] 100[Renderer]
101# Which backend API to use.
102# 0 (default): OpenGL, 1: Vulkan
103backend =
104
105# Enable graphics API debugging mode.
106# 0 (default): Disabled, 1: Enabled
107debug =
108
109# Which Vulkan physical device to use (defaults to 0)
110vulkan_device =
111
101# Whether to use software or hardware rendering. 112# Whether to use software or hardware rendering.
102# 0: Software, 1 (default): Hardware 113# 0: Software, 1 (default): Hardware
103use_hw_renderer = 114use_hw_renderer =
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..a203f0da9
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -0,0 +1,162 @@
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, &vk_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(vk_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, vk_instance, &vk_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(vk_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 const auto instance_proc_addr = vkGetInstanceProcAddr;
126 std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
127 std::memcpy(instance, &vk_instance, sizeof(vk_instance));
128 std::memcpy(surface, &vk_surface, sizeof(vk_surface));
129}
130
131std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
132 return nullptr;
133}
134
135bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const {
136 if (!Settings::values.renderer_debug) {
137 return false;
138 }
139
140 const auto vkEnumerateInstanceLayerProperties =
141 reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
142 vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceLayerProperties"));
143 if (vkEnumerateInstanceLayerProperties == nullptr) {
144 LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
145 return false;
146 }
147
148 u32 available_layers_count{};
149 if (vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr) != VK_SUCCESS) {
150 LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
151 return false;
152 }
153 std::vector<VkLayerProperties> layers(available_layers_count);
154 if (vkEnumerateInstanceLayerProperties(&available_layers_count, layers.data()) != VK_SUCCESS) {
155 LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
156 return false;
157 }
158
159 return std::find_if(layers.begin(), layers.end(), [&](const auto& layer) {
160 return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation");
161 }) != layers.end();
162}
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..2a7c06a24
--- /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 vk_instance{};
35 VkSurfaceKHR vk_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