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;
-}