Skip to content

Commit d997a0d

Browse files
committed
Dynamically adjust libplacebo swapchain depth on macOS
Direct-to-display mode on some Macs (M4 Mac Mini) can retain our drawables, which cuts the frame rate in half if we're in double-buffered mode. We don't want to use triple-buffered mode all the time because it increases latency when used if not required, so detect delayed presentation and enable it on the fly if needed.
1 parent 06bd8a7 commit d997a0d

2 files changed

Lines changed: 83 additions & 24 deletions

File tree

app/streaming/video/ffmpeg-renderers/plvk.cpp

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ bool PlVkRenderer::isExtensionSupportedByPhysicalDevice(VkPhysicalDevice device,
421421
bool PlVkRenderer::initialize(PDECODER_PARAMETERS params)
422422
{
423423
m_Window = params->window;
424+
m_MaxVideoFps = params->frameRate;
424425

425426
unsigned int instanceExtensionCount = 0;
426427
if (!SDL_Vulkan_GetInstanceExtensions(params->window, &instanceExtensionCount, nullptr)) {
@@ -488,18 +489,17 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS params)
488489
return false;
489490
}
490491

491-
VkPresentModeKHR presentMode;
492492
if (params->enableVsync) {
493493
// FIFO mode improves frame pacing compared with Mailbox, especially for
494494
// platforms like X11 that lack a VSyncSource implementation for Pacer.
495-
presentMode = VK_PRESENT_MODE_FIFO_KHR;
495+
m_VkPresentMode = VK_PRESENT_MODE_FIFO_KHR;
496496
}
497497
else {
498498
// We want immediate mode for V-Sync disabled if possible
499499
if (isPresentModeSupportedByPhysicalDevice(m_Vulkan->phys_device, VK_PRESENT_MODE_IMMEDIATE_KHR)) {
500500
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
501501
"Using Immediate present mode with V-Sync disabled");
502-
presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
502+
m_VkPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
503503
}
504504
else {
505505
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
@@ -509,41 +509,26 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS params)
509509
if (isPresentModeSupportedByPhysicalDevice(m_Vulkan->phys_device, VK_PRESENT_MODE_FIFO_RELAXED_KHR)) {
510510
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
511511
"Using FIFO Relaxed present mode with V-Sync disabled");
512-
presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
512+
m_VkPresentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
513513
}
514514
// Mailbox at least provides non-blocking behavior
515515
else if (isPresentModeSupportedByPhysicalDevice(m_Vulkan->phys_device, VK_PRESENT_MODE_MAILBOX_KHR)) {
516516
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
517517
"Using Mailbox present mode with V-Sync disabled");
518-
presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
518+
m_VkPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
519519
}
520520
// FIFO is always supported
521521
else {
522522
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
523523
"Using FIFO present mode with V-Sync disabled");
524-
presentMode = VK_PRESENT_MODE_FIFO_KHR;
524+
m_VkPresentMode = VK_PRESENT_MODE_FIFO_KHR;
525525
}
526526
}
527527
}
528528

529-
pl_vulkan_swapchain_params vkSwapchainParams = {};
530-
vkSwapchainParams.surface = m_VkSurface;
531-
vkSwapchainParams.present_mode = presentMode;
532-
vkSwapchainParams.swapchain_depth = 1; // No queued frames
533-
#if PL_API_VER >= 338
534-
vkSwapchainParams.disable_10bit_sdr = true; // Some drivers don't dither 10-bit SDR output correctly
535-
#endif
536-
537-
{
538-
// Don't let Qt take DRM master from us during pl_vulkan_create_swapchain()
539-
DrmMasterLocker locker;
540-
541-
m_Swapchain = pl_vulkan_create_swapchain(m_Vulkan, &vkSwapchainParams);
542-
if (m_Swapchain == nullptr) {
543-
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
544-
"pl_vulkan_create_swapchain() failed");
545-
return false;
546-
}
529+
// Start with a swapchain that is double-buffered for lowest display latency
530+
if (!createSwapchain(1)) {
531+
return false;
547532
}
548533

549534
m_Renderer = pl_renderer_create(m_Log, m_Vulkan->gpu);
@@ -622,6 +607,35 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS params)
622607
return true;
623608
}
624609

