diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 4a9912641b..3376eedc58 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/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index ccfed4f2e2..8218b7cd2e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -154,6 +154,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/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp new file mode 100644 index 0000000000..d5032b4328 --- /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 +#include +#include + +#include + +#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 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 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( + instance, nullptr, device->GetDispatchLoader())); + + Report(); + + memory_manager = std::make_unique(*device); + + resource_manager = std::make_unique(*device); + + const auto& framebuffer = render_window.GetFramebufferLayout(); + swapchain = std::make_unique(surface, *device); + swapchain->Create(framebuffer.width, framebuffer.height, false); + + scheduler = std::make_unique(*device, *resource_manager); + + rasterizer = std::make_unique(system, render_window, screen_info, *device, + *resource_manager, *memory_manager, *scheduler); + + blit_screen = std::make_unique(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 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(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(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/video_core.cpp b/src/video_core/video_core.cpp index 8e947394c9..a5f81a8a0c 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 +#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 CreateRenderer(Core::Frontend::EmuWindow& emu_window, Core::System& system) { - return std::make_unique(emu_window, system); + switch (Settings::values.renderer_backend) { + case Settings::RendererBackend::OpenGL: + return std::make_unique(emu_window, system); +#ifdef HAS_VULKAN + case Settings::RendererBackend::Vulkan: + return std::make_unique(emu_window, system); +#endif + default: + return nullptr; + } } std::unique_ptr CreateGPU(Core::System& system) { diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index a3fb91d295..b841e63fa4 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 7490fb718b..67ca590355 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 + #include #include #include +#include #include #include #include #include +#include #include +#ifdef HAS_VULKAN +#include +#endif + #include + +#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,9 +184,43 @@ public: do_painting = true; } + std::pair 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); + } + } +}; + +class GVKWidgetInternal final : public GWidgetInternal { +public: + GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) { + setSurfaceType(QSurface::SurfaceType::VulkanSurface); + setVulkanInstance(instance); + } + ~GVKWidgetInternal() override = default; }; GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) @@ -201,9 +237,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 +257,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 +267,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 +307,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 +355,7 @@ qreal GRenderWindow::GetWindowPixelRatio() const { } std::pair GRenderWindow::ScaleTouch(const QPointF pos) const { - const qreal pixel_ratio = GetWindowPixelRatio(); + const qreal pixel_ratio{GetWindowPixelRatio()}; return {static_cast(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), static_cast(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; } @@ -356,50 +421,46 @@ std::unique_ptr GRenderWindow::CreateSharedCont return std::make_unique(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(); - shared_context->setFormat(fmt); - shared_context->create(); - context = std::make_unique(); - 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 +471,17 @@ void GRenderWindow::InitRenderTarget() { OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); OnFramebufferSizeChanged(); - NotifyClientAreaSizeChanged(std::pair(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 +510,113 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair 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(); + shared_context->setFormat(fmt); + shared_context->create(); + context = std::make_unique(); + 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(); + 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.

