mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 09:45:24 +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.
232 lines
8.1 KiB
C++
232 lines
8.1 KiB
C++
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "Common/GPU/Vulkan/VulkanLoader.h"
|
|
#include "Common/GPU/Vulkan/VulkanContext.h"
|
|
#include "Common/GPU/Vulkan/VulkanDebug.h"
|
|
#include "Common/Log.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "Core/System.h"
|
|
#include "GPU/GPUCommon.h"
|
|
#include "Common/Data/Text/Parsers.h"
|
|
|
|
#include "libretro/LibretroVulkanContext.h"
|
|
#include "libretro/LibretroVulkanPresentation.h"
|
|
#include <libretro_vulkan.h>
|
|
#include <GPU/Vulkan/VulkanRenderManager.h>
|
|
#include "ext/glslang/OGLCompilersDLL/InitializeDll.h"
|
|
|
|
#undef fflush
|
|
|
|
using namespace PPSSPP_VK;
|
|
|
|
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() {
|
|
if (presentation) {
|
|
presentation->WaitForPresentation();
|
|
}
|
|
LibretroHWRenderContext::SwapBuffers();
|
|
}
|
|
|
|
static bool create_device(retro_vulkan_context *context, VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr, const char **required_device_extensions, unsigned num_required_device_extensions, const char **required_device_layers, unsigned num_required_device_layers, const VkPhysicalDeviceFeatures *required_features) {
|
|
init_glslang();
|
|
|
|
vk = new VulkanContext();
|
|
|
|
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 (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();
|
|
}
|
|
|
|
std::vector<const char *> 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();
|
|
context->queue = vk->GetGraphicsQueue();
|
|
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
|
|
return true;
|
|
}
|
|
|
|
static const VkApplicationInfo *GetApplicationInfo(void) {
|
|
static VkApplicationInfo app_info{ VK_STRUCTURE_TYPE_APPLICATION_INFO };
|
|
app_info.pApplicationName = "PPSSPP";
|
|
app_info.applicationVersion = Version(PPSSPP_GIT_VERSION).ToInteger();
|
|
app_info.pEngineName = "PPSSPP";
|
|
app_info.engineVersion = 2;
|
|
app_info.apiVersion = VK_API_VERSION_1_0;
|
|
return &app_info;
|
|
}
|
|
|
|
bool LibretroVulkanContext::InitAPI(void *wnd, std::string *deviceName, std::string *error_message) {
|
|
if (!LibretroHWRenderContext::InitHW(true)) {
|
|
return false;
|
|
}
|
|
|
|
static const struct retro_hw_render_context_negotiation_interface_vulkan iface = {
|
|
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN,
|
|
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION,
|
|
GetApplicationInfo,
|
|
create_device, // Callback above.
|
|
nullptr,
|
|
};
|
|
Libretro::environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, (void *)&iface);
|
|
|
|
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
|
|
return true;
|
|
}
|
|
|
|
void LibretroVulkanContext::ContextReset() {
|
|
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 (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;
|
|
}
|
|
hwRenderInterface = (retro_hw_render_interface_vulkan *)iface;
|
|
|
|
LibretroHWRenderContext::ContextReset();
|
|
}
|
|
|
|
void LibretroVulkanContext::ContextDestroy() {
|
|
INFO_LOG(Log::G3D, "LibretroVulkanContext::ContextDestroy()");
|
|
if (vk) {
|
|
vk->WaitUntilQueueIdle();
|
|
LibretroHWRenderContext::ContextDestroy();
|
|
}
|
|
}
|
|
|
|
void LibretroVulkanContext::CreateDrawContext() {
|
|
int w = g_Config.iInternalResolution * NATIVEWIDTH;
|
|
int h = g_Config.iInternalResolution * NATIVEHEIGHT;
|
|
if (g_Config.bDisplayCropTo16x9) {
|
|
h -= g_Config.iInternalResolution * 2;
|
|
}
|
|
|
|
auto libretroPresentation = std::make_unique<LibretroVulkanPresentation>(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) {
|
|
useMultiThreading = false;
|
|
}
|
|
draw_ = Draw::T3DCreateVulkanContext(vk, useMultiThreading);
|
|
|
|
((VulkanRenderManager*)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER))->SetInflightFrames(g_Config.iInflightFrames);
|
|
SetGPUBackend(GPUBackend::VULKAN);
|
|
}
|
|
|
|
void LibretroVulkanContext::ShutdownAPI() {
|
|
if (!vk) {
|
|
return;
|
|
}
|
|
|
|
if (draw_)
|
|
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, vk->GetBackbufferWidth(), vk->GetBackbufferHeight());
|
|
|
|
LibretroHWRenderContext::ShutdownAPI();
|
|
|
|
vk->WaitUntilQueueIdle();
|
|
|
|
if (presentation) {
|
|
presentation->Destroy(vk);
|
|
vk->SetPresentation(nullptr);
|
|
presentation = nullptr;
|
|
}
|
|
|
|
vk->DestroyDevice();
|
|
vk->DestroyInstance();
|
|
|
|
delete vk;
|
|
vk = nullptr;
|
|
hwRenderInterface = nullptr;
|
|
|
|
finalize_glslang();
|
|
glslang::DetachProcess();
|
|
}
|
|
|
|
void *LibretroVulkanContext::GetAPIContext() { return vk; }
|