610+
611+
bool PlVkRenderer::createSwapchain(int depth)
612+
{
613+
pl_swapchain_destroy(&m_Swapchain);
614+
615+
pl_vulkan_swapchain_params vkSwapchainParams = {};
616+
vkSwapchainParams.surface = m_VkSurface;
617+
vkSwapchainParams.present_mode = m_VkPresentMode;
618+
vkSwapchainParams.swapchain_depth = depth;
619+
#if PL_API_VER >= 338
620+
vkSwapchainParams.disable_10bit_sdr = true; // Some drivers don't dither 10-bit SDR output correctly
621+
#endif
622+
623+
{
624+
// Don't let Qt take DRM master from us during pl_vulkan_create_swapchain()
625+
DrmMasterLocker locker;
626+
627+
m_Swapchain = pl_vulkan_create_swapchain(m_Vulkan, &vkSwapchainParams);
628+
if (m_Swapchain == nullptr) {
629+
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
630+
"pl_vulkan_create_swapchain() failed");
631+
return false;
632+
}
633+
}
634+
635+
m_SwapchainDepth = depth;
636+
return true;
637+
}
638+
625639
bool PlVkRenderer::prepareDecoderContext(AVCodecContext *context, AVDictionary **)
626640
{
627641
if (m_HwDeviceCtx) {
@@ -991,6 +1005,10 @@ void PlVkRenderer::renderFrame(AVFrame *frame)
9911005
targetFrame.crop.x1 = dst.x + dst.w;
9921006
targetFrame.crop.y1 = dst.y + dst.h;
9931007

1008+
#ifdef PLVK_USE_DYNAMIC_SWAPCHAIN_DEPTH
1009+
Uint32 renderStartTime = SDL_GetTicks();
1010+
#endif
1011+
9941012
// Render the video image and overlays into the swapchain buffer
9951013
targetFrame.num_overlays = (int)overlays.size();
9961014
targetFrame.overlays = overlays.data();
@@ -1013,6 +1031,32 @@ void PlVkRenderer::renderFrame(AVFrame *frame)
10131031
goto UnmapExit;
10141032
}
10151033

1034+
#ifdef PLVK_USE_DYNAMIC_SWAPCHAIN_DEPTH
1035+
// Trigger a switch to triple-buffered mode if our frame presentation time
1036+
// exceeds 110% of the frame interval for half a second of frames.
1037+
if (SDL_GetTicks() - renderStartTime > (1100U / m_MaxVideoFps)) {
1038+
m_DelayedPresents++;
1039+
}
1040+
else if (m_DelayedPresents > 0) {
1041+
m_DelayedPresents--;
1042+
}
1043+
1044+
if (m_DelayedPresents == m_MaxVideoFps / 2 && m_SwapchainDepth < 2) {
1045+
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
1046+
"Switching to triple-buffered swapchain after delayed presentations");
1047+
if (!createSwapchain(2)) {
1048+
// Recreate the renderer
1049+
SDL_Event event;
1050+
event.type = SDL_RENDER_DEVICE_RESET;
1051+
SDL_PushEvent(&event);
1052+
goto UnmapExit;
1053+
}
1054+
1055+
// Restore the swapchain's colorspace from the previous swapchain frame
1056+
pl_swapchain_colorspace_hint(m_Swapchain, &targetFrame.color);
1057+
}
1058+
#endif
1059+
10161060
#ifdef Q_OS_WIN32
10171061
// On Windows, we swap buffers here instead of waitToRender()
10181062
// to avoid some performance problems on Nvidia GPUs.

app/streaming/video/ffmpeg-renderers/plvk.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ class MetalVulkanTextureFactory {
2323
pl_vulkan m_Vulkan;
2424
/* CVMetalTextureCacheRef */ void* m_TextureCache = nullptr;
2525
};
26+
27+
// Work around direct-to-display mode sometimes (but not always!) blocking us
28+
// from getting a new drawable while the current one is getting scanned out.
29+
#define PLVK_USE_DYNAMIC_SWAPCHAIN_DEPTH 1
30+
2631
#endif
2732

2833
class PlVkRenderer : public IFFmpegRenderer {
@@ -49,6 +54,7 @@ class PlVkRenderer : public IFFmpegRenderer {
4954
static void unlockQueue(AVHWDeviceContext *dev_ctx, uint32_t queue_family, uint32_t index);
5055
static void overlayUploadComplete(void* opaque);
5156

57+
bool createSwapchain(int depth);
5258
bool mapAvFrameToPlacebo(const AVFrame *frame, pl_frame* mappedFrame);
5359
void unmapAvFrameFromPlacebo(const AVFrame *frame, pl_frame* mappedFrame);
5460
bool populateQueues(int videoFormat);
@@ -68,13 +74,22 @@ class PlVkRenderer : public IFFmpegRenderer {
6874
std::unique_ptr<MetalVulkanTextureFactory> m_MetalTextureFactory;
6975
#endif
7076

77+
#ifdef PLVK_USE_DYNAMIC_SWAPCHAIN_DEPTH
78+
int m_DelayedPresents = 0;
79+
#endif
80+
7181
// SDL state
7282
SDL_Window* m_Window = nullptr;
7383

84+
// Stream state
85+
int m_MaxVideoFps;
86+
7487
// The libplacebo rendering state
7588
pl_log m_Log = nullptr;
7689
pl_vk_inst m_PlVkInstance = nullptr;
7790
VkSurfaceKHR m_VkSurface = VK_NULL_HANDLE;
91+
int m_SwapchainDepth = 0;
92+
VkPresentModeKHR m_VkPresentMode;
7893
pl_vulkan m_Vulkan = nullptr;
7994
pl_swapchain m_Swapchain = nullptr;
8095
pl_renderer m_Renderer = nullptr;

0 commit comments

Comments
 (0)