From 17af71fe7a947605ecc4aa9b3b07840a8cddf70d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 9 Jun 2024 15:00:56 -0500 Subject: [PATCH] Allow the Vulkan renderer to accept HDR input even without HDR output capability In addition to resolving issues with mixing HDR and SDR displays and moving between them while streaming, it also allows streaming HDR content to an SDR display with tone mapping handled transparently by libplacebo. --- app/streaming/video/ffmpeg-renderers/plvk.cpp | 35 ++++++++++--------- app/streaming/video/ffmpeg-renderers/plvk.h | 5 +-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/streaming/video/ffmpeg-renderers/plvk.cpp b/app/streaming/video/ffmpeg-renderers/plvk.cpp index 8e95d0b0..80796688 100644 --- a/app/streaming/video/ffmpeg-renderers/plvk.cpp +++ b/app/streaming/video/ffmpeg-renderers/plvk.cpp @@ -148,7 +148,7 @@ PlVkRenderer::~PlVkRenderer() pl_log_destroy(&m_Log); } -bool PlVkRenderer::chooseVulkanDevice(PDECODER_PARAMETERS params) +bool PlVkRenderer::chooseVulkanDevice(PDECODER_PARAMETERS params, bool hdrOutputRequired) { uint32_t physicalDeviceCount = 0; fn_vkEnumeratePhysicalDevices(m_PlVkInstance->instance, &physicalDeviceCount, nullptr); @@ -161,7 +161,7 @@ bool PlVkRenderer::chooseVulkanDevice(PDECODER_PARAMETERS params) // First, try the first device in the list to support device selection layers // that put the user's preferred GPU in the first slot. fn_vkGetPhysicalDeviceProperties(physicalDevices[0], &deviceProps); - if (tryInitializeDevice(physicalDevices[0], &deviceProps, params)) { + if (tryInitializeDevice(physicalDevices[0], &deviceProps, params, hdrOutputRequired)) { return true; } devicesTried.emplace(0); @@ -177,7 +177,7 @@ bool PlVkRenderer::chooseVulkanDevice(PDECODER_PARAMETERS params) VkPhysicalDeviceProperties deviceProps; fn_vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProps); if (deviceProps.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { - if (tryInitializeDevice(physicalDevices[i], &deviceProps, params)) { + if (tryInitializeDevice(physicalDevices[i], &deviceProps, params, hdrOutputRequired)) { return true; } devicesTried.emplace(i); @@ -194,7 +194,7 @@ bool PlVkRenderer::chooseVulkanDevice(PDECODER_PARAMETERS params) VkPhysicalDeviceProperties deviceProps; fn_vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProps); if (deviceProps.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { - if (tryInitializeDevice(physicalDevices[i], &deviceProps, params)) { + if (tryInitializeDevice(physicalDevices[i], &deviceProps, params, hdrOutputRequired)) { return true; } devicesTried.emplace(i); @@ -210,18 +210,20 @@ bool PlVkRenderer::chooseVulkanDevice(PDECODER_PARAMETERS params) VkPhysicalDeviceProperties deviceProps; fn_vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProps); - if (tryInitializeDevice(physicalDevices[i], &deviceProps, params)) { + if (tryInitializeDevice(physicalDevices[i], &deviceProps, params, hdrOutputRequired)) { return true; } devicesTried.emplace(i); } SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "No suitable Vulkan devices found!"); + "No suitable %sVulkan devices found!", + hdrOutputRequired ? "HDR-capable " : ""); return false; } -bool PlVkRenderer::tryInitializeDevice(VkPhysicalDevice device, VkPhysicalDeviceProperties* deviceProps, PDECODER_PARAMETERS decoderParams) +bool PlVkRenderer::tryInitializeDevice(VkPhysicalDevice device, VkPhysicalDeviceProperties* deviceProps, + PDECODER_PARAMETERS decoderParams, bool hdrOutputRequired) { // Check the Vulkan API version first to ensure it meets libplacebo's minimum if (deviceProps->apiVersion < PL_VK_MIN_VERSION) { @@ -271,7 +273,7 @@ bool PlVkRenderer::tryInitializeDevice(VkPhysicalDevice device, VkPhysicalDevice return false; } - if ((decoderParams->videoFormat & VIDEO_FORMAT_MASK_10BIT) && !isColorSpaceSupportedByPhysicalDevice(device, VK_COLOR_SPACE_HDR10_ST2084_EXT)) { + if (hdrOutputRequired && !isColorSpaceSupportedByPhysicalDevice(device, VK_COLOR_SPACE_HDR10_ST2084_EXT)) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Vulkan device '%s' does not support HDR10 (ST.2084 PQ)", deviceProps->deviceName); @@ -390,8 +392,12 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS params) return false; } - // Enumerate physical devices and choose one that is suitable for our needs - if (!chooseVulkanDevice(params)) { + // Enumerate physical devices and choose one that is suitable for our needs. + // + // For HDR streaming, we try to find an HDR-capable Vulkan device first then + // try another search without the HDR requirement if the first attempt fails. + if (!chooseVulkanDevice(params, params->videoFormat & VIDEO_FORMAT_MASK_10BIT) && + (!(params->videoFormat & VIDEO_FORMAT_MASK_10BIT) || !chooseVulkanDevice(params, false))) { return false; } @@ -896,13 +902,8 @@ bool PlVkRenderer::notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO info) int PlVkRenderer::getRendererAttributes() { - int attributes = 0; - - if (isColorSpaceSupportedByPhysicalDevice(m_Vulkan->phys_device, VK_COLOR_SPACE_HDR10_ST2084_EXT)) { - attributes |= RENDERER_ATTRIBUTE_HDR_SUPPORT; - } - - return attributes; + // This renderer supports HDR (including tone mapping to SDR displays) + return RENDERER_ATTRIBUTE_HDR_SUPPORT; } int PlVkRenderer::getDecoderColorRange() diff --git a/app/streaming/video/ffmpeg-renderers/plvk.h b/app/streaming/video/ffmpeg-renderers/plvk.h index 3596002e..8edec5ad 100644 --- a/app/streaming/video/ffmpeg-renderers/plvk.h +++ b/app/streaming/video/ffmpeg-renderers/plvk.h @@ -37,8 +37,9 @@ private: bool mapAvFrameToPlacebo(const AVFrame *frame, pl_frame* mappedFrame); bool getQueue(VkQueueFlags requiredFlags, uint32_t* queueIndex, uint32_t* queueCount); - bool chooseVulkanDevice(PDECODER_PARAMETERS params); - bool tryInitializeDevice(VkPhysicalDevice device, VkPhysicalDeviceProperties* deviceProps, PDECODER_PARAMETERS decoderParams); + 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);