Unsupported extensions:
") + + unsupported_gl_extensions.join(QStringLiteral("
"))); + 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 2fc64895f6..71a2fa3213 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -7,17 +7,28 @@ #include #include #include + #include #include #include + +#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 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 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 shared_context; +#ifdef HAS_VULKAN + std::unique_ptr 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/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 90c1f9459d..9631059c75 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 ce49569bb1..e028c4c807 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -7,7 +7,7 @@ 0 0 400 - 474 + 467 @@ -103,44 +103,6 @@ - - - - Enable Verbose Reporting Services - - - - - - - - true - - - - This will be reset automatically when yuzu closes. - - - 20 - - - - - - - - - - Advanced - - - - - - Kiosk (Quest) Mode - - - @@ -167,6 +129,95 @@ + + + + Graphics + + + + + + true + + + When checked, the graphics API enters in a slower debugging mode + + + Enable Graphics Debugging + + + + + + + + + + Dump + + + + + + When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory. + + + Dump Decompressed NSOs + + + + + + + When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory. + + + Dump ExeFS + + + + + + + Enable Verbose Reporting Services + + + + + + + + true + + + + This will be reset automatically when yuzu closes. + + + 20 + + + + + + + + + + Advanced + + + + + + Kiosk (Quest) Mode + + + + + + @@ -185,6 +236,19 @@ + + toggle_gdbstub + gdbport_spinbox + log_filter_edit + toggle_console + open_log_button + homebrew_args_edit + enable_graphics_debugging + dump_decompressed_nso + dump_exefs + reporting_services + quest_flag + diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 2c9e322c94..f57a24e36d 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 +#include +#ifdef HAS_VULKAN +#include +#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(&QComboBox::currentIndexChanged), this, + [this] { UpdateDeviceComboBox(); }); + connect(ui->device, static_cast(&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(Settings::values.renderer_backend)); ui->resolution_factor_combobox->setCurrentIndex( static_cast(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(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( + 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 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( + 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(ui->api->currentIndex()); +} diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index fae28d98e5..7e0596d9ce 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -5,7 +5,10 @@ #pragma once #include +#include +#include #include +#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; QColor bg_color; + + std::vector vulkan_devices; + u32 vulkan_device{}; }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 0309ee3002..e24372204b 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -7,21 +7,69 @@ 0 0 400 - 300 + 321 Form - + - + + + + + API Settings + + + + + + + + API: + + + + + + + + OpenGL + + + + + Vulkan + + + + + + + + + + + + Device: + + + + + + + + + + + - Graphics + Graphics Settings - + @@ -29,13 +77,6 @@ - - - - Use accurate GPU emulation (slow) - - - @@ -43,6 +84,13 @@ + + + + Use accurate GPU emulation (slow) + + + @@ -51,11 +99,11 @@ - + - Internal Resolution + Internal Resolution: @@ -91,7 +139,7 @@ - + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index b5dd3e0d60..4000bf44ad 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.

Unsupported extensions:
") + - unsupported_gl_extensions.join(QStringLiteral("
"))); + 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(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 a56f9a981f..65d4f50bb2 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 b5f06ab9e8..a15719a0f1 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/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index b1c512db1b..e961398855 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 eaa971f77c..b38f566611 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 6fde694a2a..7ffa0ac09f 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 #include #include +#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 EmuWindow_SDL2_GL::CreateSharedContext() const { return std::make_unique(); } 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 630deba937..c753085a83 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 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 0000000000..89e736ef65 --- /dev/null +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -0,0 +1,161 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#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(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(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 enabled_extensions; + enabled_extensions.insert(enabled_extensions.begin(), extra_ext_names.get(), + extra_ext_names.get() + extra_ext_count); + + std::vector 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(enabled_extensions.size()); + instance_ci.ppEnabledExtensionNames = enabled_extensions.data(); + if (Settings::values.renderer_debug) { + instance_ci.enabledLayerCount = static_cast(enabled_layers.size()); + instance_ci.ppEnabledLayerNames = enabled_layers.data(); + } + + const auto vkCreateInstance = + reinterpret_cast(vkGetInstanceProcAddr(nullptr, "vkCreateInstance")); + if (vkCreateInstance == nullptr || + vkCreateInstance(&instance_ci, nullptr, &instance) != VK_SUCCESS) { + LOG_CRITICAL(Frontend, "Failed to create Vulkan instance!"); + exit(EXIT_FAILURE); + } + + vkDestroyInstance = reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkDestroyInstance")); + if (vkDestroyInstance == nullptr) { + LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!"); + exit(EXIT_FAILURE); + } + + if (!SDL_Vulkan_CreateSurface(render_window, instance, &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(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 { + std::memcpy(get_instance_proc_addr, vkGetInstanceProcAddr, sizeof(vkGetInstanceProcAddr)); + std::memcpy(instance, &this->instance, sizeof(this->instance)); + std::memcpy(surface, &this->surface, sizeof(this->surface)); +} + +std::unique_ptr 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( + 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 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 0000000000..f7234841bc --- /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 +#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 CreateSharedContext() const override; + +private: + bool UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const; + + VkInstance instance{}; + VkSurfaceKHR surface{}; + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; + PFN_vkDestroyInstance vkDestroyInstance{}; +}; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 3ee088a912..325795321e 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 emu_window{std::make_unique(fullscreen)}; + std::unique_ptr emu_window; + switch (Settings::values.renderer_backend) { + case Settings::RendererBackend::OpenGL: + emu_window = std::make_unique(fullscreen); + break; + case Settings::RendererBackend::Vulkan: +#ifdef HAS_VULKAN + emu_window = std::make_unique(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 e7fe8decf9..f2cc4a797f 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 #include #include + +#include + #define SDL_MAIN_HANDLED #include -#include + #include + +#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 1a8953c75e..c7fccc0028 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;