From ae6ea69c851546258dd4fe7062104ad23da75da8 Mon Sep 17 00:00:00 2001 From: Autumn Ashton Date: Fri, 15 Aug 2025 22:26:21 +0100 Subject: [PATCH] radv: Implement VK_KHR_video_encode_quantization_map Implement VK_KHR_video_encode_quantization_map on < VCN5. Passes CTS for `*quantization_map*`. Signed-off-by: Autumn Ashton Reviewed-by: David Rosca Closes: #13717 Part-of: --- docs/relnotes/new_features.txt | 1 + src/amd/vulkan/radv_formats.c | 4 + src/amd/vulkan/radv_physical_device.c | 5 ++ src/amd/vulkan/radv_video.c | 115 +++++++++++++++++++++++--- src/amd/vulkan/radv_video_enc.c | 36 ++++++++ 5 files changed, 149 insertions(+), 12 deletions(-) diff --git a/docs/relnotes/new_features.txt b/docs/relnotes/new_features.txt index f0d37759be6..52e22f4e1df 100644 --- a/docs/relnotes/new_features.txt +++ b/docs/relnotes/new_features.txt @@ -6,3 +6,4 @@ VK_ARM_shader_core_builtins on panvk VK_KHR_shader_untyped_pointers on anv cl_ext_immutable_memory_objects VK_KHR_video_encode_intra_refresh on radv +VK_KHR_video_encode_quantization_map on radv \ No newline at end of file diff --git a/src/amd/vulkan/radv_formats.c b/src/amd/vulkan/radv_formats.c index 76989e9f070..d1874e59113 100644 --- a/src/amd/vulkan/radv_formats.c +++ b/src/amd/vulkan/radv_formats.c @@ -492,6 +492,10 @@ radv_physical_device_get_format_properties(struct radv_physical_device *pdev, Vk tiled |= VK_FORMAT_FEATURE_2_HOST_IMAGE_TRANSFER_BIT_EXT; } + if (pdev->video_encode_enabled && format == VK_FORMAT_R32_SINT) { + linear |= VK_FORMAT_FEATURE_2_VIDEO_ENCODE_QUANTIZATION_DELTA_MAP_BIT_KHR; + } + out_properties->linearTilingFeatures = linear; out_properties->optimalTilingFeatures = tiled; out_properties->bufferFeatures = buffer; diff --git a/src/amd/vulkan/radv_physical_device.c b/src/amd/vulkan/radv_physical_device.c index 1deb21d0fc9..300ee571ab2 100644 --- a/src/amd/vulkan/radv_physical_device.c +++ b/src/amd/vulkan/radv_physical_device.c @@ -649,6 +649,7 @@ radv_physical_device_get_supported_extensions(const struct radv_physical_device .KHR_video_encode_av1 = (radv_video_encode_av1_supported(pdev) && VIDEO_CODEC_AV1ENC && pdev->video_encode_enabled), .KHR_video_encode_intra_refresh = pdev->video_encode_enabled, + .KHR_video_encode_quantization_map = pdev->video_encode_enabled && pdev->info.vcn_ip_version < VCN_5_0_0, .KHR_video_encode_queue = pdev->video_encode_enabled, .KHR_vulkan_memory_model = true, .KHR_workgroup_memory_explicit_layout = true, @@ -1384,6 +1385,9 @@ radv_physical_device_get_features(const struct radv_physical_device *pdev, struc /* VK_KHR_video_encode_intra_refresh */ .videoEncodeIntraRefresh = true, + + /* VK_KHR_video_encode_quantization_map */ + .videoEncodeQuantizationMap = true, }; } @@ -2072,6 +2076,7 @@ radv_get_physical_device_properties(struct radv_physical_device *pdev) VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR, VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT, VK_IMAGE_LAYOUT_ZERO_INITIALIZED_EXT, + VK_IMAGE_LAYOUT_VIDEO_ENCODE_QUANTIZATION_MAP_KHR, }; p->copySrcLayoutCount = ARRAY_SIZE(supported_layouts); diff --git a/src/amd/vulkan/radv_video.c b/src/amd/vulkan/radv_video.c index a021f6c5049..42961df1ac3 100644 --- a/src/amd/vulkan/radv_video.c +++ b/src/amd/vulkan/radv_video.c @@ -804,6 +804,20 @@ radv_video_is_profile_supported(struct radv_physical_device *pdev, const VkVideo return VK_SUCCESS; } +static uint32_t +radv_video_get_qp_map_texel_size(VkVideoCodecOperationFlagBitsKHR codec) +{ + switch (codec) { + case VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR: + return 16; + case VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR: + case VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR: + return 64; + default: + UNREACHABLE("Unsupported video codec operation"); + } +} + VKAPI_ATTR VkResult VKAPI_CALL radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, const VkVideoProfileInfoKHR *pVideoProfile, VkVideoCapabilitiesKHR *pCapabilities) @@ -873,6 +887,8 @@ radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, cons (struct VkVideoEncodeCapabilitiesKHR *)vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_CAPABILITIES_KHR); struct VkVideoEncodeIntraRefreshCapabilitiesKHR *intra_refresh_caps = vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_INTRA_REFRESH_CAPABILITIES_KHR); + struct VkVideoEncodeQuantizationMapCapabilitiesKHR *qp_map_caps = + vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_QUANTIZATION_MAP_CAPABILITIES_KHR); if (enc_caps) { enc_caps->flags = 0; @@ -885,6 +901,9 @@ radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, cons enc_caps->encodeInputPictureGranularity = pCapabilities->pictureAccessGranularity; enc_caps->supportedEncodeFeedbackFlags = VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BUFFER_OFFSET_BIT_KHR | VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BYTES_WRITTEN_BIT_KHR; + + if (pdev->info.vcn_ip_version < VCN_5_0_0) + enc_caps->flags |= VK_VIDEO_ENCODE_CAPABILITY_QUANTIZATION_DELTA_MAP_BIT_KHR; } if (intra_refresh_caps) { intra_refresh_caps->intraRefreshModes = VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_BASED_BIT_KHR | @@ -895,6 +914,11 @@ radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, cons intra_refresh_caps->partitionIndependentIntraRefreshRegions = true; intra_refresh_caps->nonRectangularIntraRefreshRegions = false; } + if (qp_map_caps) { + const uint32_t qp_map_texel_size = radv_video_get_qp_map_texel_size(pVideoProfile->videoCodecOperation); + qp_map_caps->maxQuantizationMapExtent.width = pCapabilities->maxCodedExtent.width / qp_map_texel_size; + qp_map_caps->maxQuantizationMapExtent.height = pCapabilities->maxCodedExtent.height / qp_map_texel_size; + } pCapabilities->minBitstreamBufferOffsetAlignment = 256; pCapabilities->minBitstreamBufferSizeAlignment = 8; if (pdev->info.vcn_ip_version >= VCN_5_0_0) @@ -972,6 +996,8 @@ radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, cons case VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR: { struct VkVideoEncodeH264CapabilitiesKHR *ext = vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_H264_CAPABILITIES_KHR); + struct VkVideoEncodeH264QuantizationMapCapabilitiesKHR *qp_map_caps = + vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_H264_QUANTIZATION_MAP_CAPABILITIES_KHR); ext->flags = VK_VIDEO_ENCODE_H264_CAPABILITY_HRD_COMPLIANCE_BIT_KHR | VK_VIDEO_ENCODE_H264_CAPABILITY_PER_PICTURE_TYPE_MIN_MAX_QP_BIT_KHR | @@ -1005,11 +1031,18 @@ radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, cons MAX2(ext->maxPPictureL0ReferenceCount, ext->maxBPictureL0ReferenceCount + ext->maxL1ReferenceCount); pCapabilities->minCodedExtent.width = pdev->enc_hw_ver >= RADV_VIDEO_ENC_HW_5 ? 96 : 128; pCapabilities->minCodedExtent.height = pdev->enc_hw_ver >= RADV_VIDEO_ENC_HW_5 ? 32 : 128; + + if (qp_map_caps) { + qp_map_caps->minQpDelta = -51; + qp_map_caps->maxQpDelta = 51; + } break; } case VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR: { struct VkVideoEncodeH265CapabilitiesKHR *ext = (struct VkVideoEncodeH265CapabilitiesKHR *)vk_find_struct( pCapabilities->pNext, VIDEO_ENCODE_H265_CAPABILITIES_KHR); + struct VkVideoEncodeH265QuantizationMapCapabilitiesKHR *qp_map_caps = + vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_H265_QUANTIZATION_MAP_CAPABILITIES_KHR); pCapabilities->pictureAccessGranularity.width = VK_VIDEO_H265_CTU_MAX_WIDTH; if (enc_caps) { @@ -1057,11 +1090,18 @@ radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, cons MAX2(ext->maxPPictureL0ReferenceCount, ext->maxBPictureL0ReferenceCount + ext->maxL1ReferenceCount); pCapabilities->minCodedExtent.width = pdev->enc_hw_ver >= RADV_VIDEO_ENC_HW_5 ? 384 : 130; pCapabilities->minCodedExtent.height = 128; + + if (qp_map_caps) { + qp_map_caps->minQpDelta = -51; + qp_map_caps->maxQpDelta = 51; + } break; } case VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR: { struct VkVideoEncodeAV1CapabilitiesKHR *ext = (struct VkVideoEncodeAV1CapabilitiesKHR *)vk_find_struct( pCapabilities->pNext, VIDEO_ENCODE_AV1_CAPABILITIES_KHR); + struct VkVideoEncodeAV1QuantizationMapCapabilitiesKHR *qp_map_caps = + vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_AV1_QUANTIZATION_MAP_CAPABILITIES_KHR); pCapabilities->maxDpbSlots = RADV_VIDEO_AV1_MAX_DPB_SLOTS; pCapabilities->maxActiveReferencePictures = RADV_VIDEO_AV1_MAX_NUM_REF_FRAME; @@ -1120,6 +1160,11 @@ radv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, cons } pCapabilities->minCodedExtent.width = pdev->enc_hw_ver >= RADV_VIDEO_ENC_HW_5 ? 320 : 128; pCapabilities->minCodedExtent.height = 128; + + if (qp_map_caps) { + qp_map_caps->minQIndexDelta = -255; + qp_map_caps->minQIndexDelta = 255; + } break; } default: @@ -1164,10 +1209,18 @@ radv_GetPhysicalDeviceVideoFormatPropertiesKHR(VkPhysicalDevice physicalDevice, VK_FROM_HANDLE(radv_physical_device, pdev, physicalDevice); if ((pVideoFormatInfo->imageUsage & - (VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR | VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR)) && + (VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR | VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR | + VK_IMAGE_USAGE_VIDEO_ENCODE_QUANTIZATION_DELTA_MAP_BIT_KHR)) && !pdev->video_encode_enabled) return VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR; + /* Cannot be a QP map and other video enc/dec usages, as they are totally different formats. */ + if ((pVideoFormatInfo->imageUsage & VK_IMAGE_USAGE_VIDEO_ENCODE_QUANTIZATION_DELTA_MAP_BIT_KHR) && + (pVideoFormatInfo->imageUsage & + (VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR | VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR | + VK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR | VK_IMAGE_USAGE_VIDEO_DECODE_DST_BIT_KHR))) + return VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR; + /* VCN < 5 requires separate allocates for DPB and decode video. */ if (pdev->info.vcn_ip_version < VCN_5_0_0 && (pVideoFormatInfo->imageUsage & @@ -1176,6 +1229,7 @@ radv_GetPhysicalDeviceVideoFormatPropertiesKHR(VkPhysicalDevice physicalDevice, return VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR; VkFormat format = VK_FORMAT_UNDEFINED; + uint32_t qp_map_texel_size = 0; const struct VkVideoProfileListInfoKHR *prof_list = (struct VkVideoProfileListInfoKHR *)vk_find_struct_const(pVideoFormatInfo->pNext, VIDEO_PROFILE_LIST_INFO_KHR); if (prof_list) { @@ -1191,12 +1245,24 @@ radv_GetPhysicalDeviceVideoFormatPropertiesKHR(VkPhysicalDevice physicalDevice, VkFormat profile_format = VK_FORMAT_UNDEFINED; - if (profile->lumaBitDepth == VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR) - profile_format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; - else if (profile->lumaBitDepth == VK_VIDEO_COMPONENT_BIT_DEPTH_10_BIT_KHR) - profile_format = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16; - else if (profile->lumaBitDepth == VK_VIDEO_COMPONENT_BIT_DEPTH_12_BIT_KHR) - profile_format = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16; + if (pVideoFormatInfo->imageUsage & VK_IMAGE_USAGE_VIDEO_ENCODE_QUANTIZATION_DELTA_MAP_BIT_KHR) { + const uint32_t profile_qp_map_texel_size = radv_video_get_qp_map_texel_size(profile->videoCodecOperation); + + /* All profiles must share the same qp_map texel size. */ + if (qp_map_texel_size != 0 && qp_map_texel_size != profile_qp_map_texel_size) + return VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR; + + qp_map_texel_size = profile_qp_map_texel_size; + + profile_format = VK_FORMAT_R32_SINT; + } else { + if (profile->lumaBitDepth == VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR) + profile_format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; + else if (profile->lumaBitDepth == VK_VIDEO_COMPONENT_BIT_DEPTH_10_BIT_KHR) + profile_format = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16; + else if (profile->lumaBitDepth == VK_VIDEO_COMPONENT_BIT_DEPTH_12_BIT_KHR) + profile_format = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16; + } /* All profiles must share the same format. */ if (format != VK_FORMAT_UNDEFINED && format != profile_format) @@ -1205,12 +1271,17 @@ radv_GetPhysicalDeviceVideoFormatPropertiesKHR(VkPhysicalDevice physicalDevice, format = profile_format; } } else { + /* On AMD, we need a codec specified for qp map as the extents differ. */ + if (pVideoFormatInfo->imageUsage & VK_IMAGE_USAGE_VIDEO_ENCODE_QUANTIZATION_DELTA_MAP_BIT_KHR) + return VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR; + format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; } if (format == VK_FORMAT_UNDEFINED) return VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR; + const bool qp_map = pVideoFormatInfo->imageUsage & VK_IMAGE_USAGE_VIDEO_ENCODE_QUANTIZATION_DELTA_MAP_BIT_KHR; const bool dpb = pVideoFormatInfo->imageUsage & (VK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR | VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR); const bool src_dst = pVideoFormatInfo->imageUsage & @@ -1218,13 +1289,17 @@ radv_GetPhysicalDeviceVideoFormatPropertiesKHR(VkPhysicalDevice physicalDevice, VkImageTiling tiling[3]; uint32_t num_tiling = 0; - tiling[num_tiling++] = VK_IMAGE_TILING_OPTIMAL; - - if (src_dst && !dpb) + if (qp_map) { tiling[num_tiling++] = VK_IMAGE_TILING_LINEAR; + } else { + tiling[num_tiling++] = VK_IMAGE_TILING_OPTIMAL; - if (src_dst && pdev->info.gfx_level >= GFX9) - tiling[num_tiling++] = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + if (src_dst && !dpb) + tiling[num_tiling++] = VK_IMAGE_TILING_LINEAR; + + if (src_dst && pdev->info.gfx_level >= GFX9) + tiling[num_tiling++] = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + } VK_OUTARRAY_MAKE_TYPED(VkVideoFormatPropertiesKHR, out, pVideoFormatProperties, pVideoFormatPropertyCount); @@ -1242,6 +1317,22 @@ radv_GetPhysicalDeviceVideoFormatPropertiesKHR(VkPhysicalDevice physicalDevice, p->imageType = VK_IMAGE_TYPE_2D; p->imageTiling = tiling[i]; p->imageUsageFlags = pVideoFormatInfo->imageUsage; + + if (qp_map) { + struct VkVideoFormatQuantizationMapPropertiesKHR *qp_map_props = + vk_find_struct(p->pNext, VIDEO_FORMAT_QUANTIZATION_MAP_PROPERTIES_KHR); + struct VkVideoFormatH265QuantizationMapPropertiesKHR *qp_map_h265_props = + vk_find_struct(p->pNext, VIDEO_FORMAT_H265_QUANTIZATION_MAP_PROPERTIES_KHR); + struct VkVideoFormatAV1QuantizationMapPropertiesKHR *qp_map_av1_props = + vk_find_struct(p->pNext, VIDEO_FORMAT_AV1_QUANTIZATION_MAP_PROPERTIES_KHR); + + if (qp_map_props) + qp_map_props->quantizationMapTexelSize = (VkExtent2D){qp_map_texel_size, qp_map_texel_size}; + if (qp_map_h265_props) + qp_map_h265_props->compatibleCtbSizes = VK_VIDEO_ENCODE_H265_CTB_SIZE_64_BIT_KHR; + if (qp_map_av1_props) + qp_map_av1_props->compatibleSuperblockSizes = VK_VIDEO_ENCODE_AV1_SUPERBLOCK_SIZE_64_BIT_KHR; + } } } diff --git a/src/amd/vulkan/radv_video_enc.c b/src/amd/vulkan/radv_video_enc.c index af5ada0d86a..10b75dba467 100644 --- a/src/amd/vulkan/radv_video_enc.c +++ b/src/amd/vulkan/radv_video_enc.c @@ -1707,6 +1707,40 @@ radv_enc_intra_refresh(struct radv_cmd_buffer *cmd_buffer, const VkVideoEncodeIn RADEON_ENC_END(); } +static void +radv_enc_qp_map(struct radv_cmd_buffer *cmd_buffer, const struct VkVideoEncodeInfoKHR *enc_info) +{ + struct radv_device *device = radv_cmd_buffer_device(cmd_buffer); + const struct radv_physical_device *pdev = radv_device_physical(device); + const struct VkVideoEncodeQuantizationMapInfoKHR *quantiziation_map_info = + vk_find_struct_const(enc_info->pNext, VIDEO_ENCODE_QUANTIZATION_MAP_INFO_KHR); + const struct radv_image_view *qp_map_view = + quantiziation_map_info ? radv_image_view_from_handle(quantiziation_map_info->quantizationMap) : NULL; + const struct radv_image *qp_map = qp_map_view ? qp_map_view->image : NULL; + + RADEON_ENC_BEGIN(pdev->vcn_enc_cmds.enc_qp_map); + if (enc_info->flags & VK_VIDEO_ENCODE_WITH_QUANTIZATION_DELTA_MAP_BIT_KHR && qp_map) { + /* VCN < 5 uses a 32-bit signed integer for QP maps. */ + assert(qp_map->vk.format == VK_FORMAT_R32_SINT); + + const uint32_t qp_map_type = cmd_buffer->video.vid->enc_rate_control_method == RENCODE_RATE_CONTROL_METHOD_NONE + ? RENCODE_QP_MAP_TYPE_DELTA + : RENCODE_QP_MAP_TYPE_MAP_PA; + radv_cs_add_buffer(device->ws, cmd_buffer->cs->b, qp_map->bindings[0].bo); + const uint64_t va = qp_map->bindings[0].addr; + RADEON_ENC_CS(qp_map_type); + RADEON_ENC_CS(va >> 32); + RADEON_ENC_CS(va & 0xffffffff); + RADEON_ENC_CS(qp_map->planes[0].surface.u.gfx9.surf_pitch); + } else { + RADEON_ENC_CS(RENCODE_QP_MAP_TYPE_NONE); + RADEON_ENC_CS(0); + RADEON_ENC_CS(0); + RADEON_ENC_CS(0); + } + RADEON_ENC_END(); +} + static void radv_enc_rc_per_pic(struct radv_cmd_buffer *cmd_buffer, const VkVideoEncodeInfoKHR *enc_info, rvcn_enc_rate_ctl_per_picture_t *per_pic) @@ -2776,6 +2810,8 @@ radv_vcn_encode_video(struct radv_cmd_buffer *cmd_buffer, const VkVideoEncodeInf } // intra_refresh radv_enc_intra_refresh(cmd_buffer, enc_info); + // qp map + radv_enc_qp_map(cmd_buffer, enc_info); // v2 input format if (pdev->enc_hw_ver >= RADV_VIDEO_ENC_HW_2) { radv_enc_input_format(cmd_buffer);