azahar/src/citra_libretro/libretro_vk.h
Eric Warmenhoven d9b77cc21e
Implement libretro core (#1215)
* libretro core

* Bringing citra libretro implementation over
* libretro: hook up vulkan renderer
* libretro: github actions
* libretro: gyro
* libretro: core options v2
* libretro: on ios turn off shader jit if unavailable
* moltenvk 1.3.0 introduces 8-bit indexes but allocates 16-bit for metal; this ends up allocating stream buffer * 2 = 132MiB. Instead, just use 16-bit indexes. (This will be necessary for standalone when bumping moltenvk version.)

* libretro core: address review feedback

* libretro: microphone support

* cmake: Add ENABLE_ROOM_STANDALONE to list of incompatible libretro flags

* libretro: proper initial geometry

* libretro: fix software renderer

* libretro: address review feedback

* .github/libretro.yml: Pin macOS runners at macOS 26

* ci: Remove explicit selection of Xcode 16.0

* .github/libretro.yml: remove unnecessary windows builder apt commands

* .github/libretro.yml: bump min macos version to 11.0

* ci: Re-enable CI jobs for all libretro cores

This is under the condition that we don't introduce build cache for these builds

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
Co-authored-by: PabloMK7 <hackyglitch2@gmail.com>
2026-02-19 22:30:25 +00:00

175 lines
5.2 KiB
C++

// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <variant>
#include <fmt/format.h>
#include "common/common_types.h"
#include "common/dynamic_library/dynamic_library.h"
#include "video_core/renderer_vulkan/vk_common.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_platform.h"
#include "libretro_vulkan.h"
VK_DEFINE_HANDLE(VmaAllocation)
namespace LibRetro {
extern void VulkanResetContext();
/// Returns VkApplicationInfo for negotiation interface
const VkApplicationInfo* GetVulkanApplicationInfo();
/// CreateDevice callback for negotiation interface
bool CreateVulkanDevice(struct retro_vulkan_context* context, VkInstance instance,
VkPhysicalDevice gpu, VkSurfaceKHR surface,
PFN_vkGetInstanceProcAddr get_instance_proc_addr,
const char** required_device_extensions,
unsigned num_required_device_extensions,
const char** required_device_layers, unsigned num_required_device_layers,
const VkPhysicalDeviceFeatures* required_features);
} // namespace LibRetro
namespace Vulkan {
class LibRetroVKInstance : public Instance {
public:
explicit LibRetroVKInstance(Frontend::EmuWindow& window, u32 physical_device_index);
/// Returns the Vulkan instance
vk::Instance GetInstance() const override;
/// Returns the Vulkan device
vk::Device GetDevice() const override;
};
class Scheduler;
class RenderManager;
class MasterSemaphore;
/// LibRetro-specific MasterSemaphore implementation
class MasterSemaphoreLibRetro : public MasterSemaphore {
using Waitable = std::pair<vk::Fence, u64>;
public:
explicit MasterSemaphoreLibRetro(const Instance& instance);
~MasterSemaphoreLibRetro() override;
void Refresh() override;
void Wait(u64 tick) override;
void SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal,
u64 signal_value) override;
private:
void WaitThread(std::stop_token token);
vk::Fence GetFreeFence();
const Instance& instance;
std::deque<vk::Fence> free_queue;
std::queue<Waitable> wait_queue;
std::mutex free_mutex;
std::mutex wait_mutex;
std::condition_variable free_cv;
std::condition_variable_any wait_cv;
std::jthread wait_thread;
};
/// Factory function for scheduler to create LibRetro MasterSemaphore
std::unique_ptr<MasterSemaphore> CreateLibRetroMasterSemaphore(const Instance& instance);
struct Frame {
u32 width;
u32 height;
VmaAllocation allocation;
vk::Framebuffer framebuffer;
vk::Image image;
vk::ImageView image_view;
vk::Semaphore render_ready;
vk::Fence present_done;
vk::CommandBuffer cmdbuf;
};
/// LibRetro-specific PresentWindow implementation (same interface as desktop version)
class PresentWindow final {
public:
explicit PresentWindow(Frontend::EmuWindow& emu_window, const Instance& instance,
Scheduler& scheduler, bool low_refresh_rate);
~PresentWindow();
/// Waits for all queued frames to finish presenting.
void WaitPresent();
/// Returns the last used render frame.
Frame* GetRenderFrame();
/// Recreates the render frame to match provided parameters.
void RecreateFrame(Frame* frame, u32 width, u32 height);
/// Queues the provided frame for presentation.
void Present(Frame* frame);
/// This is called to notify the rendering backend of a surface change
void NotifySurfaceChanged();
[[nodiscard]] vk::RenderPass Renderpass() const noexcept {
return present_renderpass;
}
u32 ImageCount() const noexcept {
return static_cast<u32>(frame_pool.size());
}
private:
/// Creates the render pass for LibRetro output
vk::RenderPass CreateRenderpass();
/// Creates output texture for LibRetro submission
void CreateOutputTexture(u32 width, u32 height);
/// Destroys current output texture
void DestroyOutputTexture();
/// Creates frame resources
void CreateFrameResources();
/// Destroys frame resources
void DestroyFrameResources();
private:
Frontend::EmuWindow& emu_window;
const Instance& instance;
[[maybe_unused]] Scheduler& scheduler;
// LibRetro output texture (replaces swapchain)
vk::Image output_image{};
vk::ImageView output_image_view{};
VmaAllocation output_allocation{};
vk::Format output_format{vk::Format::eR8G8B8A8Unorm};
vk::ImageViewCreateInfo output_view_create_info{};
// Frame management
vk::RenderPass present_renderpass{};
vk::CommandPool command_pool{};
std::vector<Frame> frame_pool;
u32 current_frame_index{0};
// Current output dimensions
u32 output_width{0};
u32 output_height{0};
// Vulkan objects
vk::Queue graphics_queue{};
// Persistent LibRetro image descriptor, must persist across frames for RetroArch frame duping
// during pause
retro_vulkan_image persistent_libretro_image{};
};
} // namespace Vulkan