From f92cbc55018b5a3d98dd2093354f20c62ace5fda Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Tue, 21 Jan 2020 16:40:53 -0300 Subject: yuzu: Implement Vulkan frontend Adds a Qt and SDL2 frontend for Vulkan. It also finishes the missing bits on Vulkan initialization. --- src/yuzu_cmd/CMakeLists.txt | 11 ++ src/yuzu_cmd/emu_window/emu_window_sdl2.cpp | 4 + src/yuzu_cmd/emu_window/emu_window_sdl2.h | 3 + src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp | 7 ++ src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h | 4 + src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp | 161 +++++++++++++++++++++++++ src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h | 39 ++++++ src/yuzu_cmd/yuzu.cpp | 18 ++- 8 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp create mode 100644 src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h (limited to 'src/yuzu_cmd') 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/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 #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 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 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..89e736ef6 --- /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 000000000..f7234841b --- /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 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 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 -- cgit v1.2.3