From 4160414b31d33e7af87a042dd41b1e8d844fc386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 6 Aug 2026 17:07:05 +0200 Subject: [PATCH] Vulkan: remove libretro's global function-pointer wrapper hack libretro/libretro_vulkan.cpp got PPSSPP's libretro core working with RetroArch's Vulkan integration by globally monkey-patching PPSSPP's Vulkan loader function pointers (vkCreateInstance, vkCreateDevice, vkCreateSwapchainKHR, vkAcquireNextImageKHR, vkQueuePresentKHR, vkQueueSubmit, etc.) so the unmodified VulkanContext class would end up wrapping RetroArch's already-existing VkInstance/VkDevice instead of creating its own, and so a fake VkSwapchainKHR (a self-managed array of images synced against RetroArch's retro_hw_render_interface_vulkan callbacks) could stand in for the real swapchain that libretro's Vulkan model doesn't have. Flagged in-code as "a wacky wrapper". Replaces that with first-class support in VulkanContext for the two things libretro actually needs: - Adopting an externally-created instance/device instead of faking vkCreateInstance/vkCreateDevice: VulkanContext::CreateInstanceExternal() adopts RetroArch's VkInstance; CreateDevice() gained optional extraDeviceExtensions/extraRequiredFeatures params so RetroArch's requirements get merged into a real vkCreateDevice() call; ownsInstance_/ownsDevice_ flags (the latter set via SetDeviceExternallyOwned()) mean DestroyInstance()/DestroyDevice() skip the real vkDestroy* calls when something else owns the object, without needing to intercept anything. VulkanLoader gained VulkanLoadFromGetInstanceProcAddr() for bootstrapping from a host-supplied proc-addr getter instead of dlopen/dlsym-ing the loader ourselves - vkGetDeviceProcAddr is resolved via the real instance handle (not NULL), since per the Vulkan spec it's not one of the handful of commands queryable with a NULL instance. - A pluggable presentation backend (Common/GPU/Vulkan/VulkanPresentation.h) for hosts with no real VK_KHR_swapchain, replacing the fake-swapchain- handle trick. VulkanContext::GetPresentation() is null by default, so every existing platform's real-swapchain code path is untouched; libretro/LibretroVulkanPresentation implements this interface directly against retro_hw_render_interface_vulkan, as real class state instead of file-scope globals. Several pieces of state that are normally only populated as a side effect of ReinitSurface()/InitSwapchain() - the graphics queue/queue family index (ChooseQueue() is entangled with real-surface presentation-support checks), the swapchain format, and the available present modes - needed presentation-aware fallbacks since libretro never calls that real-surface path at all. libretro/LibretroVulkanContext.cpp now drives VulkanContext's real, public API directly - no more hijacked function pointers, no more fake surface or swapchain. libretro/libretro_vulkan.cpp is deleted. Verified with a full build+run in RetroArch (not just compile-time checks): the libretro Makefile doesn't track header dependencies (cl.exe doesn't support -MMD/-MP, and Makefile.common never sets up an equivalent), so a `make clean` full rebuild is required after any header change to avoid linking stale object code from before the change - several of the fixes above were initially masked by exactly that. --- Common/CMakeLists.txt | 1 + Common/Common.vcxproj | 1 + Common/Common.vcxproj.filters | 3 + Common/GPU/Vulkan/VulkanContext.cpp | 110 ++++- Common/GPU/Vulkan/VulkanContext.h | 62 ++- Common/GPU/Vulkan/VulkanFrameData.cpp | 18 +- Common/GPU/Vulkan/VulkanFramebuffer.cpp | 6 +- Common/GPU/Vulkan/VulkanImage.cpp | 2 + Common/GPU/Vulkan/VulkanLoader.cpp | 20 + Common/GPU/Vulkan/VulkanLoader.h | 7 + Common/GPU/Vulkan/VulkanPresentation.h | 45 ++ Common/GPU/Vulkan/VulkanQueueRunner.cpp | 6 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 34 +- Common/GPU/Vulkan/thin3d_vulkan.cpp | 25 +- Windows/PPSSPP.vcxproj | 10 +- Windows/PPSSPP.vcxproj.filters | 5 +- libretro/CMakeLists.txt | 2 +- libretro/LibretroVulkanContext.cpp | 140 +++++-- libretro/LibretroVulkanPresentation.cpp | 133 ++++++ libretro/LibretroVulkanPresentation.h | 67 +++ libretro/Makefile.common | 2 +- libretro/libretro.cpp | 6 +- libretro/libretro_vulkan.cpp | 478 ---------------------- 23 files changed, 609 insertions(+), 574 deletions(-) create mode 100644 Common/GPU/Vulkan/VulkanPresentation.h create mode 100644 libretro/LibretroVulkanPresentation.cpp create mode 100644 libretro/LibretroVulkanPresentation.h delete mode 100644 libretro/libretro_vulkan.cpp diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index 5e1872ac30..fc7f95c67e 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -197,6 +197,7 @@ add_library(Common STATIC GPU/Vulkan/VulkanQueueRunner.h GPU/Vulkan/VulkanFrameData.cpp GPU/Vulkan/VulkanFrameData.h + GPU/Vulkan/VulkanPresentation.h GPU/Vulkan/VulkanGraphicsContext.cpp Input/GestureDetector.cpp Input/GestureDetector.h diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index af5fee50f9..d11d158f10 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -459,6 +459,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 326a9bcf80..cd892a2ea1 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -314,6 +314,9 @@ GPU\Vulkan + + GPU\Vulkan + Render\Text diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 7be171f1d3..4c68d5d65e 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -114,16 +114,7 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { #endif } - // Check which Vulkan version we should request. - // Our code is fine with any version from 1.0 to 1.2, we don't know about higher versions. - vulkanInstanceApiVersion_ = VK_API_VERSION_1_0; - if (vkEnumerateInstanceVersion) { - vkEnumerateInstanceVersion(&vulkanInstanceApiVersion_); - vulkanInstanceApiVersion_ &= 0xFFFFF000; // Remove patch version. - vulkanInstanceApiVersion_ = std::min(VK_API_VERSION_1_4, vulkanInstanceApiVersion_); - std::string versionString = FormatAPIVersion(vulkanInstanceApiVersion_); - INFO_LOG(Log::G3D, "Detected Vulkan API version: %s", versionString.c_str()); - } + DetectInstanceApiVersion(); instance_layer_names_.clear(); @@ -254,6 +245,42 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { return res; } + return FinishInstanceInit(); +} + +VkResult VulkanContext::CreateInstanceExternal(VkInstance instance) { + instance_ = instance; + ownsInstance_ = false; + DetectInstanceApiVersion(); + + // We didn't go through the normal extension-selection dance in CreateInstance() (there's nothing to + // enable - the instance already exists), but IsInstanceExtensionAvailable() and the properties2 codepath + // below still want these populated, so gather them the same way CreateInstance() does. + GetInstanceLayerProperties(); + GetInstanceLayerExtensionList(nullptr, &instance_extension_properties_); + + // vkGetPhysicalDeviceProperties2/Features2 are core as of Vulkan 1.1, so an adopted 1.1+ instance can + // always use them regardless of whether the owning application explicitly enabled the KHR extension. + extensionsLookup_.KHR_get_physical_device_properties2 = + vulkanInstanceApiVersion_ >= VK_API_VERSION_1_1 || IsInstanceExtensionAvailable(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + return FinishInstanceInit(); +} + +void VulkanContext::DetectInstanceApiVersion() { + // Check which Vulkan version we should request. + // Our code is fine with any version from 1.0 to 1.2, we don't know about higher versions. + vulkanInstanceApiVersion_ = VK_API_VERSION_1_0; + if (vkEnumerateInstanceVersion) { + vkEnumerateInstanceVersion(&vulkanInstanceApiVersion_); + vulkanInstanceApiVersion_ &= 0xFFFFF000; // Remove patch version. + vulkanInstanceApiVersion_ = std::min(VK_API_VERSION_1_4, vulkanInstanceApiVersion_); + std::string versionString = FormatAPIVersion(vulkanInstanceApiVersion_); + INFO_LOG(Log::G3D, "Detected Vulkan API version: %s", versionString.c_str()); + } +} + +VkResult VulkanContext::FinishInstanceInit() { VulkanLoadInstanceFunctions(instance_, extensionsLookup_, vulkanInstanceApiVersion_); if (!CheckLayers(instance_layer_properties_, instance_layer_names_)) { WARN_LOG(Log::G3D, "CheckLayers for instance failed"); @@ -265,12 +292,14 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { #if SIMULATE_VULKAN_FAILURE == 3 gpu_count = 0; #else - res = vkEnumeratePhysicalDevices(instance_, &gpu_count, nullptr); + VkResult res = vkEnumeratePhysicalDevices(instance_, &gpu_count, nullptr); #endif if (gpu_count <= 0) { ERROR_LOG(Log::G3D, "Vulkan driver found but no supported GPU is available"); init_error_ = "No Vulkan physical devices found"; - vkDestroyInstance(instance_, nullptr); + if (ownsInstance_) { + vkDestroyInstance(instance_, nullptr); + } instance_ = nullptr; return VK_ERROR_INITIALIZATION_FAILED; } @@ -281,7 +310,9 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { res = vkEnumeratePhysicalDevices(instance_, &gpu_count, physical_devices_.data()); if (res != VK_SUCCESS) { init_error_ = "Failed to enumerate physical devices"; - vkDestroyInstance(instance_, nullptr); + if (ownsInstance_) { + vkDestroyInstance(instance_, nullptr); + } instance_ = nullptr; return res; } @@ -350,7 +381,9 @@ void VulkanContext::DestroyInstance() { } } - vkDestroyInstance(instance_, nullptr); + if (ownsInstance_) { + vkDestroyInstance(instance_, nullptr); + } VulkanFree(); instance_ = VK_NULL_HANDLE; } @@ -383,7 +416,9 @@ void VulkanContext::UpdateInflightFrames(int n) { void VulkanContext::WaitUntilQueueIdle() { // Should almost never be used + LockQueue(); vkQueueWaitIdle(gfx_queue_); + UnlockQueue(); } bool VulkanContext::MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) { @@ -559,6 +594,11 @@ bool VulkanContext::EnableDeviceExtension(const char *extension, uint32_t coreVe if (coreVersion != 0 && vulkanDeviceApiVersion_ >= coreVersion) { return true; } + for (const char *alreadyEnabled : device_extensions_enabled_) { + if (!strcmp(alreadyEnabled, extension)) { + return true; + } + } for (auto &iter : device_extension_properties_) { if (!strcmp(iter.extensionName, extension)) { device_extensions_enabled_.push_back(extension); @@ -581,7 +621,7 @@ bool VulkanContext::EnableInstanceExtension(const char *extension, uint32_t core return false; } -VkResult VulkanContext::CreateDevice(int physical_device) { +VkResult VulkanContext::CreateDevice(int physical_device, const std::vector &extraDeviceExtensions, const VkPhysicalDeviceFeatures *extraRequiredFeatures) { physical_device_ = physical_device; INFO_LOG(Log::G3D, "Chose physical device %d: %s", physical_device, physicalDeviceProperties_[physical_device].properties.deviceName); @@ -639,6 +679,12 @@ VkResult VulkanContext::CreateDevice(int physical_device) { device_extensions_enabled_.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + // A host application (e.g. a libretro frontend) may require extra device extensions on top of what we'd + // normally ask for. + for (const char *extraExtension : extraDeviceExtensions) { + EnableDeviceExtension(extraExtension, 0); + } + if (!init_error_.empty() || physical_device_ < 0) { ERROR_LOG(Log::G3D, "Vulkan init failed: %s", init_error_.c_str()); return VK_ERROR_INITIALIZATION_FAILED; @@ -761,7 +807,18 @@ VkResult VulkanContext::CreateDevice(int physical_device) { deviceFeatures_.enabled.standard.shaderCullDistance = deviceFeatures_.available.standard.shaderCullDistance; deviceFeatures_.enabled.standard.geometryShader = deviceFeatures_.available.standard.geometryShader; deviceFeatures_.enabled.standard.sampleRateShading = deviceFeatures_.available.standard.sampleRateShading; - + + // A host application (e.g. a libretro frontend) may require some additional features to be enabled. + if (extraRequiredFeatures) { + VkBool32 *enabled = (VkBool32 *)&deviceFeatures_.enabled.standard; + const VkBool32 *required = (const VkBool32 *)extraRequiredFeatures; + for (size_t i = 0; i < sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32); i++) { + if (required[i]) { + enabled[i] = VK_TRUE; + } + } + } + #ifdef _DEBUG // For debugging! Although, it might hide problems, so turning it off. Can be useful to rule out classes of issues. // deviceFeatures_.enabled.standard.robustBufferAccess = deviceFeatures_.available.standard.robustBufferAccess; @@ -1348,6 +1405,23 @@ bool VulkanContext::ChooseQueue() { return true; } +bool VulkanContext::ChooseGraphicsQueueWithoutSurface() { + uint32_t graphicsQueueNodeIndex = UINT32_MAX; + for (uint32_t i = 0; i < queue_count; i++) { + if ((queueFamilyProperties_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { + graphicsQueueNodeIndex = i; + break; + } + } + if (graphicsQueueNodeIndex == UINT32_MAX) { + ERROR_LOG(Log::G3D, "Could not find a graphics queue"); + return false; + } + graphics_queue_family_index_ = graphicsQueueNodeIndex; + vkGetDeviceQueue(device_, graphics_queue_family_index_, 0, &gfx_queue_); + return true; +} + int clamp(int x, int a, int b) { if (x < a) return a; @@ -1624,7 +1698,9 @@ void VulkanContext::DestroyDevice() { vmaDestroyAllocator(allocator_); allocator_ = VK_NULL_HANDLE; - vkDestroyDevice(device_, nullptr); + if (ownsDevice_) { + vkDestroyDevice(device_, nullptr); + } device_ = nullptr; } diff --git a/Common/GPU/Vulkan/VulkanContext.h b/Common/GPU/Vulkan/VulkanContext.h index 34bdb3adc0..72e1576093 100644 --- a/Common/GPU/Vulkan/VulkanContext.h +++ b/Common/GPU/Vulkan/VulkanContext.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ #include "Common/GPU/Vulkan/VulkanDebug.h" #include "Common/GPU/Vulkan/VulkanAlloc.h" #include "Common/GPU/Vulkan/VulkanProfiler.h" +#include "Common/GPU/Vulkan/VulkanPresentation.h" // Enable or disable a simple logging profiler for Vulkan. // Mostly useful for profiling texture uploads currently, but could be useful for @@ -162,6 +164,11 @@ public: }; VkResult CreateInstance(const CreateInfo &info); + // For adopting an already-created VkInstance (e.g. handed to us by a host application/frontend + // like libretro/RetroArch) instead of creating our own. Runs the same post-creation bookkeeping + // (function pointer loading, API version/physical device enumeration, etc.) as CreateInstance(), but + // does not call vkCreateInstance, and DestroyInstance() will not call vkDestroyInstance either. + VkResult CreateInstanceExternal(VkInstance instance); void DestroyInstance(); int GetBestPhysicalDevice() const; @@ -176,7 +183,19 @@ public: bool EnableDeviceExtension(const char *extension, uint32_t coreVersion); // Was previously two functions, ChooseDevice and CreateDevice. - VkResult CreateDevice(int physical_device); + // extraDeviceExtensions/extraRequiredFeatures let a host application (e.g. libretro) merge in extra + // requirements it needs on top of what PPSSPP would normally request, without needing to intercept + // the underlying vkCreateDevice call. + VkResult CreateDevice(int physical_device, + const std::vector &extraDeviceExtensions = {}, + const VkPhysicalDeviceFeatures *extraRequiredFeatures = nullptr); + + // Some host applications (e.g. a libretro frontend, per its create_device contract) take over + // responsibility for eventually destroying the VkDevice once we've handed it back to them, even + // though we're the one that called vkCreateDevice. Call this after CreateDevice() succeeds in that + // case - DestroyDevice() will still run all our own device-resource cleanup, just skip the final + // vkDestroyDevice call. + void SetDeviceExternallyOwned() { ownsDevice_ = false; } const std::string &InitError() const { return init_error_; } @@ -252,6 +271,13 @@ public: return graphics_queue_family_index_; } + // Normally, picking the graphics queue (ChooseQueue(), private below) is entangled with surface + // creation (ReinitSurface()) since it also needs to check which queue family can present to that + // particular surface. Hosts with no real WSI surface at all (e.g. libretro) still need a graphics + // queue, just without that presentation-support check - this does exactly that, and nothing else. + // Only valid to call after CreateDevice() has succeeded. + bool ChooseGraphicsQueueWithoutSurface(); + struct PhysicalDeviceProps { VkPhysicalDeviceProperties properties; VkPhysicalDevicePushDescriptorPropertiesKHR pushDescriptorProperties; @@ -350,12 +376,27 @@ public: } VkSwapchainKHR GetSwapchain() const { return swapchain_; } - VkFormat GetSwapchainFormat() const { return swapchainFormat_; } + VkFormat GetSwapchainFormat() const { return presentation_ ? presentation_->GetFormat() : swapchainFormat_; } bool IsSwapchainInited() const { return swapchainInited_; } + + // Opt-in replacement for the real-swapchain path above (see VulkanPresentation.h for why a host + // application might want this). Null (the default) means "use the real swapchain". + void SetPresentation(std::unique_ptr presentation) { presentation_ = std::move(presentation); } + VulkanPresentation *GetPresentation() const { return presentation_.get(); } + + VkImageLayout GetPresentLayout() const { + return presentation_ ? presentation_->GetPresentLayout() : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + } + + // Wrap every real vkQueueSubmit/vkQueueWaitIdle call with these - no-ops unless a presentation backend + // that shares its VkQueue with another owner (e.g. libretro) is active. + void LockQueue() { if (presentation_) presentation_->LockQueue(); } + void UnlockQueue() { if (presentation_) presentation_->UnlockQueue(); } + void PrepareSubmit(VkSubmitInfo &submitInfo) { if (presentation_) presentation_->PrepareSubmit(submitInfo); } bool HasRealSwapchain() const { return swapChainExtent_.width > 0; } - int GetBackbufferWidth() { return (int)swapChainExtent_.width; } - int GetBackbufferHeight() { return (int)swapChainExtent_.height; } + int GetBackbufferWidth() { return (int)(presentation_ ? presentation_->GetExtent().width : swapChainExtent_.width); } + int GetBackbufferHeight() { return (int)(presentation_ ? presentation_->GetExtent().height : swapChainExtent_.height); } void SetProfilerEnabledPtr(bool *enabled) { for (auto &frame : frame_) { @@ -429,6 +470,11 @@ public: private: bool ChooseQueue(); + // Shared tail of CreateInstance()/CreateInstanceExternal(): function pointer loading, physical device + // enumeration, debug callback setup. + VkResult FinishInstanceInit(); + void DetectInstanceApiVersion(); + void SetDebugNameImpl(uint64_t handle, VkObjectType type, const char *name); VkResult InitDebugUtilsCallback(); @@ -456,6 +502,11 @@ private: u32 vulkanInstanceApiVersion_ = 0; u32 vulkanDeviceApiVersion_ = 0; + // False when instance_/device_ were adopted from an external owner (e.g. a libretro frontend) rather + // than created by us - in that case DestroyInstance()/DestroyDevice() must not actually destroy them. + bool ownsInstance_ = true; + bool ownsDevice_ = true; + std::string init_error_; std::vector instance_layer_names_; std::vector instance_layer_properties_; @@ -506,6 +557,9 @@ private: VkSwapchainKHR swapchain_ = VK_NULL_HANDLE; VkFormat swapchainFormat_ = VK_FORMAT_UNDEFINED; + // When set, replaces the real-swapchain path above. See VulkanPresentation.h. + std::unique_ptr presentation_; + uint32_t queue_count = 0; bool swapchainInited_ = false; diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 7c8300da2a..f33d36e0bf 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -77,8 +77,13 @@ void FrameData::Destroy(VulkanContext *vulkan) { void FrameData::AcquireNextImage(VulkanContext *vulkan) { _dbg_assert_(!hasAcquired); - // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. - VkResult res = vkAcquireNextImageKHR(vulkan->GetDevice(), vulkan->GetSwapchain(), UINT64_MAX, acquireSemaphore, (VkFence)VK_NULL_HANDLE, &curSwapchainImage); + VkResult res; + if (VulkanPresentation *presentation = vulkan->GetPresentation()) { + res = presentation->AcquireNextImage(vulkan, acquireSemaphore, &curSwapchainImage); + } else { + // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. + res = vkAcquireNextImageKHR(vulkan->GetDevice(), vulkan->GetSwapchain(), UINT64_MAX, acquireSemaphore, (VkFence)VK_NULL_HANDLE, &curSwapchainImage); + } switch (res) { case VK_SUCCESS: hasAcquired = true; @@ -111,6 +116,10 @@ VkResult FrameData::QueuePresent(VulkanContext *vulkan, FrameDataShared &shared) hasAcquired = false; _dbg_assert_(!skipSwap); + if (VulkanPresentation *presentation = vulkan->GetPresentation()) { + return presentation->QueuePresent(vulkan, vulkan->GetGraphicsQueue(), curSwapchainImage, shared.swapchainImages_[curSwapchainImage].renderingCompleteSemaphore); + } + VkSwapchainKHR swapchain = vulkan->GetSwapchain(); VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; present.swapchainCount = 1; @@ -230,12 +239,15 @@ void FrameData::Submit(VulkanContext *vulkan, FrameSubmitType type, FrameDataSha submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &sharedData.swapchainImages_[curSwapchainImage].renderingCompleteSemaphore; } + vulkan->PrepareSubmit(submit_info); VkResult res; if (fenceToTrigger == fence) { VLOG("Doing queue submit, fencing frame %d", this->index); // The fence is waited on by the main thread, they are not allowed to access it simultaneously. + vulkan->LockQueue(); res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); + vulkan->UnlockQueue(); if (sharedData.useMultiThreading) { std::lock_guard lock(fenceMutex); readyForFence = true; @@ -243,7 +255,9 @@ void FrameData::Submit(VulkanContext *vulkan, FrameSubmitType type, FrameDataSha } } else { VLOG("Doing queue submit, fencing something (%p)", fenceToTrigger); + vulkan->LockQueue(); res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); + vulkan->UnlockQueue(); } if (res == VK_ERROR_DEVICE_LOST) { diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.cpp b/Common/GPU/Vulkan/VulkanFramebuffer.cpp index f36b1eb4c6..d2073367e8 100644 --- a/Common/GPU/Vulkan/VulkanFramebuffer.cpp +++ b/Common/GPU/Vulkan/VulkanFramebuffer.cpp @@ -165,7 +165,7 @@ VKRFramebuffer::~VKRFramebuffer() { void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VulkanBarrierBatch *barriers, VKRImage &img, int width, int height, int numLayers, VkSampleCountFlagBits sampleCount, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) { // We don't support more exotic layer setups for now. Mono or stereo. _dbg_assert_(numLayers == 1 || numLayers == 2); - + _dbg_assert_(format != VK_FORMAT_UNDEFINED); VkImageCreateInfo ici{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; ici.arrayLayers = numLayers; ici.mipLevels = 1; @@ -323,7 +323,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas attachments[attachmentCount].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[attachmentCount].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[attachmentCount].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - attachments[attachmentCount].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[attachmentCount].finalLayout = isBackbuffer ? vulkan->GetPresentLayout() : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentCount++; if (hasDepth) { @@ -347,7 +347,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas attachments[attachmentCount].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[attachmentCount].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - attachments[attachmentCount].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[attachmentCount].finalLayout = isBackbuffer ? vulkan->GetPresentLayout() : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachmentCount++; if (hasDepth) { diff --git a/Common/GPU/Vulkan/VulkanImage.cpp b/Common/GPU/Vulkan/VulkanImage.cpp index 6a29a9fdbb..fff06a2d18 100644 --- a/Common/GPU/Vulkan/VulkanImage.cpp +++ b/Common/GPU/Vulkan/VulkanImage.cpp @@ -49,6 +49,8 @@ bool VulkanTexture::CreateDirect(int w, int h, int depth, int numMips, VkFormat return false; } + _dbg_assert_(format != VK_FORMAT_UNDEFINED); + Wipe(); width_ = (int16_t)w; diff --git a/Common/GPU/Vulkan/VulkanLoader.cpp b/Common/GPU/Vulkan/VulkanLoader.cpp index cb1f03b528..e64ab080b2 100644 --- a/Common/GPU/Vulkan/VulkanLoader.cpp +++ b/Common/GPU/Vulkan/VulkanLoader.cpp @@ -686,6 +686,26 @@ bool VulkanLoad(std::string *errorStr) { #endif } +bool VulkanLoadFromGetInstanceProcAddr(VkInstance instance, PFN_vkGetInstanceProcAddr getInstanceProcAddr) { + vkGetInstanceProcAddr = getInstanceProcAddr; + vkCreateInstance = (PFN_vkCreateInstance)getInstanceProcAddr(nullptr, "vkCreateInstance"); + // Per the Vulkan spec, vkGetDeviceProcAddr is not one of the handful of commands queryable with a + // NULL instance - it requires the real, already-existing instance. + vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)getInstanceProcAddr(instance, "vkGetDeviceProcAddr"); + vkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion)getInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion"); + vkEnumerateInstanceExtensionProperties = (PFN_vkEnumerateInstanceExtensionProperties)getInstanceProcAddr(nullptr, "vkEnumerateInstanceExtensionProperties"); + vkEnumerateInstanceLayerProperties = (PFN_vkEnumerateInstanceLayerProperties)getInstanceProcAddr(nullptr, "vkEnumerateInstanceLayerProperties"); + + if (vkCreateInstance && vkGetInstanceProcAddr && vkGetDeviceProcAddr && vkEnumerateInstanceExtensionProperties && vkEnumerateInstanceLayerProperties) { + INFO_LOG(Log::G3D, "VulkanLoadFromGetInstanceProcAddr: Base functions loaded."); + // NOTE: It's ok if vkEnumerateInstanceVersion is missing. + return true; + } else { + ERROR_LOG(Log::G3D, "VulkanLoadFromGetInstanceProcAddr: Failed to load Vulkan base functions"); + return false; + } +} + void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanExtensions &enabledExtensions, uint32_t vulkanInstanceApiVersion) { #if !PPSSPP_PLATFORM(IOS_APP_STORE) INFO_LOG(Log::G3D, "Loading Vulkan instance functions. Instance API version: %08x (%d.%d.%d)", vulkanInstanceApiVersion, VK_API_VERSION_MAJOR(vulkanInstanceApiVersion), VK_API_VERSION_MINOR(vulkanInstanceApiVersion), VK_API_VERSION_PATCH(vulkanInstanceApiVersion)); diff --git a/Common/GPU/Vulkan/VulkanLoader.h b/Common/GPU/Vulkan/VulkanLoader.h index 159cb97c03..e006d6d94f 100644 --- a/Common/GPU/Vulkan/VulkanLoader.h +++ b/Common/GPU/Vulkan/VulkanLoader.h @@ -285,6 +285,13 @@ bool VulkanMayBeAvailable(); void VulkanSetAvailable(bool available); bool VulkanLoad(std::string *errorStr); +// Alternative to VulkanLoad() for adopting a Vulkan loader that a host application (e.g. a libretro +// frontend) already resolved for us, instead of dlopen/dlsym-ing the Vulkan loader library ourselves. +// instance must already be a valid, live VkInstance - vkGetDeviceProcAddr can only be resolved through +// vkGetInstanceProcAddr with a real instance, not NULL (see the Vulkan spec's vkGetInstanceProcAddr +// "encodes the info from" table - only a handful of global commands are NULL-instance queryable, and +// vkGetDeviceProcAddr isn't one of them). +bool VulkanLoadFromGetInstanceProcAddr(VkInstance instance, PFN_vkGetInstanceProcAddr getInstanceProcAddr); void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanExtensions &enabledExtensions, uint32_t vulkanApiVersion); void VulkanLoadDeviceFunctions(VkDevice device, const VulkanExtensions &enabledExtensions, uint32_t vulkanApiVersion); void VulkanFree(); diff --git a/Common/GPU/Vulkan/VulkanPresentation.h b/Common/GPU/Vulkan/VulkanPresentation.h new file mode 100644 index 0000000000..08ec1f570d --- /dev/null +++ b/Common/GPU/Vulkan/VulkanPresentation.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Common/GPU/Vulkan/VulkanLoader.h" + +class VulkanContext; + +// Pluggable replacement for VulkanContext's normal real-swapchain path (InitSwapchain()/GetSwapchain()), +// for host environments that hand PPSSPP a way to render frames without a real VK_KHR_swapchain - for +// example a libretro frontend, which owns the actual presentation surface itself and instead wants each +// finished frame handed back through its own callback. +// +// When VulkanContext::GetPresentation() is null (the default), every consumer of this interface falls back +// to the normal real-swapchain code path unchanged. Setting a VulkanPresentation via +// VulkanContext::SetPresentation() opts into this interface instead. +class VulkanPresentation { +public: + virtual ~VulkanPresentation() {} + + virtual void Destroy(VulkanContext *vulkan) = 0; + + // Mirrors vkAcquireNextImageKHR: waits for the next image to become available, and signals + // signalSemaphore once it's safe to render into it. + virtual VkResult AcquireNextImage(VulkanContext *vulkan, VkSemaphore signalSemaphore, uint32_t *imageIndex) = 0; + // Mirrors vkQueuePresentKHR: hands the finished image back, waiting on waitSemaphore first. + virtual VkResult QueuePresent(VulkanContext *vulkan, VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore) = 0; + + virtual uint32_t GetImageCount() const = 0; + virtual VkImage GetImage(uint32_t index) const = 0; + virtual VkExtent2D GetExtent() const = 0; + virtual VkFormat GetFormat() const = 0; + + // The layout backbuffer images should be left in after rendering, since there's no real presentation + // engine to hand them to via a VK_IMAGE_LAYOUT_PRESENT_SRC_KHR transition. + virtual VkImageLayout GetPresentLayout() const = 0; + + // Some hosts (e.g. libretro) share a single VkQueue between the frontend and the core, and require + // exclusive access to be taken around every vkQueueSubmit/vkQueueWaitIdle. No-ops by default. + virtual void LockQueue() {} + virtual void UnlockQueue() {} + + // Called right before every real vkQueueSubmit, in case the backend needs to adjust it - for example, + // a host that doesn't let semaphores cross the frontend/core boundary meaningfully may need to strip + // them out here. No-op by default. + virtual void PrepareSubmit(VkSubmitInfo &submitInfo) {} +}; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 89999997c2..2ad9cbeeb0 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -148,6 +148,8 @@ bool VulkanQueueRunner::InitDepthStencilBuffer(VkCommandBuffer cmd, VulkanBarrie barrier->srcAccessMask = 0; barrier->dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + _dbg_assert_(depth_format != VK_FORMAT_UNDEFINED); + VkImageViewCreateInfo depth_view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; depth_view_info.image = depth_.image; depth_view_info.format = depth_format; @@ -1705,7 +1707,7 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd // and then back into it. // Regarding layers, backbuffer currently only has one layer. recordBarrier_.TransitionImage(backbufferImage_, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vulkan_->GetPresentLayout(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); copyLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; @@ -1771,7 +1773,7 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd // and then back into it. // Regarding layers, backbuffer currently only has one layer. recordBarrier_.TransitionImage(backbufferImage_, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, vulkan_->GetPresentLayout(), VK_ACCESS_TRANSFER_READ_BIT, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); recordBarrier_.Flush(cmd); // probably not needed diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index fbe280c569..d8b7574b9c 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -316,14 +316,14 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan, bool useThread, } bool VulkanRenderManager::CreateBackbuffers() { - if (!vulkan_->IsSwapchainInited()) { + if (!vulkan_->IsSwapchainInited() && !vulkan_->GetPresentation()) { ERROR_LOG(Log::G3D, "No swapchain - can't create backbuffers"); return false; } VkCommandBuffer cmdInit = GetInitCmd(); - if (vulkan_->HasRealSwapchain()) { + if (vulkan_->HasRealSwapchain() || vulkan_->GetPresentation()) { if (!CreateSwapchainViewsAndDepth(cmdInit, &postInitBarrier_, frameDataShared_)) { return false; } @@ -351,26 +351,37 @@ bool VulkanRenderManager::CreateBackbuffers() { } bool VulkanRenderManager::CreateSwapchainViewsAndDepth(VkCommandBuffer cmdInit, VulkanBarrierBatch *barriers, FrameDataShared &frameDataShared) { - VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &frameDataShared.swapchainImageCount_, nullptr); - _dbg_assert_(res == VK_SUCCESS); + std::vector swapchainImages; + if (VulkanPresentation *presentation = vulkan_->GetPresentation()) { + frameDataShared.swapchainImageCount_ = presentation->GetImageCount(); + swapchainImages.resize(frameDataShared.swapchainImageCount_); + for (uint32_t i = 0; i < frameDataShared.swapchainImageCount_; i++) { + swapchainImages[i] = presentation->GetImage(i); + } + } else { + VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &frameDataShared.swapchainImageCount_, nullptr); + _dbg_assert_(res == VK_SUCCESS); - VkImage *swapchainImages = new VkImage[frameDataShared.swapchainImageCount_]; - res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &frameDataShared.swapchainImageCount_, swapchainImages); - if (res != VK_SUCCESS) { - ERROR_LOG(Log::G3D, "vkGetSwapchainImagesKHR failed"); - delete[] swapchainImages; - return false; + swapchainImages.resize(frameDataShared.swapchainImageCount_); + res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &frameDataShared.swapchainImageCount_, swapchainImages.data()); + if (res != VK_SUCCESS) { + ERROR_LOG(Log::G3D, "vkGetSwapchainImagesKHR failed"); + return false; + } } static const VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; for (uint32_t i = 0; i < frameDataShared.swapchainImageCount_; i++) { SwapchainImageData sc_buffer{}; sc_buffer.image = swapchainImages[i]; - res = vkCreateSemaphore(vulkan_->GetDevice(), &semaphoreCreateInfo, nullptr, &sc_buffer.renderingCompleteSemaphore); + VkResult res = vkCreateSemaphore(vulkan_->GetDevice(), &semaphoreCreateInfo, nullptr, &sc_buffer.renderingCompleteSemaphore); _dbg_assert_(res == VK_SUCCESS); VkImageViewCreateInfo color_image_view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; color_image_view.format = vulkan_->GetSwapchainFormat(); + + _dbg_assert_(color_image_view.format != VK_FORMAT_UNDEFINED); + color_image_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; color_image_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; color_image_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; @@ -393,7 +404,6 @@ bool VulkanRenderManager::CreateSwapchainViewsAndDepth(VkCommandBuffer cmdInit, frameDataShared.swapchainImages_.push_back(sc_buffer); _dbg_assert_(res == VK_SUCCESS); } - delete[] swapchainImages; // Must be before InitBackbufferRenderPass. if (queueRunner_.InitDepthStencilBuffer(cmdInit, barriers)) { diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index fbca02d4e0..44dc0504f0 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -915,8 +915,10 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) : vulkan_(vulkan), renderManager_(vulkan, useRenderThread, frameTimeHistory_) { shaderLanguageDesc_.Init(GLSL_VULKAN); - // Make sure that the surface has been initialized. - _dbg_assert_(vulkan->GetAvailablePresentModes().size() > 0); + // Make sure that the surface has been initialized. Doesn't apply when a pluggable presentation + // backend (see VulkanPresentation.h) is in use instead of a real swapchain/surface - there's no + // present mode concept there at all. + _dbg_assert_(vulkan->GetPresentation() || vulkan->GetAvailablePresentModes().size() > 0); caps_.fragmentShaderFullPrecisionFloat = true; caps_.coordConvention = CoordConvention::Vulkan; @@ -963,12 +965,19 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) caps_.presentInstantModeChange = false; // TODO: Fix this with some work in VulkanContext caps_.presentModesSupported = (PresentMode)0; - for (auto mode : vulkan->GetAvailablePresentModes()) { - switch (mode) { - case VK_PRESENT_MODE_FIFO_KHR: caps_.presentModesSupported |= PresentMode::FIFO; break; - case VK_PRESENT_MODE_IMMEDIATE_KHR: caps_.presentModesSupported |= PresentMode::IMMEDIATE; break; - case VK_PRESENT_MODE_MAILBOX_KHR: caps_.presentModesSupported |= PresentMode::MAILBOX; break; - default: break; // Ignore any other modes. + if (vulkan->GetPresentation()) { + // No real present modes exist in this model (the host, e.g. libretro, owns real presentation + // and controls its own timing) - we always hand back one finished frame at a time serially, + // which is closest in spirit to FIFO. + caps_.presentModesSupported = PresentMode::FIFO; + } else { + for (auto mode : vulkan->GetAvailablePresentModes()) { + switch (mode) { + case VK_PRESENT_MODE_FIFO_KHR: caps_.presentModesSupported |= PresentMode::FIFO; break; + case VK_PRESENT_MODE_IMMEDIATE_KHR: caps_.presentModesSupported |= PresentMode::IMMEDIATE; break; + case VK_PRESENT_MODE_MAILBOX_KHR: caps_.presentModesSupported |= PresentMode::MAILBOX; break; + default: break; // Ignore any other modes. + } } } diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index 595d2af092..af037e9734 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -529,7 +529,7 @@ true true - + true true true @@ -882,6 +882,14 @@ true true + + true + true + true + true + true + true + true true diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index faa765ee1a..a96978d5dd 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -202,7 +202,7 @@ Other Platforms\libretro - + Other Platforms\libretro @@ -433,6 +433,9 @@ Other Platforms\libretro + + Other Platforms\libretro + Other Platforms\libretro diff --git a/libretro/CMakeLists.txt b/libretro/CMakeLists.txt index 7b600ffbbc..8f972d0270 100644 --- a/libretro/CMakeLists.txt +++ b/libretro/CMakeLists.txt @@ -5,7 +5,7 @@ set(LIBRETRO_SRCS LibretroGLContext.cpp LibretroGLCoreContext.cpp LibretroVulkanContext.cpp - libretro_vulkan.cpp) + LibretroVulkanPresentation.cpp) if(WIN32) set(LIBRETRO_SRCS ${LIBRETRO_SRCS} diff --git a/libretro/LibretroVulkanContext.cpp b/libretro/LibretroVulkanContext.cpp index faffcecc69..b0cdf6b2bf 100644 --- a/libretro/LibretroVulkanContext.cpp +++ b/libretro/LibretroVulkanContext.cpp @@ -1,3 +1,5 @@ +#include +#include #include "Common/GPU/Vulkan/VulkanLoader.h" #include "Common/GPU/Vulkan/VulkanContext.h" @@ -10,24 +12,28 @@ #include "Common/Data/Text/Parsers.h" #include "libretro/LibretroVulkanContext.h" +#include "libretro/LibretroVulkanPresentation.h" #include #include #include "ext/glslang/OGLCompilersDLL/InitializeDll.h" #undef fflush -static VulkanContext *vk; +using namespace PPSSPP_VK; -void vk_libretro_init(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); -void vk_libretro_shutdown(); -void vk_libretro_set_hwrender_interface(retro_hw_render_interface *hw_render_interface); -void vk_libretro_wait_for_presentation(); +static VulkanContext *vk; +// Non-owning: lifetime is tied to vk (owns it via VulkanContext::presentation_). +static LibretroVulkanPresentation *presentation; +// Fetched in ContextReset(), consumed when CreateDrawContext() constructs the presentation backend. +static retro_hw_render_interface_vulkan *hwRenderInterface; LibretroVulkanContext::LibretroVulkanContext() : LibretroHWRenderContext(RETRO_HW_CONTEXT_VULKAN, VK_MAKE_VERSION(1, 0, 18)) {} void LibretroVulkanContext::SwapBuffers() { - vk_libretro_wait_for_presentation(); + if (presentation) { + presentation->WaitForPresentation(); + } LibretroHWRenderContext::SwapBuffers(); } @@ -36,37 +42,72 @@ static bool create_device(retro_vulkan_context *context, VkInstance instance, Vk vk = new VulkanContext(); - vk_libretro_init(instance, gpu, surface, get_instance_proc_addr, required_device_extensions, num_required_device_extensions, required_device_layers, num_required_device_layers, required_features); - - // TODO: Here we'll inject the instance and all of the stuff into the VulkanContext. - - vk->CreateInstance({}); - - int physical_device = 0; - while (gpu && vk->GetPhysicalDevice(physical_device) != gpu) { - physical_device++; + if (!VulkanLoadFromGetInstanceProcAddr(instance, get_instance_proc_addr)) { + ERROR_LOG(Log::G3D, "Failed to resolve Vulkan functions from libretro-provided get_instance_proc_addr"); + delete vk; + vk = nullptr; + return false; } - if (!gpu) { + if (vk->CreateInstanceExternal(instance) != VK_SUCCESS) { + ERROR_LOG(Log::G3D, "Failed to adopt libretro-provided Vulkan instance: %s", vk->InitError().c_str()); + delete vk; + vk = nullptr; + return false; + } + + int physical_device = -1; + if (gpu) { + for (int i = 0; i < vk->GetNumPhysicalDevices(); i++) { + if (vk->GetPhysicalDevice(i) == gpu) { + physical_device = i; + break; + } + } + if (physical_device < 0) { + WARN_LOG(Log::G3D, "libretro-provided VkPhysicalDevice %p not found among our own enumerated physical devices - falling back to best physical device", (void *)gpu); + } + } + if (physical_device < 0) { physical_device = vk->GetBestPhysicalDevice(); } - vk->CreateDevice(physical_device); -#ifdef _WIN32 - vk->InitSurface(WINDOWSYSTEM_WIN32, nullptr, nullptr); -#elif defined(__ANDROID__) - vk->InitSurface(WINDOWSYSTEM_ANDROID, nullptr, nullptr); -#elif defined(VK_USE_PLATFORM_METAL_EXT) - vk->InitSurface(WINDOWSYSTEM_METAL_EXT, nullptr, nullptr); -#elif defined(VK_USE_PLATFORM_XLIB_KHR) - vk->InitSurface(WINDOWSYSTEM_XLIB, nullptr, nullptr); -#elif defined(VK_USE_PLATFORM_XCB_KHR) - vk->InitSurface(WINDOWSYSTEM_XCB, nullptr, nullptr); -#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) - vk->InitSurface(WINDOWSYSTEM_WAYLAND, nullptr, nullptr); -#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) - vk->InitSurface(WINDOWSYSTEM_DISPLAY, nullptr, nullptr); -#endif + std::vector extraDeviceExtensions(required_device_extensions, required_device_extensions + num_required_device_extensions); + if (vk->CreateDevice(physical_device, extraDeviceExtensions, required_features) != VK_SUCCESS) { + ERROR_LOG(Log::G3D, "Failed to create Vulkan device: %s", vk->InitError().c_str()); + delete vk; + vk = nullptr; + return false; + } + // CreateDevice() enables whatever of extraDeviceExtensions is available and silently skips the rest - + // if RetroArch's own subsequent Vulkan calls depend on one of these, silently not enabling it could + // cause exactly the kind of driver-level crash that's hard to diagnose, so make sure we'd notice. + for (const char *extraExtension : extraDeviceExtensions) { + bool enabled = false; + for (const char *e : vk->GetDeviceExtensionsEnabled()) { + if (!strcmp(e, extraExtension)) { + enabled = true; + break; + } + } + if (!enabled) { + WARN_LOG(Log::G3D, "libretro required device extension '%s' was not enabled", extraExtension); + } + } + + // Per the libretro create_device contract, the frontend takes over responsibility for eventually + // destroying the VkDevice we just created and handed back to it below. + vk->SetDeviceExternallyOwned(); + + // Normally the graphics queue is only resolved as a side effect of ReinitSurface()/ChooseQueue(), + // since that also needs to check presentation support against a real WSI surface - libretro has no + // such surface (RetroArch owns real presentation itself), so pick the queue directly instead. + if (!vk->ChooseGraphicsQueueWithoutSurface()) { + ERROR_LOG(Log::G3D, "Failed to choose a graphics queue"); + delete vk; + vk = nullptr; + return false; + } context->gpu = vk->GetPhysicalDevice(physical_device); context->device = vk->GetDevice(); @@ -74,6 +115,8 @@ static bool create_device(retro_vulkan_context *context, VkInstance instance, Vk context->queue_family_index = vk->GetGraphicsQueueFamilyIndex(); context->presentation_queue = context->queue; context->presentation_queue_family_index = context->queue_family_index; + INFO_LOG(Log::G3D, "libretro create_device: gpu=%p device=%p queue=%p queue_family_index=%u", + (void *)context->gpu, (void *)context->device, (void *)context->queue, context->queue_family_index); #ifdef _DEBUG fflush(stdout); #endif @@ -109,16 +152,16 @@ bool LibretroVulkanContext::InitAPI(void *wnd, std::string *deviceName, std::str } void LibretroVulkanContext::ContextReset() { - retro_hw_render_interface *vulkan; - if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void **)&vulkan) || !vulkan) { + retro_hw_render_interface *iface; + if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void **)&iface) || !iface) { ERROR_LOG(Log::G3D, "Failed to get HW rendering interface!\n"); return; } - if (vulkan->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION) { - ERROR_LOG(Log::G3D, "HW render interface mismatch, expected %u, got %u!\n", RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION, vulkan->interface_version); + if (iface->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION) { + ERROR_LOG(Log::G3D, "HW render interface mismatch, expected %u, got %u!\n", RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION, iface->interface_version); return; } - vk_libretro_set_hwrender_interface(vulkan); + hwRenderInterface = (retro_hw_render_interface_vulkan *)iface; LibretroHWRenderContext::ContextReset(); } @@ -132,12 +175,19 @@ void LibretroVulkanContext::ContextDestroy() { } void LibretroVulkanContext::CreateDrawContext() { - vk->ReinitSurface(); + int w = g_Config.iInternalResolution * NATIVEWIDTH; + int h = g_Config.iInternalResolution * NATIVEHEIGHT; + if (g_Config.bDisplayCropTo16x9) { + h -= g_Config.iInternalResolution * 2; + } - // TODO: Integrate properly with libretro vulkan context. We currently use a wacky wrapper (libretro_vulkan.cpp) - if (!vk->InitSwapchain(VK_PRESENT_MODE_FIFO_KHR)) { + auto libretroPresentation = std::make_unique(hwRenderInterface, VK_FORMAT_B8G8R8A8_UNORM, VkExtent2D{ (uint32_t)w, (uint32_t)h }); + if (!libretroPresentation->Create(vk)) { + ERROR_LOG(Log::G3D, "Failed to create libretro Vulkan presentation images"); return; } + presentation = libretroPresentation.get(); + vk->SetPresentation(std::move(libretroPresentation)); bool useMultiThreading = g_Config.bRenderMultiThreading; if (g_Config.iInflightFrames == 1) { @@ -161,17 +211,21 @@ void LibretroVulkanContext::ShutdownAPI() { vk->WaitUntilQueueIdle(); - vk->DestroySwapchain(); - vk->DestroySurface(); + if (presentation) { + presentation->Destroy(vk); + vk->SetPresentation(nullptr); + presentation = nullptr; + } + vk->DestroyDevice(); vk->DestroyInstance(); delete vk; vk = nullptr; + hwRenderInterface = nullptr; finalize_glslang(); glslang::DetachProcess(); - vk_libretro_shutdown(); } void *LibretroVulkanContext::GetAPIContext() { return vk; } diff --git a/libretro/LibretroVulkanPresentation.cpp b/libretro/LibretroVulkanPresentation.cpp new file mode 100644 index 0000000000..0734df32f0 --- /dev/null +++ b/libretro/LibretroVulkanPresentation.cpp @@ -0,0 +1,133 @@ +#include "libretro/LibretroVulkanPresentation.h" + +#include "Common/GPU/Vulkan/VulkanContext.h" + +using namespace PPSSPP_VK; + +LibretroVulkanPresentation::LibretroVulkanPresentation(retro_hw_render_interface_vulkan *vulkan, VkFormat format, VkExtent2D extent) + : vulkan_(vulkan), format_(format), extent_(extent) { +} + +bool LibretroVulkanPresentation::Create(VulkanContext *context) { + dedicatedAllocation_ = context->Extensions().KHR_dedicated_allocation; + + uint32_t mask = vulkan_->get_sync_index_mask(vulkan_->handle); + uint32_t count = 0; + while (mask) { + count++; + mask >>= 1; + } + + VkDevice device = context->GetDevice(); + images_.resize(count); + for (uint32_t i = 0; i < count; i++) { + Image &img = images_[i]; + + VkImageCreateInfo info{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + info.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = format_; + info.extent = { extent_.width, extent_.height, 1 }; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if (vkCreateImage(device, &info, nullptr, &img.image) != VK_SUCCESS) { + return false; + } + + VkMemoryRequirements memreq; + vkGetImageMemoryRequirements(device, img.image, &memreq); + + VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + alloc.allocationSize = memreq.size; + + VkMemoryDedicatedAllocateInfoKHR dedicated{ VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; + if (dedicatedAllocation_) { + alloc.pNext = &dedicated; + dedicated.image = img.image; + } + + if (!context->MemoryTypeFromProperties(memreq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &alloc.memoryTypeIndex)) { + return false; + } + if (vkAllocateMemory(device, &alloc, nullptr, &img.memory) != VK_SUCCESS) { + return false; + } + if (vkBindImageMemory(device, img.image, img.memory, 0) != VK_SUCCESS) { + return false; + } + + img.retroImage.create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + img.retroImage.create_info.image = img.image; + img.retroImage.create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + img.retroImage.create_info.format = format_; + img.retroImage.create_info.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; + img.retroImage.create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + img.retroImage.create_info.subresourceRange.layerCount = 1; + img.retroImage.create_info.subresourceRange.levelCount = 1; + if (vkCreateImageView(device, &img.retroImage.create_info, nullptr, &img.retroImage.image_view) != VK_SUCCESS) { + return false; + } + img.retroImage.image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + + return true; +} + +void LibretroVulkanPresentation::Destroy(VulkanContext *context) { + VkDevice device = context->GetDevice(); + for (Image &img : images_) { + if (img.retroImage.image_view) { + vkDestroyImageView(device, img.retroImage.image_view, nullptr); + } + if (img.image) { + vkDestroyImage(device, img.image, nullptr); + } + if (img.memory) { + vkFreeMemory(device, img.memory, nullptr); + } + } + images_.clear(); +} + +VkResult LibretroVulkanPresentation::AcquireNextImage(VulkanContext *vulkan, VkSemaphore signalSemaphore, uint32_t *imageIndex) { + // Unlike a real swapchain, RetroArch doesn't signal signalSemaphore for us here - PrepareSubmit() + // strips any wait on it before the real vkQueueSubmit, matching the old hack's behavior. + vulkan_->wait_sync_index(vulkan_->handle); + *imageIndex = vulkan_->get_sync_index(vulkan_->handle); + return VK_SUCCESS; +} + +VkResult LibretroVulkanPresentation::QueuePresent(VulkanContext *vulkan, VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore) { + std::unique_lock lock(mutex_); + currentIndex_ = (int)imageIndex; + vulkan_->set_image(vulkan_->handle, &images_[imageIndex].retroImage, 0, nullptr, vulkan_->queue_index); + everPresented_ = true; + condVar_.notify_all(); + return VK_SUCCESS; +} + +void LibretroVulkanPresentation::LockQueue() { + vulkan_->lock_queue(vulkan_->handle); +} + +void LibretroVulkanPresentation::UnlockQueue() { + vulkan_->unlock_queue(vulkan_->handle); +} + +void LibretroVulkanPresentation::PrepareSubmit(VkSubmitInfo &submitInfo) { + submitInfo.waitSemaphoreCount = 0; + submitInfo.pWaitSemaphores = nullptr; + submitInfo.signalSemaphoreCount = 0; + submitInfo.pSignalSemaphores = nullptr; +} + +void LibretroVulkanPresentation::WaitForPresentation() { + std::unique_lock lock(mutex_); + if (everPresented_ && currentIndex_ < 0) { + condVar_.wait(lock); + } +} diff --git a/libretro/LibretroVulkanPresentation.h b/libretro/LibretroVulkanPresentation.h new file mode 100644 index 0000000000..b459ac7f3e --- /dev/null +++ b/libretro/LibretroVulkanPresentation.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +// Must come before : this is what defines VK_USE_PLATFORM_WIN32_KHR (etc.) before +// the first inclusion of ext/vulkan/vulkan.h - since that header is include-guarded, whichever include +// reaches it first determines whether the platform-specific declarations (e.g. +// PFN_vkCreateWin32SurfaceKHR) exist for the rest of this translation unit. +#include "Common/GPU/Vulkan/VulkanPresentation.h" + +#define VK_NO_PROTOTYPES +#include + +// Implements VulkanContext's pluggable presentation backend (see VulkanPresentation.h) against +// libretro's retro_hw_render_interface_vulkan: instead of a real VK_KHR_swapchain (which doesn't exist in +// this model - RetroArch owns the real presentation surface itself), PPSSPP renders into its own rotating +// set of VkImages, sized to match RetroArch's sync index mask, and hands each finished frame back via +// set_image() rather than vkQueuePresentKHR. +class LibretroVulkanPresentation : public VulkanPresentation { +public: + // vulkan must remain valid for the lifetime of this object - it's owned by RetroArch, not us. + LibretroVulkanPresentation(retro_hw_render_interface_vulkan *vulkan, VkFormat format, VkExtent2D extent); + + bool Create(VulkanContext *context); + void Destroy(VulkanContext *context) override; + + VkResult AcquireNextImage(VulkanContext *vulkan, VkSemaphore signalSemaphore, uint32_t *imageIndex) override; + VkResult QueuePresent(VulkanContext *vulkan, VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore) override; + + uint32_t GetImageCount() const override { return (uint32_t)images_.size(); } + VkImage GetImage(uint32_t index) const override { return images_[index].image; } + VkExtent2D GetExtent() const override { return extent_; } + VkFormat GetFormat() const override { return format_; } + + VkImageLayout GetPresentLayout() const override { return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } + + void LockQueue() override; + void UnlockQueue() override; + // RetroArch's shared VkQueue model doesn't let semaphores cross the frontend/core boundary + // meaningfully - strip them before the real vkQueueSubmit. + void PrepareSubmit(VkSubmitInfo &submitInfo) override; + + // Called from LibretroVulkanContext::SwapBuffers(), mirroring the old free-function + // vk_libretro_wait_for_presentation(). + void WaitForPresentation(); + +private: + struct Image { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + retro_vulkan_image retroImage{}; + }; + + retro_hw_render_interface_vulkan *vulkan_; + VkFormat format_; + VkExtent2D extent_; + bool dedicatedAllocation_ = false; + + std::vector images_; + + std::mutex mutex_; + std::condition_variable condVar_; + int currentIndex_ = -1; + bool everPresented_ = false; +}; diff --git a/libretro/Makefile.common b/libretro/Makefile.common index ee02fc740f..197f72f7ab 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -1043,7 +1043,7 @@ SOURCES_CXX += \ $(GPUDIR)/Vulkan/TextureCacheVulkan.cpp \ $(GPUDIR)/Vulkan/VulkanUtil.cpp \ $(LIBRETRODIR)/LibretroVulkanContext.cpp \ - $(LIBRETRODIR)/libretro_vulkan.cpp + $(LIBRETRODIR)/LibretroVulkanPresentation.cpp ifeq ($(PLATFORM_EXT), win32) diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index bcde70a12c..142a0176e4 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -1171,7 +1171,11 @@ void retro_init(void) { log_cb = log.log; g_logManager.Init(&g_Config.bEnableLogging); - g_logManager.SetOutputsEnabled(LogOutput::ExternalCallback); + // Also enable LogOutput::DebugString unconditionally (not just via Init()'s IsDebuggerPresent() + // auto-detection, which only fires if a debugger was already attached before Init() ran) so the + // log always shows up in the debugger's Output window when debugging the core in-process with + // RetroArch, regardless of where RetroArch itself routes the ExternalCallback log messages. + g_logManager.EnableOutput(LogOutput::ExternalCallback | LogOutput::DebugString); g_logManager.SetExternalLogCallback(&RetroLogCallback, (void *)log_cb); } diff --git a/libretro/libretro_vulkan.cpp b/libretro/libretro_vulkan.cpp deleted file mode 100644 index cad3f2daed..0000000000 --- a/libretro/libretro_vulkan.cpp +++ /dev/null @@ -1,478 +0,0 @@ -#include -#include -#include -#include -#include - -#include "Common/GPU/Vulkan/VulkanLoader.h" -#include "Common/Log.h" -#include "Core/Config.h" - -#define VK_NO_PROTOTYPES -#include -#include "libretro/LibretroGraphicsContext.h" - -using namespace PPSSPP_VK; - -static retro_hw_render_interface_vulkan *vulkan; - -static struct { - 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; -} vk_init_info; -static bool DEDICATED_ALLOCATION; - -struct VkSwapchainImage { - VkImage handle; - VkDeviceMemory memory; - retro_vulkan_image retro_image; -}; - -struct VkSwapchainKHR_T { - uint32_t count; - std::vector images; - std::mutex mutex; - std::condition_variable condVar; - int current_index; - bool ever_presented = false; -}; -static VkSwapchainKHR_T chain; - -#define LIBRETRO_VK_WARP_LIST() \ - LIBRETRO_VK_WARP_FUNC(vkCreateInstance); \ - LIBRETRO_VK_WARP_FUNC(vkDestroyInstance); \ - LIBRETRO_VK_WARP_FUNC(vkCreateDevice); \ - LIBRETRO_VK_WARP_FUNC(vkDestroyDevice); \ - LIBRETRO_VK_WARP_FUNC(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); \ - LIBRETRO_VK_WARP_FUNC(vkDestroySurfaceKHR); \ - LIBRETRO_VK_WARP_FUNC(vkCreateSwapchainKHR); \ - LIBRETRO_VK_WARP_FUNC(vkGetSwapchainImagesKHR); \ - LIBRETRO_VK_WARP_FUNC(vkAcquireNextImageKHR); \ - LIBRETRO_VK_WARP_FUNC(vkQueuePresentKHR); \ - LIBRETRO_VK_WARP_FUNC(vkDestroySwapchainKHR); \ - LIBRETRO_VK_WARP_FUNC(vkQueueSubmit); \ - LIBRETRO_VK_WARP_FUNC(vkQueueWaitIdle); \ - LIBRETRO_VK_WARP_FUNC(vkCmdPipelineBarrier); \ - LIBRETRO_VK_WARP_FUNC(vkCreateRenderPass); - -#define LIBRETRO_VK_WARP_FUNC(x) \ - PFN_##x x##_org - -LIBRETRO_VK_WARP_FUNC(vkGetInstanceProcAddr); -LIBRETRO_VK_WARP_FUNC(vkGetDeviceProcAddr); -LIBRETRO_VK_WARP_LIST(); - -static VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance_libretro(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance) { - *pInstance = vk_init_info.instance; - return VK_SUCCESS; -} - -static void add_name_unique(std::vector &list, const char *value) { - for (const char *name : list) { - if (!strcmp(value, name)) - return; - } - - list.push_back(value); -} - -static VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice_libretro(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDevice *pDevice) { - VkDeviceCreateInfo newInfo = *pCreateInfo; - - // Add our custom layers - std::vector enabledLayerNames(pCreateInfo->ppEnabledLayerNames, pCreateInfo->ppEnabledLayerNames + pCreateInfo->enabledLayerCount); - - for (uint32_t i = 0; i < vk_init_info.num_required_device_layers; i++) { - add_name_unique(enabledLayerNames, vk_init_info.required_device_layers[i]); - } - - newInfo.enabledLayerCount = (uint32_t)enabledLayerNames.size(); - newInfo.ppEnabledLayerNames = newInfo.enabledLayerCount ? enabledLayerNames.data() : nullptr; - - // Add our custom extensions - std::vector enabledExtensionNames(pCreateInfo->ppEnabledExtensionNames, pCreateInfo->ppEnabledExtensionNames + pCreateInfo->enabledExtensionCount); - - for (uint32_t i = 0; i < vk_init_info.num_required_device_extensions; i++) { - add_name_unique(enabledExtensionNames, vk_init_info.required_device_extensions[i]); - } - - for (const char *extensionName : enabledExtensionNames) { - if (!strcmp(extensionName, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME)) - DEDICATED_ALLOCATION = true; - } - - newInfo.enabledExtensionCount = (uint32_t)enabledExtensionNames.size(); - newInfo.ppEnabledExtensionNames = newInfo.enabledExtensionCount ? enabledExtensionNames.data() : nullptr; - - // Add required features through VkPhysicalDeviceFeatures2 when it is present. - // Vulkan requires pEnabledFeatures to be NULL in that case. - std::unordered_map originalFeaturePointers; - VkPhysicalDeviceFeatures placeholderEnabledFeatures{}; - bool has_features2 = false; - - for (const VkBaseOutStructure *next = (const VkBaseOutStructure *)pCreateInfo->pNext; next != nullptr;) { - if (next->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2) { - VkPhysicalDeviceFeatures *enabledFeatures = &((VkPhysicalDeviceFeatures2 *)next)->features; - originalFeaturePointers.try_emplace(enabledFeatures, *enabledFeatures); - has_features2 = true; - } - - next = (const VkBaseOutStructure *)next->pNext; - } - - if (!has_features2) { - if (newInfo.pEnabledFeatures) { - placeholderEnabledFeatures = *newInfo.pEnabledFeatures; - } - - newInfo.pEnabledFeatures = &placeholderEnabledFeatures; - originalFeaturePointers.try_emplace((VkPhysicalDeviceFeatures *)newInfo.pEnabledFeatures, *newInfo.pEnabledFeatures); - } else { - newInfo.pEnabledFeatures = nullptr; - } - - for (const auto& pair : originalFeaturePointers) { - for (uint32_t i = 0; i < sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32); i++) { - if (((VkBool32 *)vk_init_info.required_features)[i]) - ((VkBool32 *)pair.first)[i] = VK_TRUE; - } - } - - VkResult res = vkCreateDevice_org(physicalDevice, &newInfo, pAllocator, pDevice); - - // The above code potentially modifies application memory. Restore it to avoid unexpected side effects. - for (const auto& pair : originalFeaturePointers) { - *pair.first = pair.second; - } - - return res; -} - -static VKAPI_ATTR VkResult VKAPI_CALL vkCreateLibretroSurfaceKHR(VkInstance instance, const void *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { - *pSurface = vk_init_info.surface; - return VK_SUCCESS; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR_libretro(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) { - VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR_org(physicalDevice, surface, pSurfaceCapabilities); - if (res == VK_SUCCESS) { - int w = g_Config.iInternalResolution * NATIVEWIDTH; - int h = g_Config.iInternalResolution * NATIVEHEIGHT; - - if (g_Config.bDisplayCropTo16x9) - h -= g_Config.iInternalResolution * 2; - - pSurfaceCapabilities->minImageExtent.width = w; - pSurfaceCapabilities->minImageExtent.height = h; - pSurfaceCapabilities->maxImageExtent.width = w; - pSurfaceCapabilities->maxImageExtent.height = h; - pSurfaceCapabilities->currentExtent.width = w; - pSurfaceCapabilities->currentExtent.height = h; - // The libretro fake swapchain presents images directly to the frontend via set_image(), - // so the real surface's pre-rotation transform is irrelevant. Force identity to prevent - // VulkanContext from applying rotation (which would double-rotate on Android). - pSurfaceCapabilities->currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - pSurfaceCapabilities->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - } - return res; -} - -static bool MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) { - VkPhysicalDeviceMemoryProperties memory_properties; - vkGetPhysicalDeviceMemoryProperties(vulkan->gpu, &memory_properties); - // Search memtypes to find first index with those properties - for (uint32_t i = 0; i < 32; i++) { - if ((typeBits & 1) == 1) { - // Type is available, does it match user properties? - if ((memory_properties.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) { - *typeIndex = i; - return true; - } - } - typeBits >>= 1; - } - // No memory types matched, return failure - return false; -} - -static VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR_libretro(VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain) { - uint32_t swapchain_mask = vulkan->get_sync_index_mask(vulkan->handle); - - chain.count = 0; - while (swapchain_mask) { - chain.count++; - swapchain_mask >>= 1; - } - chain.images.resize(chain.count); - - for (uint32_t i = 0; i < chain.count; i++) { - { - VkImageCreateInfo info{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - info.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - info.imageType = VK_IMAGE_TYPE_2D; - info.format = pCreateInfo->imageFormat; - info.extent.width = pCreateInfo->imageExtent.width; - info.extent.height = pCreateInfo->imageExtent.height; - info.extent.depth = 1; - info.mipLevels = 1; - info.arrayLayers = 1; - info.samples = VK_SAMPLE_COUNT_1_BIT; - info.tiling = VK_IMAGE_TILING_OPTIMAL; - info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - vkCreateImage(device, &info, pAllocator, &chain.images[i].handle); - } - - VkMemoryRequirements memreq; - vkGetImageMemoryRequirements(device, chain.images[i].handle, &memreq); - - VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - alloc.allocationSize = memreq.size; - - VkMemoryDedicatedAllocateInfoKHR dedicated{ VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; - if (DEDICATED_ALLOCATION) { - alloc.pNext = &dedicated; - dedicated.image = chain.images[i].handle; - } - - MemoryTypeFromProperties(memreq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &alloc.memoryTypeIndex); - VkResult res = vkAllocateMemory(device, &alloc, pAllocator, &chain.images[i].memory); - assert(res == VK_SUCCESS); - res = vkBindImageMemory(device, chain.images[i].handle, chain.images[i].memory, 0); - assert(res == VK_SUCCESS); - - chain.images[i].retro_image.create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - chain.images[i].retro_image.create_info.image = chain.images[i].handle; - chain.images[i].retro_image.create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; - chain.images[i].retro_image.create_info.format = pCreateInfo->imageFormat; - chain.images[i].retro_image.create_info.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; - chain.images[i].retro_image.create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - chain.images[i].retro_image.create_info.subresourceRange.layerCount = 1; - chain.images[i].retro_image.create_info.subresourceRange.levelCount = 1; - res = vkCreateImageView(device, &chain.images[i].retro_image.create_info, pAllocator, &chain.images[i].retro_image.image_view); - assert(res == VK_SUCCESS); - - chain.images[i].retro_image.image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - } - - chain.current_index = -1; - chain.ever_presented = false; - *pSwapchain = (VkSwapchainKHR)&chain; - - return VK_SUCCESS; -} -static VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainImagesKHR_libretro(VkDevice device, VkSwapchainKHR swapchain_, uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages) { - VkSwapchainKHR_T *swapchain = (VkSwapchainKHR_T *)swapchain_; - if (pSwapchainImages) { - assert(*pSwapchainImageCount <= swapchain->count); - for (int i = 0; i < *pSwapchainImageCount; i++) - pSwapchainImages[i] = swapchain->images[i].handle; - } else - *pSwapchainImageCount = swapchain->count; - - return VK_SUCCESS; -} - -static VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImageKHR_libretro(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *pImageIndex) { - vulkan->wait_sync_index(vulkan->handle); - *pImageIndex = vulkan->get_sync_index(vulkan->handle); -#if 0 - vulkan->set_signal_semaphore(vulkan->handle, semaphore); -#endif - return VK_SUCCESS; -} - -static VKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR_libretro(VkQueue queue, const VkPresentInfoKHR *pPresentInfo) { - VkSwapchainKHR_T *swapchain = (VkSwapchainKHR_T *)pPresentInfo->pSwapchains[0]; - std::unique_lock lock(swapchain->mutex); -#if 0 - if(chain.current_index >= 0) - chain.condVar.wait(lock); -#endif - - chain.current_index = pPresentInfo->pImageIndices[0]; -#if 0 - vulkan->set_image(vulkan->handle, &swapchain->images[pPresentInfo->pImageIndices[0]].retro_image, pPresentInfo->waitSemaphoreCount, pPresentInfo->pWaitSemaphores, vulkan->queue_index); -#else - vulkan->set_image(vulkan->handle, &swapchain->images[pPresentInfo->pImageIndices[0]].retro_image, 0, nullptr, vulkan->queue_index); -#endif - chain.ever_presented = true; - swapchain->condVar.notify_all(); - - return VK_SUCCESS; -} - -void vk_libretro_wait_for_presentation() { - std::unique_lock lock(chain.mutex); - if (chain.ever_presented && chain.current_index < 0) - chain.condVar.wait(lock); -#if 0 - chain.current_index = -1; - chain.condVar.notify_all(); -#endif -} - -static VKAPI_ATTR void VKAPI_CALL vkDestroyInstance_libretro(VkInstance instance, const VkAllocationCallbacks *pAllocator) {} -static VKAPI_ATTR void VKAPI_CALL vkDestroyDevice_libretro(VkDevice device, const VkAllocationCallbacks *pAllocator) {} -static VKAPI_ATTR void VKAPI_CALL vkDestroySurfaceKHR_libretro(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks *pAllocator) {} -static VKAPI_ATTR void VKAPI_CALL vkDestroySwapchainKHR_libretro(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks *pAllocator) { - for (int i = 0; i < chain.count; i++) { - vkDestroyImage(device, chain.images[i].handle, pAllocator); - vkDestroyImageView(device, chain.images[i].retro_image.image_view, pAllocator); - vkFreeMemory(device, chain.images[i].memory, pAllocator); - } - - chain.images.clear(); - chain.count = 0; - chain.current_index = -1; - chain.ever_presented = false; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit_libretro(VkQueue queue, uint32_t submitCount, const VkSubmitInfo *pSubmits, VkFence fence) { - VkResult res = VK_SUCCESS; - -#if 0 - for(int i = 0; i < submitCount; i++) - vulkan->set_command_buffers(vulkan->handle, pSubmits[i].commandBufferCount, pSubmits[i].pCommandBuffers); -#else -#if 1 - for (int i = 0; i < submitCount; i++) { - ((VkSubmitInfo *)pSubmits)[i].waitSemaphoreCount = 0; - ((VkSubmitInfo *)pSubmits)[i].pWaitSemaphores = nullptr; - ((VkSubmitInfo *)pSubmits)[i].signalSemaphoreCount = 0; - ((VkSubmitInfo *)pSubmits)[i].pSignalSemaphores = nullptr; - } -#endif - vulkan->lock_queue(vulkan->handle); - res = vkQueueSubmit_org(queue, submitCount, pSubmits, fence); - vulkan->unlock_queue(vulkan->handle); -#endif - - return res; -} - -VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle_libretro(VkQueue queue) { - vulkan->lock_queue(vulkan->handle); - VkResult res = vkQueueWaitIdle_org(queue); - vulkan->unlock_queue(vulkan->handle); - return res; -} - -VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier_libretro(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers) { - VkImageMemoryBarrier *barriers = (VkImageMemoryBarrier *)pImageMemoryBarriers; - for (int i = 0; i < imageMemoryBarrierCount; i++) { - if (pImageMemoryBarriers[i].oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) { - barriers[i].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barriers[i].srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - } - if (pImageMemoryBarriers[i].newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) { - barriers[i].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barriers[i].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - } - } - return vkCmdPipelineBarrier_org(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, barriers); -} - -VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass_libretro(VkDevice device, const VkRenderPassCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkRenderPass *pRenderPass) { - if (pCreateInfo->pAttachments[0].finalLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) - ((VkAttachmentDescription *)pCreateInfo->pAttachments)[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - return vkCreateRenderPass_org(device, pCreateInfo, pAllocator, pRenderPass); -} - -#undef LIBRETRO_VK_WARP_FUNC -#define LIBRETRO_VK_WARP_FUNC(x) \ - if (!strcmp(pName, #x)) { \ - x##_org = (PFN_##x)fptr; \ - return (PFN_vkVoidFunction)x##_libretro; \ - } - -VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr_libretro(VkInstance instance, const char *pName) { - if (false -#ifdef _WIN32 - || !strcmp(pName, "vkCreateWin32SurfaceKHR") -#endif -#ifdef __ANDROID__ - || !strcmp(pName, "vkCreateAndroidSurfaceKHR") -#endif -#ifdef VK_USE_PLATFORM_METAL_EXT - || !strcmp(pName, "vkCreateMetalSurfaceEXT") -#endif -#ifdef VK_USE_PLATFORM_XLIB_KHR - || !strcmp(pName, "vkCreateXlibSurfaceKHR") -#endif -#ifdef VK_USE_PLATFORM_XCB_KHR - || !strcmp(pName, "vkCreateXcbSurfaceKHR") -#endif -#ifdef VK_USE_PLATFORM_WAYLAND_KHR - || !strcmp(pName, "vkCreateWaylandSurfaceKHR") -#endif -#ifdef VK_USE_PLATFORM_DISPLAY_KHR - || !strcmp(pName, "vkCreateDisplayPlaneSurfaceKHR") -#endif - ) { - return (PFN_vkVoidFunction)vkCreateLibretroSurfaceKHR; - } - - PFN_vkVoidFunction fptr = vk_init_info.get_instance_proc_addr(instance, pName); - if (!fptr) { - ERROR_LOG(Log::G3D, "Failed to load VK instance function: %s", pName); - return fptr; - } - - LIBRETRO_VK_WARP_LIST(); - - return fptr; -} - -VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr_libretro(VkDevice device, const char *pName) { - PFN_vkVoidFunction fptr = vkGetDeviceProcAddr_org(device, pName); - if (!fptr) - return fptr; - - LIBRETRO_VK_WARP_LIST(); - - return fptr; -} - -void vk_libretro_init(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) { - assert(surface); - - vk_init_info.instance = instance; - vk_init_info.gpu = gpu; - vk_init_info.surface = surface; - vk_init_info.get_instance_proc_addr = get_instance_proc_addr; - vk_init_info.required_device_extensions = required_device_extensions; - vk_init_info.num_required_device_extensions = num_required_device_extensions; - vk_init_info.required_device_layers = required_device_layers; - vk_init_info.num_required_device_layers = num_required_device_layers; - vk_init_info.required_features = required_features; - - vkGetInstanceProcAddr_org = vkGetInstanceProcAddr; - vkGetInstanceProcAddr = vkGetInstanceProcAddr_libretro; - vkGetDeviceProcAddr_org = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr(instance, "vkGetDeviceProcAddr");; - vkGetDeviceProcAddr = vkGetDeviceProcAddr_libretro; - vkCreateInstance = vkCreateInstance_libretro; - - vkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion)vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion"); - vkEnumerateInstanceExtensionProperties = (PFN_vkEnumerateInstanceExtensionProperties)vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceExtensionProperties"); - vkEnumerateInstanceLayerProperties = (PFN_vkEnumerateInstanceLayerProperties)vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceLayerProperties"); -} - -void vk_libretro_set_hwrender_interface(retro_hw_render_interface *hw_render_interface) { - vulkan = (retro_hw_render_interface_vulkan *)hw_render_interface; -} - -void vk_libretro_shutdown() { - memset(&vk_init_info, 0, sizeof(vk_init_info)); - vulkan = nullptr; - DEDICATED_ALLOCATION = false; -}