summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbunnei <bunneidev@gmail.com>2020-02-03 22:56:25 +0100
committerGitHub <noreply@github.com>2020-02-03 22:56:25 +0100
commitc31ec00d676f6dda0bebee70d7a0b230e5babee9 (patch)
tree52c674cace5652f5d359dedd02ee01f911559192
parentMerge pull request #3374 from lioncash/udp (diff)
parentci: Disable Vulkan for Windows MinGW builds (diff)
downloadyuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar.gz
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar.bz2
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar.lz
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar.xz
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.tar.zst
yuzu-c31ec00d676f6dda0bebee70d7a0b230e5babee9.zip
-rw-r--r--.ci/scripts/linux/docker.sh2
-rw-r--r--.ci/scripts/windows/docker.sh2
-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
36 files changed, 1156 insertions, 181 deletions
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index 5559a527c..f11878128 100644
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -5,7 +5,7 @@ cd /yuzu
ccache -s
mkdir build || true && cd build
-cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
+cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_VULKAN=No
ninja
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index e8f26933a..beb554b65 100644
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -13,7 +13,7 @@ echo '' >> /bin/cmd
chmod +x /bin/cmd
mkdir build || true && cd build
-cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
+cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_VULKAN=No
ninja
# Clean up the dirty hacks
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 {
is_powered_on = false;
exit_lock = false;
- gpu_core->WaitIdle();
+ if (gpu_core) {
+ gpu_core->WaitIdle();
+ }
// Shutdown emulation session
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:
return nullptr;
}
+ /// Returns if window is shown (not minimized)
+ virtual bool IsShown() const = 0;
+
+ /// Retrieves Vulkan specific handlers from the window
+ virtual void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const = 0;
+
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @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 {
S1TB = 0x10000000000ULL,
};
+enum class RendererBackend {
+ OpenGL = 0,
+ Vulkan = 1,
+};
+
struct Values {
// System
bool use_docked_mode;
@@ -419,6 +424,10 @@ struct Values {
SDMCSize sdmc_size;
// Renderer
+ RendererBackend renderer_backend;
+ bool renderer_debug;
+ int vulkan_device;
+
float resolution_factor;
bool use_frame_limit;
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() {
return telemetry_id;
}
+static const char* TranslateRenderer(Settings::RendererBackend backend) {
+ switch (backend) {
+ case Settings::RendererBackend::OpenGL:
+ return "OpenGL";
+ case Settings::RendererBackend::Vulkan:
+ return "Vulkan";
+ }
+ return "Unknown";
+}
+
u64 GetTelemetryId() {
u64 telemetry_id{};
const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
@@ -169,7 +179,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
AddField(field_type, "Audio_SinkId", Settings::values.sink_id);
AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core);
- AddField(field_type, "Renderer_Backend", "OpenGL");
+ AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend));
AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor);
AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit);
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)
renderer_vulkan/maxwell_to_vk.cpp
renderer_vulkan/maxwell_to_vk.h
renderer_vulkan/renderer_vulkan.h
+ renderer_vulkan/renderer_vulkan.cpp
renderer_vulkan/vk_blit_screen.cpp
renderer_vulkan/vk_blit_screen.h
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:
void TickFrame() {
++epoch;
while (!pending_destruction.empty()) {
- if (pending_destruction.front()->GetEpoch() + 1 > epoch) {
+ // Delay at least 4 frames before destruction.
+ // This is due to triple buffering happening on some drivers.
+ static constexpr u64 epochs_to_destroy = 5;
+ if (pending_destruction.front()->GetEpoch() + epochs_to_destroy > epoch) {
break;
}
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 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/telemetry.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/frontend/emu_window.h"
+#include "core/memory.h"
+#include "core/perf_stats.h"
+#include "core/settings.h"
+#include "core/telemetry_session.h"
+#include "video_core/gpu.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/renderer_vulkan.h"
+#include "video_core/renderer_vulkan/vk_blit_screen.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_memory_manager.h"
+#include "video_core/renderer_vulkan/vk_rasterizer.h"
+#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_swapchain.h"
+
+namespace Vulkan {
+
+namespace {
+
+VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity_,
+ VkDebugUtilsMessageTypeFlagsEXT type,
+ const VkDebugUtilsMessengerCallbackDataEXT* data,
+ [[maybe_unused]] void* user_data) {
+ const vk::DebugUtilsMessageSeverityFlagBitsEXT severity{severity_};
+ const char* message{data->pMessage};
+
+ if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
+ LOG_CRITICAL(Render_Vulkan, "{}", message);
+ } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
+ LOG_WARNING(Render_Vulkan, "{}", message);
+ } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
+ LOG_INFO(Render_Vulkan, "{}", message);
+ } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
+ LOG_DEBUG(Render_Vulkan, "{}", message);
+ }
+ return VK_FALSE;
+}
+
+std::string GetReadableVersion(u32 version) {
+ return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
+ VK_VERSION_PATCH(version));
+}
+
+std::string GetDriverVersion(const VKDevice& device) {
+ // Extracted from
+ // https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314
+ const u32 version = device.GetDriverVersion();
+
+ if (device.GetDriverID() == vk::DriverIdKHR::eNvidiaProprietary) {
+ const u32 major = (version >> 22) & 0x3ff;
+ const u32 minor = (version >> 14) & 0x0ff;
+ const u32 secondary = (version >> 6) & 0x0ff;
+ const u32 tertiary = version & 0x003f;
+ return fmt::format("{}.{}.{}.{}", major, minor, secondary, tertiary);
+ }
+ if (device.GetDriverID() == vk::DriverIdKHR::eIntelProprietaryWindows) {
+ const u32 major = version >> 14;
+ const u32 minor = version & 0x3fff;
+ return fmt::format("{}.{}", major, minor);
+ }
+
+ return GetReadableVersion(version);
+}
+
+std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_extensions) {
+ std::sort(std::begin(available_extensions), std::end(available_extensions));
+
+ static constexpr std::size_t AverageExtensionSize = 64;
+ std::string separated_extensions;
+ separated_extensions.reserve(available_extensions.size() * AverageExtensionSize);
+
+ const auto end = std::end(available_extensions);
+ for (auto extension = std::begin(available_extensions); extension != end; ++extension) {
+ if (const bool is_last = extension + 1 == end; is_last) {
+ separated_extensions += *extension;
+ } else {
+ separated_extensions += fmt::format("{},", *extension);
+ }
+ }
+ return separated_extensions;
+}
+
+} // Anonymous namespace
+
+RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system)
+ : RendererBase(window), system{system} {}
+
+RendererVulkan::~RendererVulkan() {
+ ShutDown();
+}
+
+void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
+ const auto& layout = render_window.GetFramebufferLayout();
+ if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) {
+ const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
+ const bool use_accelerated =
+ rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
+ const bool is_srgb = use_accelerated && screen_info.is_srgb;
+ if (swapchain->HasFramebufferChanged(layout) || swapchain->GetSrgbState() != is_srgb) {
+ swapchain->Create(layout.width, layout.height, is_srgb);
+ blit_screen->Recreate();
+ }
+
+ scheduler->WaitWorker();
+
+ swapchain->AcquireNextImage();
+ const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated);
+
+ scheduler->Flush(false, render_semaphore);
+
+ if (swapchain->Present(render_semaphore, fence)) {
+ blit_screen->Recreate();
+ }
+
+ render_window.SwapBuffers();
+ rasterizer->TickFrame();
+ }
+
+ render_window.PollEvents();
+}
+
+bool RendererVulkan::Init() {
+ PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
+ render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface);
+ const vk::DispatchLoaderDynamic dldi(instance, vkGetInstanceProcAddr);
+
+ std::optional<vk::DebugUtilsMessengerEXT> callback;
+ if (Settings::values.renderer_debug && dldi.vkCreateDebugUtilsMessengerEXT) {
+ callback = CreateDebugCallback(dldi);
+ if (!callback) {
+ return false;
+ }
+ }
+
+ if (!PickDevices(dldi)) {
+ if (callback) {
+ instance.destroy(*callback, nullptr, dldi);
+ }
+ return false;
+ }
+ debug_callback = UniqueDebugUtilsMessengerEXT(
+ *callback, vk::ObjectDestroy<vk::Instance, vk::DispatchLoaderDynamic>(
+ instance, nullptr, device->GetDispatchLoader()));
+
+ Report();
+
+ memory_manager = std::make_unique<VKMemoryManager>(*device);
+
+ resource_manager = std::make_unique<VKResourceManager>(*device);
+
+ const auto& framebuffer = render_window.GetFramebufferLayout();
+ swapchain = std::make_unique<VKSwapchain>(surface, *device);
+ swapchain->Create(framebuffer.width, framebuffer.height, false);
+
+ scheduler = std::make_unique<VKScheduler>(*device, *resource_manager);
+
+ rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device,
+ *resource_manager, *memory_manager, *scheduler);
+
+ blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device,
+ *resource_manager, *memory_manager, *swapchain,
+ *scheduler, screen_info);
+
+ return true;
+}
+
+void RendererVulkan::ShutDown() {
+ if (!device) {
+ return;
+ }
+ const auto dev = device->GetLogical();
+ const auto& dld = device->GetDispatchLoader();
+ if (dev && dld.vkDeviceWaitIdle) {
+ dev.waitIdle(dld);
+ }
+
+ rasterizer.reset();
+ blit_screen.reset();
+ scheduler.reset();
+ swapchain.reset();
+ memory_manager.reset();
+ resource_manager.reset();
+ device.reset();
+}
+
+std::optional<vk::DebugUtilsMessengerEXT> RendererVulkan::CreateDebugCallback(
+ const vk::DispatchLoaderDynamic& dldi) {
+ const vk::DebugUtilsMessengerCreateInfoEXT callback_ci(
+ {},
+ vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
+ vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
+ vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
+ vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose,
+ vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
+ vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
+ vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
+ &DebugCallback, nullptr);
+ vk::DebugUtilsMessengerEXT callback;
+ if (instance.createDebugUtilsMessengerEXT(&callback_ci, nullptr, &callback, dldi) !=
+ vk::Result::eSuccess) {
+ LOG_ERROR(Render_Vulkan, "Failed to create debug callback");
+ return {};
+ }
+ return callback;
+}
+
+bool RendererVulkan::PickDevices(const vk::DispatchLoaderDynamic& dldi) {
+ const auto devices = instance.enumeratePhysicalDevices(dldi);
+
+ // TODO(Rodrigo): Choose device from config file
+ const s32 device_index = Settings::values.vulkan_device;
+ if (device_index < 0 || device_index >= static_cast<s32>(devices.size())) {
+ LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
+ return false;
+ }
+ const vk::PhysicalDevice physical_device = devices[device_index];
+
+ if (!VKDevice::IsSuitable(dldi, physical_device, surface)) {
+ return false;
+ }
+
+ device = std::make_unique<VKDevice>(dldi, physical_device, surface);
+ return device->Create(dldi, instance);
+}
+
+void RendererVulkan::Report() const {
+ const std::string vendor_name{device->GetVendorName()};
+ const std::string model_name{device->GetModelName()};
+ const std::string driver_version = GetDriverVersion(*device);
+ const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
+
+ const std::string api_version = GetReadableVersion(device->GetApiVersion());
+
+ const std::string extensions = BuildCommaSeparatedExtensions(device->GetAvailableExtensions());
+
+ LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);
+ LOG_INFO(Render_Vulkan, "Device: {}", model_name);
+ LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
+
+ auto& telemetry_session = system.TelemetrySession();
+ constexpr auto field = Telemetry::FieldType::UserSystem;
+ telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
+ telemetry_session.AddField(field, "GPU_Model", model_name);
+ telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
+ telemetry_session.AddField(field, "GPU_Vulkan_Version", api_version);
+ telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
+}
+
+} // 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
VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true);
Test(extension, ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME,
false);
- Test(extension, nv_device_diagnostic_checkpoints,
- VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true);
+ if (Settings::values.renderer_debug) {
+ Test(extension, nv_device_diagnostic_checkpoints,
+ VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true);
+ }
}
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) {
case OpCode::Id::MOV_SYS: {
const Node value = [this, instr] {
switch (instr.sys20) {
+ case SystemVariable::LaneId:
+ LOG_WARNING(HW_GPU, "MOV_SYS instruction with LaneId is incomplete");
+ return Immediate(0U);
case SystemVariable::InvocationId:
return Operation(OperationCode::InvocationId);
case SystemVariable::Ydirection:
return Operation(OperationCode::YNegate);
case SystemVariable::InvocationInfo:
LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
- return Immediate(0u);
+ return Immediate(0U);
case SystemVariable::Tid: {
Node value = Immediate(0);
value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9);
@@ -188,7 +191,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}",
static_cast<u32>(cc));
- if (disable_flow_stack) {
+ if (decompiled) {
break;
}
@@ -200,7 +203,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}",
static_cast<u32>(cc));
- if (disable_flow_stack) {
+ if (decompiled) {
break;
}
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 @@
// Refer to the license.txt file included.
#include <memory>
+#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
#include "video_core/gpu_asynch.h"
#include "video_core/gpu_synch.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
+#ifdef HAS_VULKAN
+#include "video_core/renderer_vulkan/renderer_vulkan.h"
+#endif
#include "video_core/video_core.h"
namespace VideoCore {
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
Core::System& system) {
- return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system);
+ switch (Settings::values.renderer_backend) {
+ case Settings::RendererBackend::OpenGL:
+ return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system);
+#ifdef HAS_VULKAN
+ case Settings::RendererBackend::Vulkan:
+ return std::make_unique<Vulkan::RendererVulkan>(emu_window, system);
+#endif
+ default:
+ return nullptr;
+ }
}
std::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() {
impl->SerializeSection(Telemetry::FieldType::Session, "Session");
impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
+ impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
auto content = impl->TopSection().dump();
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)
copy_yuzu_SDL_deps(yuzu)
copy_yuzu_unicorn_deps(yuzu)
endif()
+
+if (ENABLE_VULKAN)
+ target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
+ target_compile_definitions(yuzu PRIVATE HAS_VULKAN)
+endif()
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 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <glad/glad.h>
+
#include <QApplication>
#include <QHBoxLayout>
#include <QKeyEvent>
+#include <QMessageBox>
#include <QOffscreenSurface>
#include <QOpenGLWindow>
#include <QPainter>
#include <QScreen>
+#include <QStringList>
#include <QWindow>
+#ifdef HAS_VULKAN
+#include <QVulkanWindow>
+#endif
+
#include <fmt/format.h>
+
+#include "common/assert.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
+#include "core/frontend/scope_acquire_window_context.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
@@ -114,19 +125,10 @@ private:
QOpenGLContext context;
};
-// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
-// context.
-// The corresponding functionality is handled in EmuThread instead
-class GGLWidgetInternal : public QOpenGLWindow {
+class GWidgetInternal : public QWindow {
public:
- GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
- : QOpenGLWindow(shared_context), parent(parent) {}
-
- void paintEvent(QPaintEvent* ev) override {
- if (do_painting) {
- QPainter painter(this);
- }
- }
+ GWidgetInternal(GRenderWindow* parent) : parent(parent) {}
+ virtual ~GWidgetInternal() = default;
void resizeEvent(QResizeEvent* ev) override {
parent->OnClientAreaResized(ev->size().width(), ev->size().height());
@@ -182,11 +184,47 @@ public:
do_painting = true;
}
+ std::pair<unsigned, unsigned> GetSize() const {
+ return std::make_pair(width(), height());
+ }
+
+protected:
+ bool IsPaintingEnabled() const {
+ return do_painting;
+ }
+
private:
GRenderWindow* parent;
- bool do_painting;
+ bool do_painting = false;
+};
+
+// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
+// context.
+// The corresponding functionality is handled in EmuThread instead
+class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow {
+public:
+ GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
+ : GWidgetInternal(parent), QOpenGLWindow(shared_context) {}
+ ~GGLWidgetInternal() override = default;
+
+ void paintEvent(QPaintEvent* ev) override {
+ if (IsPaintingEnabled()) {
+ QPainter painter(this);
+ }
+ }
};
+#ifdef HAS_VULKAN
+class GVKWidgetInternal final : public GWidgetInternal {
+public:
+ GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) {
+ setSurfaceType(QSurface::SurfaceType::VulkanSurface);
+ setVulkanInstance(instance);
+ }
+ ~GVKWidgetInternal() override = default;
+};
+#endif
+
GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
: QWidget(parent), emu_thread(emu_thread) {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
@@ -201,9 +239,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
GRenderWindow::~GRenderWindow() {
InputCommon::Shutdown();
+
+ // Avoid an unordered destruction that generates a segfault
+ delete child;
}
void GRenderWindow::moveContext() {
+ if (!context) {
+ return;
+ }
DoneCurrent();
// If the thread started running, move the GL Context to the new thread. Otherwise, move it
@@ -215,8 +259,9 @@ void GRenderWindow::moveContext() {
}
void GRenderWindow::SwapBuffers() {
- context->swapBuffers(child);
-
+ if (context) {
+ context->swapBuffers(child);
+ }
if (!first_frame) {
first_frame = true;
emit FirstFrameDisplayed();
@@ -224,15 +269,38 @@ void GRenderWindow::SwapBuffers() {
}
void GRenderWindow::MakeCurrent() {
- context->makeCurrent(child);
+ if (context) {
+ context->makeCurrent(child);
+ }
}
void GRenderWindow::DoneCurrent() {
- context->doneCurrent();
+ if (context) {
+ context->doneCurrent();
+ }
}
void GRenderWindow::PollEvents() {}
+bool GRenderWindow::IsShown() const {
+ return !isMinimized();
+}
+
+void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const {
+#ifdef HAS_VULKAN
+ const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
+ const VkInstance instance_copy = vk_instance->vkInstance();
+ const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child);
+
+ std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
+ std::memcpy(instance, &instance_copy, sizeof(instance_copy));
+ std::memcpy(surface, &surface_copy, sizeof(surface_copy));
+#else
+ UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
+#endif
+}
+
// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
//
// Older versions get the window size (density independent pixels),
@@ -241,10 +309,9 @@ void GRenderWindow::PollEvents() {}
void GRenderWindow::OnFramebufferSizeChanged() {
// Screen changes potentially incur a change in screen DPI, hence we should update the
// framebuffer size
- const qreal pixel_ratio = GetWindowPixelRatio();
- const u32 width = child->QPaintDevice::width() * pixel_ratio;
- const u32 height = child->QPaintDevice::height() * pixel_ratio;
- UpdateCurrentFramebufferLayout(width, height);
+ const qreal pixelRatio{GetWindowPixelRatio()};
+ const auto size{child->GetSize()};
+ UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio);
}
void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
@@ -290,7 +357,7 @@ qreal GRenderWindow::GetWindowPixelRatio() const {
}
std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
- const qreal pixel_ratio = GetWindowPixelRatio();
+ const qreal pixel_ratio{GetWindowPixelRatio()};
return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
}
@@ -356,50 +423,46 @@ std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedCont
return std::make_unique<GGLContext>(context.get());
}
-void GRenderWindow::InitRenderTarget() {
+bool GRenderWindow::InitRenderTarget() {
shared_context.reset();
context.reset();
-
- delete child;
- child = nullptr;
-
- delete container;
- container = nullptr;
-
- delete layout();
+ if (child) {
+ delete child;
+ }
+ if (container) {
+ delete container;
+ }
+ if (layout()) {
+ delete layout();
+ }
first_frame = false;
- // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
- // WA_DontShowOnScreen, WA_DeleteOnClose
- QSurfaceFormat fmt;
- fmt.setVersion(4, 3);
- fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
- fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
- // TODO: expose a setting for buffer value (ie default/single/double/triple)
- fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
- shared_context = std::make_unique<QOpenGLContext>();
- shared_context->setFormat(fmt);
- shared_context->create();
- context = std::make_unique<QOpenGLContext>();
- context->setShareContext(shared_context.get());
- context->setFormat(fmt);
- context->create();
- fmt.setSwapInterval(0);
+ switch (Settings::values.renderer_backend) {
+ case Settings::RendererBackend::OpenGL:
+ if (!InitializeOpenGL()) {
+ return false;
+ }
+ break;
+ case Settings::RendererBackend::Vulkan:
+ if (!InitializeVulkan()) {
+ return false;
+ }
+ break;
+ }
- child = new GGLWidgetInternal(this, shared_context.get());
container = QWidget::createWindowContainer(child, this);
-
QBoxLayout* layout = new QHBoxLayout(this);
+
layout->addWidget(container);
layout->setMargin(0);
setLayout(layout);
- // Reset minimum size to avoid unwanted resizes when this function is called for a second time.
+ // Reset minimum required size to avoid resizing issues on the main window after restarting.
setMinimumSize(1, 1);
- // Show causes the window to actually be created and the OpenGL context as well, but we don't
- // want the widget to be shown yet, so immediately hide it.
+ // Show causes the window to actually be created and the gl context as well, but we don't want
+ // the widget to be shown yet, so immediately hide it.
show();
hide();
@@ -410,9 +473,17 @@ void GRenderWindow::InitRenderTarget() {
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
- NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height()));
+ NotifyClientAreaSizeChanged(child->GetSize());
BackupGeometry();
+
+ if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
+ if (!LoadOpenGL()) {
+ return false;
+ }
+ }
+
+ return true;
}
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
@@ -441,6 +512,113 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
setMinimumSize(minimal_size.first, minimal_size.second);
}
+bool GRenderWindow::InitializeOpenGL() {
+ // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
+ // WA_DontShowOnScreen, WA_DeleteOnClose
+ QSurfaceFormat fmt;
+ fmt.setVersion(4, 3);
+ fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
+ fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
+ // TODO: expose a setting for buffer value (ie default/single/double/triple)
+ fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
+ shared_context = std::make_unique<QOpenGLContext>();
+ shared_context->setFormat(fmt);
+ shared_context->create();
+ context = std::make_unique<QOpenGLContext>();
+ context->setShareContext(shared_context.get());
+ context->setFormat(fmt);
+ context->create();
+ fmt.setSwapInterval(false);
+
+ child = new GGLWidgetInternal(this, shared_context.get());
+ return true;
+}
+
+bool GRenderWindow::InitializeVulkan() {
+#ifdef HAS_VULKAN
+ vk_instance = std::make_unique<QVulkanInstance>();
+ vk_instance->setApiVersion(QVersionNumber(1, 1, 0));
+ vk_instance->setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect);
+ if (Settings::values.renderer_debug) {
+ const auto supported_layers{vk_instance->supportedLayers()};
+ const bool found =
+ std::find_if(supported_layers.begin(), supported_layers.end(), [](const auto& layer) {
+ constexpr const char searched_layer[] = "VK_LAYER_LUNARG_standard_validation";
+ return layer.name == searched_layer;
+ });
+ if (found) {
+ vk_instance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
+ vk_instance->setExtensions(QByteArrayList() << VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+ }
+ }
+ if (!vk_instance->create()) {
+ QMessageBox::critical(
+ this, tr("Error while initializing Vulkan 1.1!"),
+ tr("Your OS doesn't seem to support Vulkan 1.1 instances, or you do not have the "
+ "latest graphics drivers."));
+ return false;
+ }
+
+ child = new GVKWidgetInternal(this, vk_instance.get());
+ return true;
+#else
+ QMessageBox::critical(this, tr("Vulkan not available!"),
+ tr("yuzu has not been compiled with Vulkan support."));
+ return false;
+#endif
+}
+
+bool GRenderWindow::LoadOpenGL() {
+ Core::Frontend::ScopeAcquireWindowContext acquire_context{*this};
+ if (!gladLoadGL()) {
+ QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
+ tr("Your GPU may not support OpenGL 4.3, or you do not have the "
+ "latest graphics driver."));
+ return false;
+ }
+
+ QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
+ if (!unsupported_gl_extensions.empty()) {
+ QMessageBox::critical(
+ this, tr("Error while initializing OpenGL!"),
+ tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
+ "have the latest graphics driver.<br><br>Unsupported extensions:<br>") +
+ unsupported_gl_extensions.join(QStringLiteral("<br>")));
+ return false;
+ }
+ return true;
+}
+
+QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
+ QStringList unsupported_ext;
+
+ if (!GLAD_GL_ARB_buffer_storage)
+ unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
+ if (!GLAD_GL_ARB_direct_state_access)
+ unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
+ if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
+ unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
+ if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
+ unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
+ if (!GLAD_GL_ARB_multi_bind)
+ unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
+ if (!GLAD_GL_ARB_clip_control)
+ unsupported_ext.append(QStringLiteral("ARB_clip_control"));
+
+ // Extensions required to support some texture formats.
+ if (!GLAD_GL_EXT_texture_compression_s3tc)
+ unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
+ if (!GLAD_GL_ARB_texture_compression_rgtc)
+ unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
+ if (!GLAD_GL_ARB_depth_buffer_float)
+ unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
+
+ for (const QString& ext : unsupported_ext)
+ LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
+
+ return unsupported_ext;
+}
+
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
this->emu_thread = emu_thread;
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 @@
#include <atomic>
#include <condition_variable>
#include <mutex>
+
#include <QImage>
#include <QThread>
#include <QWidget>
+
+#include "common/thread.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
class QKeyEvent;
class QScreen;
class QTouchEvent;
+class QStringList;
+class QSurface;
+class QOpenGLContext;
+#ifdef HAS_VULKAN
+class QVulkanInstance;
+#endif
+class GWidgetInternal;
class GGLWidgetInternal;
+class GVKWidgetInternal;
class GMainWindow;
class GRenderWindow;
class QSurface;
@@ -123,6 +134,9 @@ public:
void MakeCurrent() override;
void DoneCurrent() override;
void PollEvents() override;
+ bool IsShown() const override;
+ void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
void ForwardKeyPressEvent(QKeyEvent* event);
@@ -142,7 +156,7 @@ public:
void OnClientAreaResized(u32 width, u32 height);
- void InitRenderTarget();
+ bool InitRenderTarget();
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
@@ -165,10 +179,13 @@ private:
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
- QWidget* container = nullptr;
- GGLWidgetInternal* child = nullptr;
+ bool InitializeOpenGL();
+ bool InitializeVulkan();
+ bool LoadOpenGL();
+ QStringList GetUnsupportedGLExtensions() const;
- QByteArray geometry;
+ QWidget* container = nullptr;
+ GWidgetInternal* child = nullptr;
EmuThread* emu_thread;
// Context that backs the GGLWidgetInternal (and will be used by core to render)
@@ -177,9 +194,14 @@ private:
// current
std::unique_ptr<QOpenGLContext> shared_context;
+#ifdef HAS_VULKAN
+ std::unique_ptr<QVulkanInstance> vk_instance;
+#endif
+
/// Temporary storage of the screenshot taken
QImage screenshot_image;
+ QByteArray geometry;
bool first_frame = false;
protected:
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() {
void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
+ Settings::values.renderer_backend =
+ static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt());
+ Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool();
+ Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt();
Settings::values.resolution_factor =
ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat();
Settings::values.use_frame_limit =
@@ -1056,6 +1060,9 @@ void Config::SavePathValues() {
void Config::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
+ WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0);
+ WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false);
+ WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0);
WriteSetting(QStringLiteral("resolution_factor"),
static_cast<double>(Settings::values.resolution_factor), 1.0);
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() {
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
ui->reporting_services->setChecked(Settings::values.reporting_services);
ui->quest_flag->setChecked(Settings::values.quest_flag);
+ ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug);
}
void ConfigureDebug::ApplyConfiguration() {
@@ -46,6 +48,7 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
Settings::values.reporting_services = ui->reporting_services->isChecked();
Settings::values.quest_flag = ui->quest_flag->isChecked();
+ Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
Debugger::ToggleConsole();
Log::Filter filter;
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 @@
<x>0</x>
<y>0</y>
<width>400</width>
- <height>474</height>
+ <height>467</height>
</rect>
</property>
<property name="windowTitle">
@@ -103,6 +103,80 @@
</item>
</layout>
</item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Homebrew</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Arguments String</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="homebrew_args_edit"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Graphics</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QCheckBox" name="enable_graphics_debugging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="whatsThis">
+ <string>When checked, the graphics API enters in a slower debugging mode</string>
+ </property>
+ <property name="text">
+ <string>Enable Graphics Debugging</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="title">
+ <string>Dump</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QCheckBox" name="dump_decompressed_nso">
+ <property name="whatsThis">
+ <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
+ </property>
+ <property name="text">
+ <string>Dump Decompressed NSOs</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="dump_exefs">
+ <property name="whatsThis">
+ <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
+ </property>
+ <property name="text">
+ <string>Dump ExeFS</string>
+ </property>
+ </widget>
+ </item>
<item>
<widget class="QCheckBox" name="reporting_services">
<property name="text">
@@ -129,11 +203,11 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_5">
+ <widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Advanced</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QCheckBox" name="quest_flag">
<property name="text">
@@ -145,29 +219,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Homebrew</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Arguments String</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="homebrew_args_edit"/>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -185,6 +236,19 @@
</item>
</layout>
</widget>
+ <tabstops>
+ <tabstop>toggle_gdbstub</tabstop>
+ <tabstop>gdbport_spinbox</tabstop>
+ <tabstop>log_filter_edit</tabstop>
+ <tabstop>toggle_console</tabstop>
+ <tabstop>open_log_button</tabstop>
+ <tabstop>homebrew_args_edit</tabstop>
+ <tabstop>enable_graphics_debugging</tabstop>
+ <tabstop>dump_decompressed_nso</tabstop>
+ <tabstop>dump_exefs</tabstop>
+ <tabstop>reporting_services</tabstop>
+ <tabstop>quest_flag</tabstop>
+ </tabstops>
<resources/>
<connections>
<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 @@
// Refer to the license.txt file included.
#include <QColorDialog>
+#include <QComboBox>
+#ifdef HAS_VULKAN
+#include <QVulkanInstance>
+#endif
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics.h"
@@ -51,10 +58,18 @@ Resolution FromResolutionFactor(float factor) {
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
+ vulkan_device = Settings::values.vulkan_device;
+ RetrieveVulkanDevices();
+
ui->setupUi(this);
SetConfiguration();
+ connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
+ [this] { UpdateDeviceComboBox(); });
+ connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
+ [this](int device) { UpdateDeviceSelection(device); });
+
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
const QColor new_bg_color = QColorDialog::getColor(bg_color);
if (!new_bg_color.isValid()) {
@@ -64,11 +79,22 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
});
}
+void ConfigureGraphics::UpdateDeviceSelection(int device) {
+ if (device == -1) {
+ return;
+ }
+ if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
+ vulkan_device = device;
+ }
+}
+
ConfigureGraphics::~ConfigureGraphics() = default;
void ConfigureGraphics::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
+ ui->api->setEnabled(runtime_lock);
+ ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend));
ui->resolution_factor_combobox->setCurrentIndex(
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->use_disk_shader_cache->setEnabled(runtime_lock);
@@ -80,9 +106,12 @@ void ConfigureGraphics::SetConfiguration() {
ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue));
+ UpdateDeviceComboBox();
}
void ConfigureGraphics::ApplyConfiguration() {
+ Settings::values.renderer_backend = GetCurrentGraphicsBackend();
+ Settings::values.vulkan_device = vulkan_device;
Settings::values.resolution_factor =
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
@@ -116,3 +145,68 @@ void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
const QIcon color_icon(pixmap);
ui->bg_button->setIcon(color_icon);
}
+
+void ConfigureGraphics::UpdateDeviceComboBox() {
+ ui->device->clear();
+
+ bool enabled = false;
+ switch (GetCurrentGraphicsBackend()) {
+ case Settings::RendererBackend::OpenGL:
+ ui->device->addItem(tr("OpenGL Graphics Device"));
+ enabled = false;
+ break;
+ case Settings::RendererBackend::Vulkan:
+ for (const auto device : vulkan_devices) {
+ ui->device->addItem(device);
+ }
+ ui->device->setCurrentIndex(vulkan_device);
+ enabled = !vulkan_devices.empty();
+ break;
+ }
+ ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());
+}
+
+void ConfigureGraphics::RetrieveVulkanDevices() {
+#ifdef HAS_VULKAN
+ QVulkanInstance instance;
+ instance.setApiVersion(QVersionNumber(1, 1, 0));
+ if (!instance.create()) {
+ LOG_INFO(Frontend, "Vulkan 1.1 not available");
+ return;
+ }
+ const auto vkEnumeratePhysicalDevices{reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
+ instance.getInstanceProcAddr("vkEnumeratePhysicalDevices"))};
+ if (vkEnumeratePhysicalDevices == nullptr) {
+ LOG_INFO(Frontend, "Failed to get pointer to vkEnumeratePhysicalDevices");
+ return;
+ }
+ u32 physical_device_count;
+ if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count, nullptr) !=
+ VK_SUCCESS) {
+ LOG_INFO(Frontend, "Failed to get physical devices count");
+ return;
+ }
+ std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
+ if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count,
+ physical_devices.data()) != VK_SUCCESS) {
+ LOG_INFO(Frontend, "Failed to get physical devices");
+ return;
+ }
+
+ const auto vkGetPhysicalDeviceProperties{reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
+ instance.getInstanceProcAddr("vkGetPhysicalDeviceProperties"))};
+ if (vkGetPhysicalDeviceProperties == nullptr) {
+ LOG_INFO(Frontend, "Failed to get pointer to vkGetPhysicalDeviceProperties");
+ return;
+ }
+ for (const auto physical_device : physical_devices) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(physical_device, &properties);
+ vulkan_devices.push_back(QString::fromUtf8(properties.deviceName));
+ }
+#endif
+}
+
+Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
+ return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
+}
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 @@
#pragma once
#include <memory>
+#include <vector>
+#include <QString>
#include <QWidget>
+#include "core/settings.h"
namespace Ui {
class ConfigureGraphics;
@@ -27,7 +30,16 @@ private:
void SetConfiguration();
void UpdateBackgroundColorButton(QColor color);
+ void UpdateDeviceComboBox();
+ void UpdateDeviceSelection(int device);
+
+ void RetrieveVulkanDevices();
+
+ Settings::RendererBackend GetCurrentGraphicsBackend() const;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
+
+ std::vector<QString> vulkan_devices;
+ u32 vulkan_device{};
};
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 @@
<x>0</x>
<y>0</y>
<width>400</width>
- <height>300</height>
+ <height>321</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_1">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_3">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>API Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>API:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="api">
+ <item>
+ <property name="text">
+ <string notr="true">OpenGL</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Vulkan</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Device:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="device"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
- <string>Graphics</string>
+ <string>Graphics Settings</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="use_disk_shader_cache">
<property name="text">
@@ -30,16 +78,16 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="use_accurate_gpu_emulation">
+ <widget class="QCheckBox" name="use_asynchronous_gpu_emulation">
<property name="text">
- <string>Use accurate GPU emulation (slow)</string>
+ <string>Use asynchronous GPU emulation</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="use_asynchronous_gpu_emulation">
+ <widget class="QCheckBox" name="use_accurate_gpu_emulation">
<property name="text">
- <string>Use asynchronous GPU emulation</string>
+ <string>Use accurate GPU emulation (slow)</string>
</property>
</widget>
</item>
@@ -51,11 +99,11 @@
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
- <string>Internal Resolution</string>
+ <string>Internal Resolution:</string>
</property>
</widget>
</item>
@@ -91,7 +139,7 @@
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="bg_label">
<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() {
#endif
}
-QStringList GMainWindow::GetUnsupportedGLExtensions() {
- QStringList unsupported_ext;
-
- if (!GLAD_GL_ARB_buffer_storage) {
- unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
- }
- if (!GLAD_GL_ARB_direct_state_access) {
- unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
- }
- if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) {
- unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
- }
- if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) {
- unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
- }
- if (!GLAD_GL_ARB_multi_bind) {
- unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
- }
- if (!GLAD_GL_ARB_clip_control) {
- unsupported_ext.append(QStringLiteral("ARB_clip_control"));
- }
-
- // Extensions required to support some texture formats.
- if (!GLAD_GL_EXT_texture_compression_s3tc) {
- unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
- }
- if (!GLAD_GL_ARB_texture_compression_rgtc) {
- unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
- }
- if (!GLAD_GL_ARB_depth_buffer_float) {
- unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
- }
-
- for (const QString& ext : unsupported_ext) {
- LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
- }
-
- return unsupported_ext;
-}
-
bool GMainWindow::LoadROM(const QString& filename) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr)
ShutdownGame();
- render_window->InitRenderTarget();
-
- {
- Core::Frontend::ScopeAcquireWindowContext acquire_context{*render_window};
- if (!gladLoadGL()) {
- QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3 Core!"),
- tr("Your GPU may not support OpenGL 4.3, or you do not "
- "have the latest graphics driver."));
- return false;
- }
- }
-
- const QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
- if (!unsupported_gl_extensions.empty()) {
- QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"),
- tr("Your GPU may not support one or more required OpenGL"
- "extensions. Please ensure you have the latest graphics "
- "driver.<br><br>Unsupported extensions:<br>") +
- unsupported_gl_extensions.join(QStringLiteral("<br>")));
+ if (!render_window->InitRenderTarget()) {
return false;
}
@@ -980,7 +922,9 @@ void GMainWindow::BootGame(const QString& filename) {
// Create and start the emulation thread
emu_thread = std::make_unique<EmuThread>(render_window);
emit EmulationStarting(emu_thread.get());
- render_window->moveContext();
+ if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
+ render_window->moveContext();
+ }
emu_thread->start();
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -2195,6 +2139,18 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event);
}
+void GMainWindow::keyPressEvent(QKeyEvent* event) {
+ if (render_window) {
+ render_window->ForwardKeyPressEvent(event);
+ }
+}
+
+void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
+ if (render_window) {
+ render_window->ForwardKeyReleaseEvent(event);
+ }
+}
+
static bool IsSingleFileDropEvent(QDropEvent* event) {
const QMimeData* mimeData = event->mimeData();
return mimeData->hasUrls() && mimeData->urls().length() == 1;
@@ -2227,18 +2183,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
event->acceptProposedAction();
}
-void GMainWindow::keyPressEvent(QKeyEvent* event) {
- if (render_window) {
- render_window->ForwardKeyPressEvent(event);
- }
-}
-
-void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
- if (render_window) {
- render_window->ForwardKeyReleaseEvent(event);
- }
-}
-
bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
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:
void PreventOSSleep();
void AllowOSSleep();
- QStringList GetUnsupportedGLExtensions();
bool LoadROM(const QString& filename);
void BootGame(const QString& filename);
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
emu_window/emu_window_sdl2_gl.h
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
+ emu_window/emu_window_sdl2_gl.cpp
+ emu_window/emu_window_sdl2_gl.h
resource.h
yuzu.cpp
yuzu.rc
)
+if (ENABLE_VULKAN)
+ target_sources(yuzu-cmd PRIVATE
+ emu_window/emu_window_sdl2_vk.cpp
+ emu_window/emu_window_sdl2_vk.h)
+
+ target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)
+ target_compile_definitions(yuzu-cmd PRIVATE HAS_VULKAN)
+endif()
+
create_target_directory_groups(yuzu-cmd)
target_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() {
Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
// Renderer
+ const int renderer_backend = sdl2_config->GetInteger(
+ "Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL));
+ Settings::values.renderer_backend = static_cast<Settings::RendererBackend>(renderer_backend);
+ Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "debug", false);
+ Settings::values.vulkan_device = sdl2_config->GetInteger("Renderer", "vulkan_device", 0);
+
Settings::values.resolution_factor =
static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
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=
use_multi_core=
[Renderer]
+# Which backend API to use.
+# 0 (default): OpenGL, 1: Vulkan
+backend =
+
+# Enable graphics API debugging mode.
+# 0 (default): Disabled, 1: Enabled
+debug =
+
+# Which Vulkan physical device to use (defaults to 0)
+vulkan_device =
+
# Whether to use software or hardware rendering.
# 0: Software, 1 (default): Hardware
use_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 {
return is_open;
}
+bool EmuWindow_SDL2::IsShown() const {
+ return is_shown;
+}
+
void EmuWindow_SDL2::OnResize() {
int width, height;
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:
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
+ /// Returns if window is shown (not minimized)
+ bool IsShown() const override;
+
protected:
/// Called by PollEvents when a key is pressed or released.
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 @@
#include <SDL.h>
#include <fmt/format.h>
#include <glad/glad.h>
+#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "common/string_util.h"
@@ -151,6 +152,12 @@ void EmuWindow_SDL2_GL::DoneCurrent() {
SDL_GL_MakeCurrent(render_window, nullptr);
}
+void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const {
+ // Should not have been called from OpenGL
+ UNREACHABLE();
+}
+
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
return std::make_unique<SDLGLContext>();
}
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:
/// Releases the GL context from the caller thread
void DoneCurrent() override;
+ /// Ignored in OpenGL
+ void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const override;
+
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
private:
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 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <SDL.h>
+#include <SDL_vulkan.h>
+#include <fmt/format.h>
+#include <vulkan/vulkan.h>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/scm_rev.h"
+#include "core/settings.h"
+#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
+
+EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
+ if (SDL_Vulkan_LoadLibrary(nullptr) != 0) {
+ LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError());
+ exit(EXIT_FAILURE);
+ }
+
+ vkGetInstanceProcAddr =
+ reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
+ if (vkGetInstanceProcAddr == nullptr) {
+ LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
+ exit(EXIT_FAILURE);
+ }
+
+ const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
+ Common::g_scm_branch, Common::g_scm_desc);
+ render_window =
+ SDL_CreateWindow(window_title.c_str(),
+ SDL_WINDOWPOS_UNDEFINED, // x position
+ SDL_WINDOWPOS_UNDEFINED, // y position
+ Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
+ SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_VULKAN);
+
+ const bool use_standard_layers = UseStandardLayers(vkGetInstanceProcAddr);
+
+ u32 extra_ext_count{};
+ if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, NULL)) {
+ LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions count from SDL! {}",
+ SDL_GetError());
+ exit(1);
+ }
+
+ auto extra_ext_names = std::make_unique<const char* []>(extra_ext_count);
+ if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, extra_ext_names.get())) {
+ LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions from SDL! {}", SDL_GetError());
+ exit(1);
+ }
+ std::vector<const char*> enabled_extensions;
+ enabled_extensions.insert(enabled_extensions.begin(), extra_ext_names.get(),
+ extra_ext_names.get() + extra_ext_count);
+
+ std::vector<const char*> enabled_layers;
+ if (use_standard_layers) {
+ enabled_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+ enabled_layers.push_back("VK_LAYER_LUNARG_standard_validation");
+ }
+
+ VkApplicationInfo app_info{};
+ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ app_info.apiVersion = VK_API_VERSION_1_1;
+ app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
+ app_info.pApplicationName = "yuzu-emu";
+ app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
+ app_info.pEngineName = "yuzu-emu";
+
+ VkInstanceCreateInfo instance_ci{};
+ instance_ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ instance_ci.pApplicationInfo = &app_info;
+ instance_ci.enabledExtensionCount = static_cast<u32>(enabled_extensions.size());
+ instance_ci.ppEnabledExtensionNames = enabled_extensions.data();
+ if (Settings::values.renderer_debug) {
+ instance_ci.enabledLayerCount = static_cast<u32>(enabled_layers.size());
+ instance_ci.ppEnabledLayerNames = enabled_layers.data();
+ }
+
+ const auto vkCreateInstance =
+ reinterpret_cast<PFN_vkCreateInstance>(vkGetInstanceProcAddr(nullptr, "vkCreateInstance"));
+ if (vkCreateInstance == nullptr ||
+ vkCreateInstance(&instance_ci, nullptr, &vk_instance) != VK_SUCCESS) {
+ LOG_CRITICAL(Frontend, "Failed to create Vulkan instance!");
+ exit(EXIT_FAILURE);
+ }
+
+ vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(
+ vkGetInstanceProcAddr(vk_instance, "vkDestroyInstance"));
+ if (vkDestroyInstance == nullptr) {
+ LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!SDL_Vulkan_CreateSurface(render_window, vk_instance, &vk_surface)) {
+ LOG_CRITICAL(Frontend, "Failed to create Vulkan surface! {}", SDL_GetError());
+ exit(EXIT_FAILURE);
+ }
+
+ OnResize();
+ OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
+ SDL_PumpEvents();
+ LOG_INFO(Frontend, "yuzu Version: {} | {}-{} (Vulkan)", Common::g_build_name,
+ Common::g_scm_branch, Common::g_scm_desc);
+}
+
+EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() {
+ vkDestroyInstance(vk_instance, nullptr);
+}
+
+void EmuWindow_SDL2_VK::SwapBuffers() {}
+
+void EmuWindow_SDL2_VK::MakeCurrent() {
+ // Unused on Vulkan
+}
+
+void EmuWindow_SDL2_VK::DoneCurrent() {
+ // Unused on Vulkan
+}
+
+void EmuWindow_SDL2_VK::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const {
+ const auto instance_proc_addr = vkGetInstanceProcAddr;
+ std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
+ std::memcpy(instance, &vk_instance, sizeof(vk_instance));
+ std::memcpy(surface, &vk_surface, sizeof(vk_surface));
+}
+
+std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
+ return nullptr;
+}
+
+bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const {
+ if (!Settings::values.renderer_debug) {
+ return false;
+ }
+
+ const auto vkEnumerateInstanceLayerProperties =
+ reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
+ vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceLayerProperties"));
+ if (vkEnumerateInstanceLayerProperties == nullptr) {
+ LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
+ return false;
+ }
+
+ u32 available_layers_count{};
+ if (vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr) != VK_SUCCESS) {
+ LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
+ return false;
+ }
+ std::vector<VkLayerProperties> layers(available_layers_count);
+ if (vkEnumerateInstanceLayerProperties(&available_layers_count, layers.data()) != VK_SUCCESS) {
+ LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
+ return false;
+ }
+
+ return std::find_if(layers.begin(), layers.end(), [&](const auto& layer) {
+ return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation");
+ }) != layers.end();
+}
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 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vulkan/vulkan.h>
+#include "core/frontend/emu_window.h"
+#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+
+class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
+public:
+ explicit EmuWindow_SDL2_VK(bool fullscreen);
+ ~EmuWindow_SDL2_VK();
+
+ /// Swap buffers to display the next frame
+ void SwapBuffers() override;
+
+ /// Makes the graphics context current for the caller thread
+ void MakeCurrent() override;
+
+ /// Releases the GL context from the caller thread
+ void DoneCurrent() override;
+
+ /// Retrieves Vulkan specific handlers from the window
+ void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const override;
+
+ std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
+
+private:
+ bool UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const;
+
+ VkInstance vk_instance{};
+ VkSurfaceKHR vk_surface{};
+
+ PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
+ PFN_vkDestroyInstance vkDestroyInstance{};
+};
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 @@
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
+#ifdef HAS_VULKAN
+#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
+#endif
#include "core/file_sys/registered_cache.h"
@@ -174,7 +177,20 @@ int main(int argc, char** argv) {
Settings::values.use_gdbstub = use_gdbstub;
Settings::Apply();
- std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2_GL>(fullscreen)};
+ std::unique_ptr<EmuWindow_SDL2> emu_window;
+ switch (Settings::values.renderer_backend) {
+ case Settings::RendererBackend::OpenGL:
+ emu_window = std::make_unique<EmuWindow_SDL2_GL>(fullscreen);
+ break;
+ case Settings::RendererBackend::Vulkan:
+#ifdef HAS_VULKAN
+ emu_window = std::make_unique<EmuWindow_SDL2_VK>(fullscreen);
+ break;
+#else
+ LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
+ return 1;
+#endif
+ }
if (!Settings::values.use_multi_core) {
// 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 @@
#include <algorithm>
#include <cstdlib>
#include <string>
+
+#include <fmt/format.h>
+
#define SDL_MAIN_HANDLED
#include <SDL.h>
-#include <fmt/format.h>
+
#include <glad/glad.h>
+
+#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "core/settings.h"
@@ -120,3 +125,11 @@ void EmuWindow_SDL2_Hide::MakeCurrent() {
void EmuWindow_SDL2_Hide::DoneCurrent() {
SDL_GL_MakeCurrent(render_window, nullptr);
}
+
+bool EmuWindow_SDL2_Hide::IsShown() const {
+ return false;
+}
+
+void EmuWindow_SDL2_Hide::RetrieveVulkanHandlers(void*, void*, void*) const {
+ UNREACHABLE();
+}
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:
/// Releases the GL context from the caller thread
void DoneCurrent() override;
+ /// Whether the screen is being shown or not.
+ bool IsShown() const override;
+
+ /// Retrieves Vulkan specific handlers from the window
+ void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
+ void* surface) const override;
+
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;