From 4c623fc793f0390822c601245ea88d580491330a Mon Sep 17 00:00:00 2001 From: neatnoise Date: Wed, 23 Sep 2026 18:10:16 +0200 Subject: [PATCH 1/4] feat(vulkan): add vk_quality config option for encoder quality level Add user-configurable quality level for Vulkan Video encoders. The quality parameter trades encoding speed for better visual quality. Features: - Add vk_quality config option (0 = fastest, higher = better quality) - Query driver's maxQualityLevels via Vulkan API at stream start - Automatically clamp user value to driver's supported maximum - Reuse FFmpeg's Vulkan instance for efficient quality query (no overhead) - Cache render node lookup to avoid repeated instance creation (~10-20ms saved) - Log warning when clamping occurs The maximum quality level is driver-dependent: - AMD (RADV): typically 4 - NVIDIA/Intel: varies by driver Usage in sunshine.conf: vk_quality = 2 --- docs/configuration.md | 42 +++++ src/config.cpp | 11 ++ src/config.h | 1 + src/platform/linux/vulkan_encode.cpp | 155 ++++++++++++++++++ .../assets/web/configs/config_tabs.json | 3 +- .../configs/tabs/encoders/VulkanEncoder.vue | 12 ++ .../assets/web/public/assets/locale/en.json | 5 + 7 files changed, 228 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 43bf4c69b63..d5d88d932b8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -3346,6 +3346,48 @@ consent prompt for directory ACLs, so the Web UI shows setup steps for correctin +### vk_quality + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Description + Determines encoder tradeoff between quality and speed. Auto lets the driver decide. + @note{This option only applies when using Vulkan [encoder](#encoder).} +
Default@code{}auto@endcode
Example@code{} + vk_quality = quality + @endcode
Optionsautodriver default
speedSpeed (prefer speed)
balancedBalanced
qualityQuality (prefer quality)
+ ## Software Encoder ### sw_preset diff --git a/src/config.cpp b/src/config.cpp index 7bdd9f7c5ac..1d59a2db377 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -774,6 +774,7 @@ namespace config { { 2, // vk.tune (default: ll - low latency) 2, // vk.rc_mode (default: cbr) + 0, // vk.quality (default: 0 = auto, 1 = speed, 2 = balanced, 3 = quality) }, {}, // capture @@ -1693,6 +1694,16 @@ namespace config { int_f(vars, "vk_tune", video.vk.tune); int_f(vars, "vk_rc_mode", video.vk.rc_mode); + std::string vk_quality; + string_f(vars, "vk_quality", vk_quality); + static const std::unordered_map vk_quality_map = { + {"speed"sv, 1}, + {"balanced"sv, 2}, + {"quality"sv, 3} + }; + if (auto it = vk_quality_map.find(vk_quality); it != vk_quality_map.end()) { + video.vk.quality = it->second; + } string_f(vars, "capture", video.capture); string_f(vars, "encoder", video.encoder); diff --git a/src/config.h b/src/config.h index 8bd3aa4d533..9f4cdc32d0d 100644 --- a/src/config.h +++ b/src/config.h @@ -132,6 +132,7 @@ namespace config { struct { int tune; ///< Vulkan encoder tuning mode: default, HQ, LL, ULL, or lossless. int rc_mode; ///< Vulkan encoder rate-control mode: driver, CQP, CBR, or VBR. + int quality; ///< Vulkan encoder quality preset: 0 = auto, 1 = speed, 2 = balanced, 3 = quality. } vk; ///< Vulkan encoder options. std::string capture; ///< Capture backend name selected by configuration. diff --git a/src/platform/linux/vulkan_encode.cpp b/src/platform/linux/vulkan_encode.cpp index 1e6a78994e4..da916e47836 100644 --- a/src/platform/linux/vulkan_encode.cpp +++ b/src/platform/linux/vulkan_encode.cpp @@ -39,9 +39,122 @@ using namespace std::literals; namespace vk { + /** + * @brief Query the maximum encoder quality level for a given codec. + * + * This uses FFmpeg's Vulkan context (via get_proc_addr) to query + * VkVideoEncodeCapabilitiesKHR.maxQualityLevels. The profile chain + * follows FFmpeg's vulkan_encode.c pattern: + * profile -> usage_info -> codec_profile_ext + * And output chain: + * caps -> enc_caps -> codec_caps_ext + * + * @param codec_id FFmpeg codec ID (AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_AV1). + * @param vk_ctx FFmpeg's AVVulkanDeviceContext with initialized Vulkan handles. + * @return Maximum quality level supported by the driver, or 0 if query fails. + */ + static uint32_t query_max_quality_level(AVCodecID codec_id, AVVulkanDeviceContext *vk_ctx) { + if (!vk_ctx || !vk_ctx->inst || !vk_ctx->phys_dev || !vk_ctx->get_proc_addr) { + return 0; + } + + auto vkGetPhysicalDeviceVideoCapabilitiesKHR_fn = (PFN_vkGetPhysicalDeviceVideoCapabilitiesKHR) + vk_ctx->get_proc_addr(vk_ctx->inst, "vkGetPhysicalDeviceVideoCapabilitiesKHR"); + + if (!vkGetPhysicalDeviceVideoCapabilitiesKHR_fn) { + return 0; + } + + // Codec-specific profile extensions (zero-initialized) + VkVideoEncodeH264ProfileInfoKHR h264_profile = {}; + h264_profile.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PROFILE_INFO_KHR; + h264_profile.stdProfileIdc = STD_VIDEO_H264_PROFILE_IDC_HIGH; + + VkVideoEncodeH265ProfileInfoKHR h265_profile = {}; + h265_profile.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_PROFILE_INFO_KHR; + h265_profile.stdProfileIdc = STD_VIDEO_H265_PROFILE_IDC_MAIN; + + VkVideoEncodeAV1ProfileInfoKHR av1_profile = {}; + av1_profile.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_AV1_PROFILE_INFO_KHR; + av1_profile.stdProfile = STD_VIDEO_AV1_PROFILE_MAIN; + + // Codec-specific capabilities extensions (zero-initialized) + VkVideoEncodeH264CapabilitiesKHR h264_caps = {}; + h264_caps.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_CAPABILITIES_KHR; + + VkVideoEncodeH265CapabilitiesKHR h265_caps = {}; + h265_caps.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_CAPABILITIES_KHR; + + VkVideoEncodeAV1CapabilitiesKHR av1_caps = {}; + av1_caps.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_AV1_CAPABILITIES_KHR; + + // Usage info chain (follows FFmpeg's pattern) + VkVideoEncodeUsageInfoKHR usage_info = {}; + usage_info.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_USAGE_INFO_KHR; + usage_info.videoUsageHints = VK_VIDEO_ENCODE_USAGE_STREAMING_BIT_KHR; + usage_info.videoContentHints = VK_VIDEO_ENCODE_CONTENT_DEFAULT_KHR; + usage_info.tuningMode = VK_VIDEO_ENCODE_TUNING_MODE_LOW_LATENCY_KHR; + + // Profile info + VkVideoProfileInfoKHR profile = {}; + profile.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR; + profile.pNext = &usage_info; + profile.chromaSubsampling = VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR; + profile.lumaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR; + profile.chromaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR; + + // Encode capabilities output + VkVideoEncodeCapabilitiesKHR enc_caps = {}; + enc_caps.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_CAPABILITIES_KHR; + + // Set codec operation and chain codec-specific extensions (profile and caps) + switch (codec_id) { + case AV_CODEC_ID_H264: + profile.videoCodecOperation = VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR; + usage_info.pNext = &h264_profile; + enc_caps.pNext = &h264_caps; + break; + case AV_CODEC_ID_HEVC: + profile.videoCodecOperation = VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR; + usage_info.pNext = &h265_profile; + enc_caps.pNext = &h265_caps; + break; + case AV_CODEC_ID_AV1: + profile.videoCodecOperation = VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR; + usage_info.pNext = &av1_profile; + enc_caps.pNext = &av1_caps; + break; + default: + return 0; + } + + // Main capabilities output + VkVideoCapabilitiesKHR caps = {}; + caps.sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR; + caps.pNext = &enc_caps; + + VkResult result = vkGetPhysicalDeviceVideoCapabilitiesKHR_fn(vk_ctx->phys_dev, &profile, &caps); + if (result == VK_SUCCESS) { + BOOST_LOG(debug) << "[vulkan] Driver max quality level: "sv << enc_caps.maxQualityLevels; + return enc_caps.maxQualityLevels; + } + + return 0; + } + // Match a DRI render node path to a Vulkan device index via VK_EXT_physical_device_drm. // Returns the index as a string (e.g. "1"), or empty string if no match. + // Result is cached since render device doesn't change during runtime. static std::string find_vulkan_index_for_render_node(const char *render_path) { + // Cache result to avoid repeated Vulkan instance creation (~10-20ms overhead) + static std::string cached_result; + static std::string cached_path; + static bool cached = false; + + if (cached && cached_path == render_path) { + return cached_result; + } + struct stat node_stat; if (stat(render_path, &node_stat) < 0) { return {}; @@ -85,6 +198,12 @@ namespace vk { } } vkDestroyInstance(inst, nullptr); + + // Cache the result + cached_path = render_path; + cached_result = result; + cached = true; + return result; } @@ -193,6 +312,42 @@ namespace vk { if (config::video.vk.rc_mode == 4) { ctx->rc_min_rate = 0; } + + // Map quality preset to driver's quality range (same abstraction as VAAPI) + // 0 = auto (don't pass anything to FFmpeg), 1 = speed, 2 = balanced, 3 = quality + // Note: Vulkan quality is 0 = fastest, higher = slower/better quality + int quality_preset = config::video.vk.quality; + if (quality_preset > 0 && ctx->hw_frames_ctx) { + // Reuse FFmpeg's Vulkan instance and device instead of creating new ones + auto *frames_ctx = (AVHWFramesContext *) ctx->hw_frames_ctx->data; + auto *dev_ctx = (AVHWDeviceContext *) frames_ctx->device_ref->data; + auto *vk_ctx = (AVVulkanDeviceContext *) dev_ctx->hwctx; + + uint32_t max_quality = query_max_quality_level(ctx->codec_id, vk_ctx); + if (max_quality > 0) { + // max_quality is the highest valid quality level (0 to max_quality) + int target_quality = 0; + const char *preset_name = "speed"; + switch (quality_preset) { + default: + case 1: // speed (level 0 = fastest) + target_quality = 0; + preset_name = "speed"; + break; + case 2: // balanced (middle level) + target_quality = max_quality / 2; + preset_name = "balanced"; + break; + case 3: // quality (max level = best quality) + target_quality = max_quality; + preset_name = "quality"; + break; + } + av_dict_set_int(options, "quality", target_quality, 0); + BOOST_LOG(info) << "[vulkan] Encoder quality set to "sv << target_quality + << " ("sv << preset_name << "), driver range: 0-"sv << max_quality; + } + } } /** diff --git a/src_assets/common/assets/web/configs/config_tabs.json b/src_assets/common/assets/web/configs/config_tabs.json index 97bb80925af..b85e8edf0ee 100644 --- a/src_assets/common/assets/web/configs/config_tabs.json +++ b/src_assets/common/assets/web/configs/config_tabs.json @@ -166,7 +166,8 @@ "nameKey": "config.category_vulkan_encoder", "options": { "vk_tune": 2, - "vk_rc_mode": 2 + "vk_rc_mode": 2, + "vk_quality": "" } }, { diff --git a/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue b/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue index 0bb6cfe0d9b..32fbf7637f9 100644 --- a/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue +++ b/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue @@ -34,5 +34,17 @@ const config = ref(props.config)
{{ $t('config.vk_rc_mode_desc') }}
+ + +
+ + +
{{ $t('config.vk_quality_desc') }}
+
diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index c2f3db92d20..b71921eca15 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -462,6 +462,11 @@ "vk_tune_hq": "High Quality (hq)", "vk_tune_ll": "Low Latency (ll) (default)", "vk_tune_ull": "Ultra Low Latency (ull)", + "vk_quality": "Quality", + "vk_quality_desc": "Determines encoder tradeoff between quality and speed. Auto lets the driver decide.", + "vk_quality_speed": "speed -- prefer speed", + "vk_quality_balanced": "balanced -- balanced", + "vk_quality_quality": "quality -- prefer quality", "virtual_sink": "Virtual Sink", "virtual_sink_desc": "Manually specify a virtual audio device to use. If unset, the device is chosen automatically. We strongly recommend leaving this field blank to use automatic device selection!", "virtual_sink_placeholder": "Steam Streaming Speakers", From a5a7055961f6d476dcabfe41c55387bb346ecc55 Mon Sep 17 00:00:00 2001 From: neatnoise Date: Fri, 25 Sep 2026 12:36:03 +0200 Subject: [PATCH 2/4] feat(vulkan): validate quality levels before setting --- src/platform/linux/vulkan_encode.cpp | 77 +++++++++++++++++++++------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/src/platform/linux/vulkan_encode.cpp b/src/platform/linux/vulkan_encode.cpp index da916e47836..f50af7f23db 100644 --- a/src/platform/linux/vulkan_encode.cpp +++ b/src/platform/linux/vulkan_encode.cpp @@ -40,20 +40,21 @@ using namespace std::literals; namespace vk { /** - * @brief Query the maximum encoder quality level for a given codec. + * @brief Query and validate encoder quality level for a given codec. * - * This uses FFmpeg's Vulkan context (via get_proc_addr) to query - * VkVideoEncodeCapabilitiesKHR.maxQualityLevels. The profile chain - * follows FFmpeg's vulkan_encode.c pattern: - * profile -> usage_info -> codec_profile_ext - * And output chain: - * caps -> enc_caps -> codec_caps_ext + * This uses FFmpeg's Vulkan context to query maxQualityLevels and validate + * the requested level using vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR. + * If the requested level is invalid, falls back to level 0. * * @param codec_id FFmpeg codec ID (AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_AV1). * @param vk_ctx FFmpeg's AVVulkanDeviceContext with initialized Vulkan handles. - * @return Maximum quality level supported by the driver, or 0 if query fails. + * @param requested_level Requested quality level to validate (0 = speed, max = quality). + * @param[out] max_level Maximum quality level supported by the driver. + * @return Validated quality level (requested_level if valid, 0 if invalid). */ - static uint32_t query_max_quality_level(AVCodecID codec_id, AVVulkanDeviceContext *vk_ctx) { + static uint32_t query_and_validate_quality_level(AVCodecID codec_id, AVVulkanDeviceContext *vk_ctx, uint32_t requested_level, uint32_t *max_level) { + *max_level = 0; + if (!vk_ctx || !vk_ctx->inst || !vk_ctx->phys_dev || !vk_ctx->get_proc_addr) { return 0; } @@ -134,12 +135,42 @@ namespace vk { caps.pNext = &enc_caps; VkResult result = vkGetPhysicalDeviceVideoCapabilitiesKHR_fn(vk_ctx->phys_dev, &profile, &caps); - if (result == VK_SUCCESS) { - BOOST_LOG(debug) << "[vulkan] Driver max quality level: "sv << enc_caps.maxQualityLevels; - return enc_caps.maxQualityLevels; + if (result != VK_SUCCESS) { + return 0; + } + + *max_level = enc_caps.maxQualityLevels; + BOOST_LOG(debug) << "[vulkan] Driver max quality level: "sv << *max_level; + + // If requested level is 0 or within range, try to validate it + if (requested_level > *max_level) { + BOOST_LOG(warning) << "[vulkan] Requested quality level "sv << requested_level + << " exceeds max "sv << *max_level << ", using max"sv; + requested_level = *max_level; + } + + // Try to validate the requested level + auto vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR_fn = (PFN_vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR) + vk_ctx->get_proc_addr(vk_ctx->inst, "vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR"); + + if (vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR_fn) { + VkPhysicalDeviceVideoEncodeQualityLevelInfoKHR quality_level_info = {}; + quality_level_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_ENCODE_QUALITY_LEVEL_INFO_KHR; + quality_level_info.pVideoProfile = &profile; + quality_level_info.qualityLevel = requested_level; + + VkVideoEncodeQualityLevelPropertiesKHR quality_props = {}; + quality_props.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_QUALITY_LEVEL_PROPERTIES_KHR; + + result = vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR_fn(vk_ctx->phys_dev, &quality_level_info, &quality_props); + if (result != VK_SUCCESS) { + BOOST_LOG(warning) << "[vulkan] Quality level "sv << requested_level + << " validation failed, falling back to 0"sv; + return 0; + } } - return 0; + return requested_level; } // Match a DRI render node path to a Vulkan device index via VK_EXT_physical_device_drm. @@ -323,11 +354,15 @@ namespace vk { auto *dev_ctx = (AVHWDeviceContext *) frames_ctx->device_ref->data; auto *vk_ctx = (AVVulkanDeviceContext *) dev_ctx->hwctx; - uint32_t max_quality = query_max_quality_level(ctx->codec_id, vk_ctx); + // Calculate target quality based on preset + uint32_t max_quality = 0; + uint32_t target_quality = 0; + const char *preset_name = "speed"; + + // First pass: get max quality to calculate target + query_and_validate_quality_level(ctx->codec_id, vk_ctx, 0, &max_quality); + if (max_quality > 0) { - // max_quality is the highest valid quality level (0 to max_quality) - int target_quality = 0; - const char *preset_name = "speed"; switch (quality_preset) { default: case 1: // speed (level 0 = fastest) @@ -343,8 +378,12 @@ namespace vk { preset_name = "quality"; break; } - av_dict_set_int(options, "quality", target_quality, 0); - BOOST_LOG(info) << "[vulkan] Encoder quality set to "sv << target_quality + + // Validate the calculated target quality level + uint32_t validated_quality = query_and_validate_quality_level(ctx->codec_id, vk_ctx, target_quality, &max_quality); + + av_dict_set_int(options, "quality", validated_quality, 0); + BOOST_LOG(info) << "[vulkan] Encoder quality set to "sv << validated_quality << " ("sv << preset_name << "), driver range: 0-"sv << max_quality; } } From 0467e240c2ca1d011be7c9e3f804cefd9aae0b74 Mon Sep 17 00:00:00 2001 From: neatnoise Date: Fri, 25 Sep 2026 12:43:11 +0200 Subject: [PATCH 3/4] feat(vulkan): change default quality to balanced --- src/config.cpp | 2 +- src_assets/common/assets/web/configs/config_tabs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 1d59a2db377..1f17a7dbff4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -774,7 +774,7 @@ namespace config { { 2, // vk.tune (default: ll - low latency) 2, // vk.rc_mode (default: cbr) - 0, // vk.quality (default: 0 = auto, 1 = speed, 2 = balanced, 3 = quality) + 2, // vk.quality (default: 2 = balanced, 0 = auto, 1 = speed, 3 = quality) }, {}, // capture diff --git a/src_assets/common/assets/web/configs/config_tabs.json b/src_assets/common/assets/web/configs/config_tabs.json index b85e8edf0ee..c477936c396 100644 --- a/src_assets/common/assets/web/configs/config_tabs.json +++ b/src_assets/common/assets/web/configs/config_tabs.json @@ -167,7 +167,7 @@ "options": { "vk_tune": 2, "vk_rc_mode": 2, - "vk_quality": "" + "vk_quality": "balanced" } }, { From b74333b9b89a8602999fb9fd3e6636d65132b0ef Mon Sep 17 00:00:00 2001 From: neatnoise Date: Fri, 25 Sep 2026 13:07:23 +0200 Subject: [PATCH 4/4] fix(vulkan): simplify quality config - remove auto option, default to balanced --- docs/configuration.md | 11 +++-------- src/config.cpp | 2 +- src/config.h | 2 +- src/platform/linux/vulkan_encode.cpp | 17 ++++++++++------- .../web/configs/tabs/encoders/VulkanEncoder.vue | 1 - .../assets/web/public/assets/locale/en.json | 4 ++-- 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index d5d88d932b8..fb6ce69c5a1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -3352,13 +3352,13 @@ consent prompt for directory ACLs, so the Web UI shows setup steps for correctin Description - Determines encoder tradeoff between quality and speed. Auto lets the driver decide. + Determines encoder tradeoff between quality and speed. @note{This option only applies when using Vulkan [encoder](#encoder).} Default - @code{}auto@endcode + @code{}balanced@endcode Example @@ -3368,18 +3368,13 @@ consent prompt for directory ACLs, so the Web UI shows setup steps for correctin Options - auto - driver default - - - speed Speed (prefer speed) balanced - Balanced + Balanced (default) diff --git a/src/config.cpp b/src/config.cpp index 1f17a7dbff4..ad44a29d8d8 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -774,7 +774,7 @@ namespace config { { 2, // vk.tune (default: ll - low latency) 2, // vk.rc_mode (default: cbr) - 2, // vk.quality (default: 2 = balanced, 0 = auto, 1 = speed, 3 = quality) + 2, // vk.quality (default: 2 = balanced, 1 = speed, 3 = quality) }, {}, // capture diff --git a/src/config.h b/src/config.h index 9f4cdc32d0d..76176e00f51 100644 --- a/src/config.h +++ b/src/config.h @@ -132,7 +132,7 @@ namespace config { struct { int tune; ///< Vulkan encoder tuning mode: default, HQ, LL, ULL, or lossless. int rc_mode; ///< Vulkan encoder rate-control mode: driver, CQP, CBR, or VBR. - int quality; ///< Vulkan encoder quality preset: 0 = auto, 1 = speed, 2 = balanced, 3 = quality. + int quality; ///< Vulkan encoder quality preset: 1 = speed, 2 = balanced (default), 3 = quality. } vk; ///< Vulkan encoder options. std::string capture; ///< Capture backend name selected by configuration. diff --git a/src/platform/linux/vulkan_encode.cpp b/src/platform/linux/vulkan_encode.cpp index f50af7f23db..94b353df2ee 100644 --- a/src/platform/linux/vulkan_encode.cpp +++ b/src/platform/linux/vulkan_encode.cpp @@ -44,7 +44,7 @@ namespace vk { * * This uses FFmpeg's Vulkan context to query maxQualityLevels and validate * the requested level using vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR. - * If the requested level is invalid, falls back to level 0. + * If the requested level is invalid, returns 0 to indicate failure. * * @param codec_id FFmpeg codec ID (AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_AV1). * @param vk_ctx FFmpeg's AVVulkanDeviceContext with initialized Vulkan handles. @@ -164,8 +164,6 @@ namespace vk { result = vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR_fn(vk_ctx->phys_dev, &quality_level_info, &quality_props); if (result != VK_SUCCESS) { - BOOST_LOG(warning) << "[vulkan] Quality level "sv << requested_level - << " validation failed, falling back to 0"sv; return 0; } } @@ -345,7 +343,7 @@ namespace vk { } // Map quality preset to driver's quality range (same abstraction as VAAPI) - // 0 = auto (don't pass anything to FFmpeg), 1 = speed, 2 = balanced, 3 = quality + // 1 = speed, 2 = balanced (default), 3 = quality // Note: Vulkan quality is 0 = fastest, higher = slower/better quality int quality_preset = config::video.vk.quality; if (quality_preset > 0 && ctx->hw_frames_ctx) { @@ -382,9 +380,14 @@ namespace vk { // Validate the calculated target quality level uint32_t validated_quality = query_and_validate_quality_level(ctx->codec_id, vk_ctx, target_quality, &max_quality); - av_dict_set_int(options, "quality", validated_quality, 0); - BOOST_LOG(info) << "[vulkan] Encoder quality set to "sv << validated_quality - << " ("sv << preset_name << "), driver range: 0-"sv << max_quality; + if (validated_quality == target_quality) { + av_dict_set_int(options, "quality", validated_quality, 0); + BOOST_LOG(info) << "[vulkan] Encoder quality set to "sv << validated_quality + << " ("sv << preset_name << "), driver range: 0-"sv << max_quality; + } else { + BOOST_LOG(warning) << "[vulkan] Quality level "sv << target_quality + << " is not supported by the driver; using default"sv; + } } } } diff --git a/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue b/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue index 32fbf7637f9..edd408623b1 100644 --- a/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue +++ b/src_assets/common/assets/web/configs/tabs/encoders/VulkanEncoder.vue @@ -39,7 +39,6 @@ const config = ref(props.config)