Files
Cameron Gutman 6e231778a2 Fix blocking during rendering rather than in waitToRender() on MoltenVK
MoltenVK does a lazy fetch of the drawable only when it's being modified, so waitToRender() is completely ineffective as is. Rendering a small empty overlay in waitToRender() forces the next drawable wait to happen in waitToRender(), before we pick the current frame to render. This removes one display refresh period of latency when using V-sync.
2026-06-25 20:01:29 -05:00

154 lines
6.3 KiB
C++

#pragma once
#include "renderer.h"
#ifdef Q_OS_WIN32
#define VK_USE_PLATFORM_WIN32_KHR
#endif
#include <libplacebo/log.h>
#include <libplacebo/renderer.h>
#include <libplacebo/vulkan.h>
#ifdef Q_OS_DARWIN
class MetalVulkanTextureFactory {
public:
MetalVulkanTextureFactory(pl_vulkan vulkan);
~MetalVulkanTextureFactory();
bool mapVideoToolboxToPlacebo(const AVFrame *frame, pl_frame* mappedFrame);
void unmapVideoToolboxFromPlacebo(pl_frame* mappedFrame);
private:
pl_vulkan m_Vulkan;
/* CVMetalTextureCacheRef */ void* m_TextureCache = nullptr;
};
// Work around direct-to-display mode sometimes (but not always!) blocking us
// from getting a new drawable while the current one is getting scanned out.
#define PLVK_USE_DYNAMIC_SWAPCHAIN_DEPTH 1
// MoltenVK will block for the next drawable when we render the next frame
// rather than inside pl_swapchain_start_frame(), so we will force it to wait
// by rendering some no-op work right after we get the new swapchain frame.
#define PLVK_USE_EARLY_RENDER_TO_WAIT 1
#endif
class PlVkRenderer : public IFFmpegRenderer {
public:
PlVkRenderer(AVHWDeviceType hwDeviceType = AV_HWDEVICE_TYPE_NONE, IFFmpegRenderer *backendRenderer = nullptr);
virtual ~PlVkRenderer() override;
virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override;
virtual void renderFrame(AVFrame* frame) override;
virtual bool testRenderFrame(AVFrame* frame) override;
virtual void waitToRender() override;
virtual void cleanupRenderContext() override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO) override;
virtual int getRendererAttributes() override;
virtual int getDecoderColorspace() override;
virtual int getDecoderColorRange() override;
virtual int getDecoderCapabilities() override;
virtual bool isPixelFormatSupported(int videoFormat, enum AVPixelFormat pixelFormat) override;
virtual AVPixelFormat getPreferredPixelFormat(int videoFormat) override;
private:
static void lockQueue(AVHWDeviceContext *dev_ctx, uint32_t queue_family, uint32_t index);
static void unlockQueue(AVHWDeviceContext *dev_ctx, uint32_t queue_family, uint32_t index);
static void overlayUploadComplete(void* opaque);
void beginRenderTiming();
void endRenderTiming();
bool createSwapchain(int depth);
bool createOverlay(pl_overlay* overlay, SDL_Surface* surface);
bool mapAvFrameToPlacebo(const AVFrame *frame, pl_frame* mappedFrame);
void unmapAvFrameFromPlacebo(const AVFrame *frame, pl_frame* mappedFrame);
bool populateQueues(int videoFormat);
bool chooseVulkanDevice(PDECODER_PARAMETERS params, bool hdrOutputRequired);
bool tryInitializeDevice(VkPhysicalDevice device, VkPhysicalDeviceProperties* deviceProps,
PDECODER_PARAMETERS decoderParams, bool hdrOutputRequired);
bool isExtensionSupportedByPhysicalDevice(VkPhysicalDevice device, const char* extensionName);
bool isPresentModeSupportedByPhysicalDevice(VkPhysicalDevice device, VkPresentModeKHR presentMode);
bool isColorSpaceSupportedByPhysicalDevice(VkPhysicalDevice device, VkColorSpaceKHR colorSpace);
bool isSurfacePresentationSupportedByPhysicalDevice(VkPhysicalDevice device);
// The backend renderer if we're frontend-only
IFFmpegRenderer* m_Backend;
AVHWDeviceType m_HwDeviceType;
#ifdef Q_OS_DARWIN
std::unique_ptr<MetalVulkanTextureFactory> m_MetalTextureFactory;
#endif
#ifdef PLVK_USE_DYNAMIC_SWAPCHAIN_DEPTH
int m_DelayedPresents = 0;
Uint32 m_RenderStartTime = 0;
#endif
// SDL state
SDL_Window* m_Window = nullptr;
// Stream state
int m_MaxVideoFps;
// The libplacebo rendering state
pl_log m_Log = nullptr;
pl_vk_inst m_PlVkInstance = nullptr;
VkSurfaceKHR m_VkSurface = VK_NULL_HANDLE;
int m_SwapchainDepth = 0;
VkPresentModeKHR m_VkPresentMode;
pl_vulkan m_Vulkan = nullptr;
pl_swapchain m_Swapchain = nullptr;
pl_renderer m_Renderer = nullptr;
pl_tex m_Textures[PL_MAX_PLANES] = {};
pl_color_space m_LastColorspace = {};
#ifdef PLVK_USE_EARLY_RENDER_TO_WAIT
pl_overlay m_EmptyOverlay = {};
pl_overlay_part m_EmptyOverlayPart = {};
#endif
// Pending swapchain state shared between waitToRender(), renderFrame(), and cleanupRenderContext()
pl_swapchain_frame m_SwapchainFrame = {};
bool m_HasPendingSwapchainFrame = false;
// Overlay state
SDL_SpinLock m_OverlayLock = 0;
struct {
// The staging overlay state is copied here under the overlay lock in the render thread.
//
// These values can be safely read by the render thread outside of the overlay lock,
// but the copy from stagingOverlay to overlay must only happen under the overlay
// lock when hasStagingOverlay is true.
bool hasOverlay;
pl_overlay overlay;
// This state is written by the overlay update thread
//
// NB: hasStagingOverlay may be false even if there is a staging overlay texture present,
// because this is how the overlay update path indicates that the overlay is not currently
// safe for the render thread to read.
//
// It is safe for the overlay update thread to write to stagingOverlay outside of the lock,
// as long as hasStagingOverlay is false.
bool hasStagingOverlay;
pl_overlay stagingOverlay;
} m_Overlays[Overlay::OverlayMax] = {};
// Device context used for hwaccel decoders
AVBufferRef* m_HwDeviceCtx = nullptr;
// Vulkan functions we call directly
PFN_vkDestroySurfaceKHR fn_vkDestroySurfaceKHR = nullptr;
PFN_vkGetPhysicalDeviceQueueFamilyProperties2 fn_vkGetPhysicalDeviceQueueFamilyProperties2 = nullptr;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fn_vkGetPhysicalDeviceSurfacePresentModesKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fn_vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr;
PFN_vkEnumeratePhysicalDevices fn_vkEnumeratePhysicalDevices = nullptr;
PFN_vkGetPhysicalDeviceProperties fn_vkGetPhysicalDeviceProperties = nullptr;
PFN_vkGetPhysicalDeviceSurfaceSupportKHR fn_vkGetPhysicalDeviceSurfaceSupportKHR = nullptr;
PFN_vkEnumerateDeviceExtensionProperties fn_vkEnumerateDeviceExtensionProperties = nullptr;
};