From b9ffca1ddfceb99d72eb216cc57dd053d0800bb8 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 4 Jul 2020 20:06:04 +1000 Subject: [PATCH] libretro: Vulkan renderer support --- README.md | 5 +- dep/vulkan-loader/include/vulkan_loader.h | 1 + dep/vulkan-loader/src/vulkan_loader.cpp | 3 +- src/common/vulkan/context.cpp | 70 ++++- src/common/vulkan/context.h | 18 +- src/common/vulkan/util.cpp | 9 + src/common/vulkan/util.h | 1 + src/duckstation-libretro/CMakeLists.txt | 4 +- .../duckstation-libretro.vcxproj | 21 +- .../duckstation-libretro.vcxproj.filters | 2 + .../libretro_d3d11_host_display.cpp | 2 +- .../libretro_host_interface.cpp | 18 +- .../libretro_vulkan_host_display.cpp | 261 ++++++++++++++++++ .../libretro_vulkan_host_display.h | 42 +++ src/frontend-common/vulkan_host_display.cpp | 7 +- src/frontend-common/vulkan_host_display.h | 3 + 16 files changed, 431 insertions(+), 36 deletions(-) create mode 100644 src/duckstation-libretro/libretro_vulkan_host_display.cpp create mode 100644 src/duckstation-libretro/libretro_vulkan_host_display.h diff --git a/README.md b/README.md index 23f250a50..e7051c20a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # DuckStation - PlayStation 1, aka. PSX Emulator **Discord Server:** https://discord.gg/Buktv3t -**Latest Windows and Linux (AppImage) Builds:** https://github.com/stenzek/duckstation/releases/tag/latest +**Latest Windows, Linux (AppImage), and Libretro Builds:** https://github.com/stenzek/duckstation/releases/tag/latest **Game Compatibility List:** https://docs.google.com/spreadsheets/d/1H66MxViRjjE5f8hOl5RQmF5woS1murio2dsLn14kEqo/edit?usp=sharing @@ -11,6 +11,7 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c ## Latest News +- 2020/07/04: Vulkan renderer now available in libretro core. - 2020/07/02: Now available as a libretro core. - 2020/07/01: Lightgun support with custom crosshairs. - 2020/06/19: Vulkan hardware renderer added. @@ -197,7 +198,7 @@ Hotkeys: ## Libretro Core -DuckStation is available as a libretro core, which can be loaded into a frontend such as RetroArch. Currently, only the D3D11 and OpenGL renderers are available, Vulkan will be available soon. It supports most features of the full frontend, within the constraints and limitations of being a libretro core. +DuckStation is available as a libretro core, which can be loaded into a frontend such as RetroArch. It supports most features of the full frontend, within the constraints and limitations of being a libretro core. Prebuilt binaries for Windows and 64-bit Linux can be found on the releases page. Direct links: - 64-bit Windows: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-libretro-windows-x64-release.7z diff --git a/dep/vulkan-loader/include/vulkan_loader.h b/dep/vulkan-loader/include/vulkan_loader.h index 70e9480aa..df0ae6c95 100644 --- a/dep/vulkan-loader/include/vulkan_loader.h +++ b/dep/vulkan-loader/include/vulkan_loader.h @@ -97,5 +97,6 @@ bool LoadVulkanLibrary(); bool LoadVulkanInstanceFunctions(VkInstance instance); bool LoadVulkanDeviceFunctions(VkDevice device); void UnloadVulkanLibrary(); +void ResetVulkanLibraryFunctionPointers(); } // namespace Vulkan diff --git a/dep/vulkan-loader/src/vulkan_loader.cpp b/dep/vulkan-loader/src/vulkan_loader.cpp index 7d6ccb722..c1be6ae57 100644 --- a/dep/vulkan-loader/src/vulkan_loader.cpp +++ b/dep/vulkan-loader/src/vulkan_loader.cpp @@ -23,7 +23,8 @@ #undef VULKAN_MODULE_ENTRY_POINT namespace Vulkan { -static void ResetVulkanLibraryFunctionPointers() + +void ResetVulkanLibraryFunctionPointers() { #define VULKAN_MODULE_ENTRY_POINT(name, required) name = nullptr; #define VULKAN_INSTANCE_ENTRY_POINT(name, required) name = nullptr; diff --git a/src/common/vulkan/context.cpp b/src/common/vulkan/context.cpp index ab3391236..19af7c078 100644 --- a/src/common/vulkan/context.cpp +++ b/src/common/vulkan/context.cpp @@ -19,8 +19,8 @@ std::unique_ptr g_vulkan_context; namespace Vulkan { -Context::Context(VkInstance instance, VkPhysicalDevice physical_device) - : m_instance(instance), m_physical_device(physical_device) +Context::Context(VkInstance instance, VkPhysicalDevice physical_device, bool owns_device) + : m_instance(instance), m_physical_device(physical_device), m_owns_device(owns_device) { // Read device physical memory properties, we need it for allocating buffers vkGetPhysicalDeviceProperties(physical_device, &m_device_properties); @@ -44,13 +44,17 @@ Context::~Context() DestroyGlobalDescriptorPool(); DestroyCommandBuffers(); - if (m_device != VK_NULL_HANDLE) + if (m_owns_device && m_device != VK_NULL_HANDLE) vkDestroyDevice(m_device, nullptr); if (m_debug_report_callback != VK_NULL_HANDLE) DisableDebugReports(); - vkDestroyInstance(m_instance, nullptr); + if (m_owns_device) + { + vkDestroyInstance(m_instance, nullptr); + Vulkan::UnloadVulkanLibrary(); + } } bool Context::CheckValidationLayerAvailablility() @@ -344,14 +348,14 @@ bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::uniqu return false; } - g_vulkan_context.reset(new Context(instance, gpus[gpu_index])); + g_vulkan_context.reset(new Context(instance, gpus[gpu_index], true)); // Enable debug reports if the "Host GPU" log category is enabled. if (enable_debug_reports) g_vulkan_context->EnableDebugReports(); // Attempt to create the device. - if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer) || + if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) || !g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers() || (enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, true)) == nullptr)) { @@ -359,7 +363,33 @@ bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::uniqu if (surface != VK_NULL_HANDLE) vkDestroySurfaceKHR(instance, surface, nullptr); - Vulkan::UnloadVulkanLibrary(); + g_vulkan_context.reset(); + return false; + } + + return true; +} + +bool Context::CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, + bool take_ownership, bool enable_validation_layer, bool enable_debug_reports, + const char** required_device_extensions /* = nullptr */, + u32 num_required_device_extensions /* = 0 */, + const char** required_device_layers /* = nullptr */, + u32 num_required_device_layers /* = 0 */, + const VkPhysicalDeviceFeatures* required_features /* = nullptr */) +{ + g_vulkan_context.reset(new Context(instance, gpu, take_ownership)); + + // Enable debug reports if the "Host GPU" log category is enabled. + if (enable_debug_reports) + g_vulkan_context->EnableDebugReports(); + + // Attempt to create the device. + if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, required_device_extensions, + num_required_device_extensions, required_device_layers, + num_required_device_layers, required_features) || + !g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers()) + { g_vulkan_context.reset(); return false; } @@ -403,8 +433,13 @@ bool Context::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_ return !strcmp(name, properties.extensionName); }) != available_extension_list.end()) { - Log_InfoPrintf("Enabling extension: %s", name); - extension_list->push_back(name); + if (std::none_of(extension_list->begin(), extension_list->end(), + [&](const char* existing_name) { return (std::strcmp(existing_name, name) == 0); })) + { + Log_InfoPrintf("Enabling extension: %s", name); + extension_list->push_back(name); + } + return true; } @@ -420,7 +455,7 @@ bool Context::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_ return true; } -bool Context::SelectDeviceFeatures() +bool Context::SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features) { VkPhysicalDeviceFeatures available_features; vkGetPhysicalDeviceFeatures(m_physical_device, &available_features); @@ -431,6 +466,9 @@ bool Context::SelectDeviceFeatures() return false; } + if (required_features) + std::memcpy(&m_device_features, required_features, sizeof(m_device_features)); + // Enable the features we use. m_device_features.dualSrcBlend = available_features.dualSrcBlend; m_device_features.geometryShader = available_features.geometryShader; @@ -438,7 +476,9 @@ bool Context::SelectDeviceFeatures() return true; } -bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer) +bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions, + u32 num_required_device_extensions, const char** required_device_layers, + u32 num_required_device_layers, const VkPhysicalDeviceFeatures* required_features) { u32 queue_family_count; vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, nullptr); @@ -536,16 +576,18 @@ bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer) device_info.pQueueCreateInfos = queue_infos.data(); ExtensionList enabled_extensions; + for (u32 i = 0; i < num_required_device_extensions; i++) + enabled_extensions.emplace_back(required_device_extensions[i]); if (!SelectDeviceExtensions(&enabled_extensions, surface != VK_NULL_HANDLE)) return false; - device_info.enabledLayerCount = 0; - device_info.ppEnabledLayerNames = nullptr; + device_info.enabledLayerCount = num_required_device_layers; + device_info.ppEnabledLayerNames = required_device_layers; device_info.enabledExtensionCount = static_cast(enabled_extensions.size()); device_info.ppEnabledExtensionNames = enabled_extensions.data(); // Check for required features before creating. - if (!SelectDeviceFeatures()) + if (!SelectDeviceFeatures(required_features)) return false; device_info.pEnabledFeatures = &m_device_features; diff --git a/src/common/vulkan/context.h b/src/common/vulkan/context.h index c99c76815..2d4780511 100644 --- a/src/common/vulkan/context.h +++ b/src/common/vulkan/context.h @@ -46,6 +46,15 @@ public: static bool Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr* out_swap_chain, bool enable_debug_reports, bool enable_validation_layer); + // Creates a new context from a pre-existing instance. + static bool CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, + bool take_ownership, bool enable_validation_layer, bool enable_debug_reports, + const char** required_device_extensions = nullptr, + u32 num_required_device_extensions = 0, + const char** required_device_layers = nullptr, + u32 num_required_device_layers = 0, + const VkPhysicalDeviceFeatures* required_features = nullptr); + // Destroys context. static void Destroy(); @@ -162,13 +171,15 @@ public: void WaitForGPUIdle(); private: - Context(VkInstance instance, VkPhysicalDevice physical_device); + Context(VkInstance instance, VkPhysicalDevice physical_device, bool owns_device); using ExtensionList = std::vector; static bool SelectInstanceExtensions(ExtensionList* extension_list, bool enable_surface, bool enable_debug_report); bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface); - bool SelectDeviceFeatures(); - bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer); + bool SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features); + bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions, + u32 num_required_device_extensions, const char** required_device_layers, + u32 num_required_device_layers, const VkPhysicalDeviceFeatures* required_features); bool CreateCommandBuffers(); void DestroyCommandBuffers(); @@ -210,6 +221,7 @@ private: u64 m_completed_fence_counter = 0; u32 m_current_frame; + bool m_owns_device = false; bool m_last_present_failed = false; // Render pass cache diff --git a/src/common/vulkan/util.cpp b/src/common/vulkan/util.cpp index efbe47e67..b27af1d59 100644 --- a/src/common/vulkan/util.cpp +++ b/src/common/vulkan/util.cpp @@ -232,6 +232,15 @@ void SafeDestroySampler(VkSampler& samp) } } +void SafeDestroySemaphore(VkSemaphore& sem) +{ + if (sem != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_vulkan_context->GetDevice(), sem, nullptr); + sem = VK_NULL_HANDLE; + } +} + void SafeFreeGlobalDescriptorSet(VkDescriptorSet& ds) { if (ds != VK_NULL_HANDLE) diff --git a/src/common/vulkan/util.h b/src/common/vulkan/util.h index 636214c45..1b45e2430 100644 --- a/src/common/vulkan/util.h +++ b/src/common/vulkan/util.h @@ -41,6 +41,7 @@ void SafeDestroyPipelineLayout(VkPipelineLayout& pl); void SafeDestroyDescriptorSetLayout(VkDescriptorSetLayout& dsl); void SafeDestroyBufferView(VkBufferView& bv); void SafeDestroySampler(VkSampler& samp); +void SafeDestroySemaphore(VkSemaphore& sem); void SafeFreeGlobalDescriptorSet(VkDescriptorSet& ds); void SetViewport(VkCommandBuffer command_buffer, int x, int y, int width, int height, float min_depth = 0.0f, diff --git a/src/duckstation-libretro/CMakeLists.txt b/src/duckstation-libretro/CMakeLists.txt index e95c0be66..61d9e8263 100644 --- a/src/duckstation-libretro/CMakeLists.txt +++ b/src/duckstation-libretro/CMakeLists.txt @@ -9,6 +9,8 @@ add_library(duckstation-libretro SHARED libretro_opengl_host_display.h libretro_settings_interface.cpp libretro_settings_interface.h + libretro_vulkan_host_display.cpp + libretro_vulkan_host_display.h main.cpp ) @@ -19,5 +21,5 @@ if(WIN32) ) endif() -target_link_libraries(duckstation-libretro PRIVATE core common imgui glad scmversion frontend-common libretro-common) +target_link_libraries(duckstation-libretro PRIVATE core common imgui glad scmversion frontend-common vulkan-loader libretro-common) diff --git a/src/duckstation-libretro/duckstation-libretro.vcxproj b/src/duckstation-libretro/duckstation-libretro.vcxproj index f2296bb65..77eb4b95e 100644 --- a/src/duckstation-libretro/duckstation-libretro.vcxproj +++ b/src/duckstation-libretro/duckstation-libretro.vcxproj @@ -35,6 +35,9 @@ + + {9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035} + {ee054e08-3799-4a59-a422-18259c105ffd} @@ -54,6 +57,7 @@ + @@ -64,6 +68,7 @@ + {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF} @@ -211,7 +216,7 @@ _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -232,7 +237,7 @@ _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -253,7 +258,7 @@ _ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -277,7 +282,7 @@ _ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -300,7 +305,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -323,7 +328,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -347,7 +352,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -370,7 +375,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/duckstation-libretro/duckstation-libretro.vcxproj.filters b/src/duckstation-libretro/duckstation-libretro.vcxproj.filters index daba5b448..9f26ba8a4 100644 --- a/src/duckstation-libretro/duckstation-libretro.vcxproj.filters +++ b/src/duckstation-libretro/duckstation-libretro.vcxproj.filters @@ -8,6 +8,7 @@ + @@ -16,5 +17,6 @@ + \ No newline at end of file diff --git a/src/duckstation-libretro/libretro_d3d11_host_display.cpp b/src/duckstation-libretro/libretro_d3d11_host_display.cpp index 9db4e060f..eff191cf1 100644 --- a/src/duckstation-libretro/libretro_d3d11_host_display.cpp +++ b/src/duckstation-libretro/libretro_d3d11_host_display.cpp @@ -4,7 +4,7 @@ #include "common/d3d11/shader_compiler.h" #include "common/log.h" #include "libretro_host_interface.h" -Log_SetChannel(D3D11HostDisplay); +Log_SetChannel(LibretroD3D11HostDisplay); #define HAVE_D3D11 #include "libretro_d3d.h" diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index c88a2b9ac..33bcb98f6 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -13,6 +13,7 @@ #include "libretro_host_display.h" #include "libretro_opengl_host_display.h" #include "libretro_settings_interface.h" +#include "libretro_vulkan_host_display.h" #include #include #include @@ -344,6 +345,7 @@ static std::array s_option_definitions = {{ {"D3D11", "Hardware (D3D11)"}, #endif {"OpenGL", "Hardware (OpenGL)"}, + {"Vulkan", "Hardware (Vulkan)"}, {"Software", "Software"}}, #ifdef WIN32 "D3D11" @@ -658,16 +660,14 @@ static std::optional RetroHwContextToRenderer(retro_hw_context_type case RETRO_HW_CONTEXT_OPENGLES_VERSION: return GPURenderer::HardwareOpenGL; + case RETRO_HW_CONTEXT_VULKAN: + return GPURenderer::HardwareVulkan; + #ifdef WIN32 case RETRO_HW_CONTEXT_DIRECT3D: return GPURenderer::HardwareD3D11; #endif -#if 0 - case RETRO_HW_CONTEXT_VULKAN: - return GPURenderer::HardwareVulkan; -#endif - default: return std::nullopt; } @@ -720,6 +720,10 @@ bool LibretroHostInterface::RequestHardwareRendererContext() break; #endif + case GPURenderer::HardwareVulkan: + m_hw_render_callback_valid = LibretroVulkanHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); + break; + case GPURenderer::HardwareOpenGL: m_hw_render_callback_valid = LibretroOpenGLHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); break; @@ -758,6 +762,10 @@ void LibretroHostInterface::SwitchToHardwareRenderer() display = std::make_unique(); break; + case GPURenderer::HardwareVulkan: + display = std::make_unique(); + break; + #ifdef WIN32 case GPURenderer::HardwareD3D11: display = std::make_unique(); diff --git a/src/duckstation-libretro/libretro_vulkan_host_display.cpp b/src/duckstation-libretro/libretro_vulkan_host_display.cpp new file mode 100644 index 000000000..03324805f --- /dev/null +++ b/src/duckstation-libretro/libretro_vulkan_host_display.cpp @@ -0,0 +1,261 @@ +#include "libretro_vulkan_host_display.h" +#include "common/align.h" +#include "common/assert.h" +#include "common/log.h" +#include "common/vulkan/builders.h" +#include "common/vulkan/context.h" +#include "common/vulkan/shader_cache.h" +#include "common/vulkan/util.h" +#include "libretro_host_interface.h" +#include "vulkan_loader.h" +Log_SetChannel(LibretroVulkanHostDisplay); + +LibretroVulkanHostDisplay::LibretroVulkanHostDisplay() = default; + +LibretroVulkanHostDisplay::~LibretroVulkanHostDisplay() = default; + +void LibretroVulkanHostDisplay::SetVSync(bool enabled) +{ + // The libretro frontend controls this. + Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled)); +} + +static bool LoadModuleFunctions(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr) +{ +#define VULKAN_MODULE_ENTRY_POINT(name, required) \ + if (!name && (name = reinterpret_cast(get_instance_proc_addr(instance, #name))) == nullptr) \ + { \ + Log_ErrorPrintf("Could not get function pointer for '%s'", #name); \ + return false; \ + } +#include "vulkan_entry_points.inl" +#undef VULKAN_MODULE_ENTRY_POINT + + return true; +} + +static bool RetroCreateVulkanDevice(struct retro_vulkan_context* context, VkInstance instance, VkPhysicalDevice gpu, + VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr, + const char** required_device_extensions, unsigned num_required_device_extensions, + const char** required_device_layers, unsigned num_required_device_layers, + const VkPhysicalDeviceFeatures* required_features) +{ + // We need some module functions. + vkGetInstanceProcAddr = get_instance_proc_addr; + if (!LoadModuleFunctions(instance, get_instance_proc_addr)) + { + Log_ErrorPrintf("Failed to load Vulkan module functions"); + Vulkan::ResetVulkanLibraryFunctionPointers(); + return false; + } + + if (!Vulkan::LoadVulkanInstanceFunctions(instance)) + { + Log_ErrorPrintf("Failed to load Vulkan instance functions"); + Vulkan::ResetVulkanLibraryFunctionPointers(); + return false; + } + + if (gpu == VK_NULL_HANDLE) + { + Vulkan::Context::GPUList gpus = Vulkan::Context::EnumerateGPUs(instance); + if (gpus.empty()) + { + g_libretro_host_interface.ReportError("No GPU provided and none available, cannot create device"); + Vulkan::ResetVulkanLibraryFunctionPointers(); + return false; + } + + Log_InfoPrintf("No GPU provided, using first/default"); + gpu = gpus[0]; + } + + if (!Vulkan::Context::CreateFromExistingInstance( + instance, gpu, surface, false, false, false, required_device_extensions, num_required_device_extensions, + required_device_layers, num_required_device_layers, required_features)) + { + Vulkan::ResetVulkanLibraryFunctionPointers(); + return false; + } + + context->gpu = g_vulkan_context->GetPhysicalDevice(); + context->device = g_vulkan_context->GetDevice(); + context->queue = g_vulkan_context->GetGraphicsQueue(); + context->queue_family_index = g_vulkan_context->GetGraphicsQueueFamilyIndex(); + context->presentation_queue = g_vulkan_context->GetPresentQueue(); + context->presentation_queue_family_index = g_vulkan_context->GetPresentQueueFamilyIndex(); + return true; +} + +static retro_hw_render_context_negotiation_interface_vulkan s_vulkan_context_negotiation_interface = { + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN, // interface_type + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION, // interface_version + nullptr, // get_application_info + RetroCreateVulkanDevice, // create_device + nullptr // destroy_device +}; + +bool LibretroVulkanHostDisplay::RequestHardwareRendererContext(retro_hw_render_callback* cb) +{ + cb->cache_context = true; + cb->bottom_left_origin = false; + cb->context_type = RETRO_HW_CONTEXT_VULKAN; + return g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb) && + g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, + &s_vulkan_context_negotiation_interface); +} + +bool LibretroVulkanHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, + bool debug_device) +{ + retro_hw_render_interface* ri = nullptr; + if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, &ri)) + { + Log_ErrorPrint("Failed to get HW render interface"); + return false; + } + else if (ri->interface_type != RETRO_HW_RENDER_INTERFACE_VULKAN || + ri->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION) + { + Log_ErrorPrintf("Unexpected HW interface - type %u version %u", static_cast(ri->interface_type), + static_cast(ri->interface_version)); + return false; + } + + if (!g_vulkan_context) + { + Log_ErrorPrintf("Vulkan context was not negotiated/created"); + return false; + } + + // TODO: Grab queue? it should be the same + m_ri = reinterpret_cast(ri); + return true; +} + +void LibretroVulkanHostDisplay::DestroyRenderDevice() +{ + VulkanHostDisplay::DestroyRenderDevice(); + Vulkan::ResetVulkanLibraryFunctionPointers(); +} + +bool LibretroVulkanHostDisplay::CreateResources() +{ + m_frame_render_pass = g_vulkan_context->GetRenderPass(FRAMEBUFFER_FORMAT, VK_FORMAT_UNDEFINED, VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_CLEAR); + if (m_frame_render_pass == VK_NULL_HANDLE) + return false; + + return VulkanHostDisplay::CreateResources(); +} + +void LibretroVulkanHostDisplay::DestroyResources() +{ + VulkanHostDisplay::DestroyResources(); + Vulkan::Util::SafeDestroyFramebuffer(m_frame_framebuffer); + m_frame_texture.Destroy(); +} + +VkRenderPass LibretroVulkanHostDisplay::GetRenderPassForDisplay() const +{ + return m_frame_render_pass; +} + +void LibretroVulkanHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height) +{ + m_window_info.surface_width = static_cast(new_window_width); + m_window_info.surface_height = static_cast(new_window_height); +} + +bool LibretroVulkanHostDisplay::Render() +{ + const u32 resolution_scale = g_libretro_host_interface.GetResolutionScale(); + const u32 display_width = static_cast(m_display_width) * resolution_scale; + const u32 display_height = static_cast(m_display_height) * resolution_scale; + if (!CheckFramebufferSize(display_width, display_height)) + return false; + + VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); + m_frame_texture.OverrideImageLayout(m_frame_view.image_layout); + m_frame_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + const VkClearValue clear_value = {}; + const VkRenderPassBeginInfo rp = { + VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, m_frame_render_pass, m_frame_framebuffer, + {{0, 0}, {display_width, display_height}}, 1u, &clear_value}; + vkCmdBeginRenderPass(cmdbuffer, &rp, VK_SUBPASS_CONTENTS_INLINE); + + if (HasDisplayTexture()) + { + const auto [left, top, width, height] = CalculateDrawRect(display_width, display_height, 0); + RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height, + m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, + m_display_texture_view_height, m_display_linear_filtering); + } + + if (HasSoftwareCursor()) + { + // TODO: Scale mouse x/y + const auto [left, top, width, height] = CalculateSoftwareCursorDrawRect(m_mouse_position_x, m_mouse_position_y); + RenderSoftwareCursor(left, top, width, height, m_cursor_texture.get()); + } + + vkCmdEndRenderPass(cmdbuffer); + m_frame_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + m_frame_view.image_layout = m_frame_texture.GetLayout(); + m_ri->set_image(m_ri->handle, &m_frame_view, 0, nullptr, VK_QUEUE_FAMILY_IGNORED); + + // TODO: We can't use this because it doesn't support passing fences... + // m_ri->set_command_buffers(m_ri->handle, 1, &cmdbuffer); + m_ri->lock_queue(m_ri->handle); + g_vulkan_context->SubmitCommandBuffer(); + m_ri->unlock_queue(m_ri->handle); + g_vulkan_context->MoveToNextCommandBuffer(); + + g_retro_video_refresh_callback(RETRO_HW_FRAME_BUFFER_VALID, display_width, display_height, 0); + return true; +} + +bool LibretroVulkanHostDisplay::CheckFramebufferSize(u32 width, u32 height) +{ + static constexpr VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + static constexpr VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D; + static constexpr VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL; + + if (m_frame_texture.GetWidth() == width && m_frame_texture.GetHeight() == height) + return true; + + g_vulkan_context->DeferFramebufferDestruction(m_frame_framebuffer); + m_frame_texture.Destroy(true); + + if (!m_frame_texture.Create(width, height, 1, 1, FRAMEBUFFER_FORMAT, VK_SAMPLE_COUNT_1_BIT, view_type, tiling, usage)) + return false; + + VkCommandBuffer cmdbuf = g_vulkan_context->GetCurrentCommandBuffer(); + m_frame_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + static constexpr VkClearColorValue cc = {}; + static constexpr VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + vkCmdClearColorImage(cmdbuf, m_frame_texture.GetImage(), m_frame_texture.GetLayout(), &cc, 1, &range); + + Vulkan::FramebufferBuilder fbb; + fbb.SetRenderPass(m_frame_render_pass); + fbb.AddAttachment(m_frame_texture.GetView()); + fbb.SetSize(width, height, 1); + m_frame_framebuffer = fbb.Create(g_vulkan_context->GetDevice(), false); + if (m_frame_framebuffer == VK_NULL_HANDLE) + return false; + + m_frame_view = {}; + m_frame_view.image_view = m_frame_texture.GetView(); + m_frame_view.image_layout = m_frame_texture.GetLayout(); + m_frame_view.create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + m_frame_view.create_info.image = m_frame_texture.GetImage(); + m_frame_view.create_info.viewType = view_type; + m_frame_view.create_info.format = FRAMEBUFFER_FORMAT; + m_frame_view.create_info.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_A}; + m_frame_view.create_info.subresourceRange = range; + return true; +} diff --git a/src/duckstation-libretro/libretro_vulkan_host_display.h b/src/duckstation-libretro/libretro_vulkan_host_display.h new file mode 100644 index 000000000..c5d22df1d --- /dev/null +++ b/src/duckstation-libretro/libretro_vulkan_host_display.h @@ -0,0 +1,42 @@ +#pragma once +#include "common/vulkan/texture.h" +#include "frontend-common/vulkan_host_display.h" +#include "libretro.h" + +#define HAVE_VULKAN +#include "libretro_vulkan.h" + +class LibretroVulkanHostDisplay final : public FrontendCommon::VulkanHostDisplay +{ +public: + LibretroVulkanHostDisplay(); + ~LibretroVulkanHostDisplay(); + + static bool RequestHardwareRendererContext(retro_hw_render_callback* cb); + + bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override; + void DestroyRenderDevice() override; + + void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + + void SetVSync(bool enabled) override; + + bool Render() override; + +protected: + bool CreateResources() override; + void DestroyResources() override; + VkRenderPass GetRenderPassForDisplay() const override; + +private: + static constexpr VkFormat FRAMEBUFFER_FORMAT = VK_FORMAT_R8G8B8A8_UNORM; + + bool CheckFramebufferSize(u32 width, u32 height); + + const retro_hw_render_interface_vulkan* m_ri = nullptr; + + Vulkan::Texture m_frame_texture; + retro_vulkan_image m_frame_view = {}; + VkFramebuffer m_frame_framebuffer = VK_NULL_HANDLE; + VkRenderPass m_frame_render_pass = VK_NULL_HANDLE; +}; diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp index 9b79c6ea0..3a8485b04 100644 --- a/src/frontend-common/vulkan_host_display.cpp +++ b/src/frontend-common/vulkan_host_display.cpp @@ -266,6 +266,11 @@ bool VulkanHostDisplay::HasRenderSurface() const return static_cast(m_swap_chain); } +VkRenderPass VulkanHostDisplay::GetRenderPassForDisplay() const +{ + return m_swap_chain->GetClearRenderPass(); +} + bool VulkanHostDisplay::CreateResources() { static constexpr char fullscreen_quad_vertex_shader[] = R"( @@ -348,7 +353,7 @@ void main() gpbuilder.SetNoBlendingState(); gpbuilder.SetDynamicViewportAndScissorState(); gpbuilder.SetPipelineLayout(m_pipeline_layout); - gpbuilder.SetRenderPass(m_swap_chain->GetClearRenderPass(), 0); + gpbuilder.SetRenderPass(GetRenderPassForDisplay(), 0); m_display_pipeline = gpbuilder.Create(device, pipeline_cache, false); if (m_display_pipeline == VK_NULL_HANDLE) diff --git a/src/frontend-common/vulkan_host_display.h b/src/frontend-common/vulkan_host_display.h index f34a22d6c..757dd9fd9 100644 --- a/src/frontend-common/vulkan_host_display.h +++ b/src/frontend-common/vulkan_host_display.h @@ -60,6 +60,9 @@ protected: float src_rect_height; }; + // Can be overridden by frontends. + virtual VkRenderPass GetRenderPassForDisplay() const; + virtual bool CreateResources(); virtual void DestroyResources();