mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 17:55:23 +02:00
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.
134 lines
4.7 KiB
C++
134 lines
4.7 KiB
C++
#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<std::mutex> 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<std::mutex> lock(mutex_);
|
|
if (everPresented_ && currentIndex_ < 0) {
|
|
condVar_.wait(lock);
|
|
}
|
|
}
|