From 0716678e70b9e0b8ea9b23f05790fe4e98b023c7 Mon Sep 17 00:00:00 2001 From: elwrcl Date: Fri, 21 Aug 2026 07:35:13 +0300 Subject: [PATCH] hasvk: Add VK_KHR_video_encode_intra_refresh for gen7. Each macroblock's intra/inter decision is already made on its own when the PAK objects are emitted, so refreshing a region is a matter of coding the macroblocks it covers the way an I slice codes them. Regions divide the picture evenly along rows, columns or raster order, which leaves the block, block-row and block-column modes supported and gives empty regions for free when the cycle is longer than the split axis. Two things needed care. PAK keeps the I slice numbering for MbType5Bits inside a P slice, where H.264 itself offsets intra mb_types by 5; handing it the offset value hangs the MFC. And under constrained intra prediction a neighbour only counts as available if it is in the refresh region too, because prediction may not cross into an inter macroblock. Nothing constrains motion away from not-yet-refreshed regions, which is sound only because there is no motion estimation: every vector is zero, so a macroblock reads its own co-located block and never leaves its region. Motion estimation will have to honour VkVideoReferenceIntraRefreshInfoKHR's dirty region count. Per-picture-partition mode is not offered. It wants the refreshed slice coded as an I slice, header included, rather than as intra macroblocks inside a P slice. dEQP-VK.video.encode.h264.intra_refresh_* goes from 44 NotSupported to 12 Pass; all three modes pass in the variants that can run. The other 32 stop at a CTS precondition, maxDpbSlots >= GOP frame count, which the driver's two slots do not meet. Raising that needs real DPB slot tracking and is what gates B frames and ffmpeg's encoder too. Co-Authored-By: Claude Opus 5 --- src/intel/vulkan_hasvk/anv_device.c | 4 +++ src/intel/vulkan_hasvk/anv_genX.h | 3 +- src/intel/vulkan_hasvk/anv_private.h | 44 +++++++++++++++++++++++++ src/intel/vulkan_hasvk/anv_video.c | 32 +++++++++++++++--- src/intel/vulkan_hasvk/anv_video_mfc.c | 34 +++++++++++++++---- src/intel/vulkan_hasvk/genX_cmd_video.c | 21 +++++++++++- 6 files changed, 126 insertions(+), 12 deletions(-) diff --git a/src/intel/vulkan_hasvk/anv_device.c b/src/intel/vulkan_hasvk/anv_device.c index bb13b60..599ca7d 100644 --- a/src/intel/vulkan_hasvk/anv_device.c +++ b/src/intel/vulkan_hasvk/anv_device.c @@ -289,6 +289,7 @@ get_device_extensions(const struct anv_physical_device *device, .KHR_video_decode_h264 = video_decode, .KHR_video_encode_queue = video_encode, .KHR_video_encode_h264 = video_encode, + .KHR_video_encode_intra_refresh = video_encode, .KHR_video_maintenance1 = video_decode || video_encode, .KHR_video_maintenance2 = video_decode || video_encode, .KHR_unified_image_layouts = true, @@ -735,6 +736,9 @@ get_features(const struct anv_physical_device *pdevice, /* VK_KHR_video_maintenance2 */ .videoMaintenance2 = true, + /* VK_KHR_video_encode_intra_refresh */ + .videoEncodeIntraRefresh = true, + /* VK_KHR_unified_image_layouts * * Aux usage is derived from the layout's usage flags, and GENERAL simply diff --git a/src/intel/vulkan_hasvk/anv_genX.h b/src/intel/vulkan_hasvk/anv_genX.h index 03db00c..174b1d9 100644 --- a/src/intel/vulkan_hasvk/anv_genX.h +++ b/src/intel/vulkan_hasvk/anv_genX.h @@ -101,7 +101,8 @@ void genX(hasvk_mfc_emit_slice)(struct anv_cmd_buffer *cmd_buffer, uint32_t slice_start_mb, uint32_t slice_end_mb, uint32_t width_in_mbs, uint32_t height_in_mbs, uint32_t qp, bool is_last_slice, - uint32_t dst_page_offset); + uint32_t dst_page_offset, + const struct anv_video_intra_refresh *intra_refresh); enum anv_pipe_bits genX(emit_apply_pipe_flushes)(struct anv_batch *batch, diff --git a/src/intel/vulkan_hasvk/anv_private.h b/src/intel/vulkan_hasvk/anv_private.h index 9c7a1c2..6efb9b0 100644 --- a/src/intel/vulkan_hasvk/anv_private.h +++ b/src/intel/vulkan_hasvk/anv_private.h @@ -3852,6 +3852,50 @@ struct anv_video_session_params { struct vk_video_session_parameters vk; }; +/* One picture's slice of an intra refresh cycle: the macroblocks of region + * `index` out of `cycle_duration` are coded intra, the rest inter. + */ +struct anv_video_intra_refresh { + VkVideoEncodeIntraRefreshModeFlagBitsKHR mode; + uint32_t cycle_duration; + uint32_t index; +}; + +/* Whether the macroblock at (x, y) falls in the region being refreshed. The + * regions divide the picture evenly, so a cycle longer than the number of + * blocks along the split axis simply leaves some of them empty. + */ +static inline bool +anv_video_mb_is_intra_refreshed(const struct anv_video_intra_refresh *ir, + uint32_t x, uint32_t y, + uint32_t width_in_mbs, uint32_t height_in_mbs) +{ + if (ir == NULL || ir->cycle_duration == 0) + return false; + + uint32_t pos, total; + switch (ir->mode) { + case VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_ROW_BASED_BIT_KHR: + pos = y; + total = height_in_mbs; + break; + case VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_COLUMN_BASED_BIT_KHR: + pos = x; + total = width_in_mbs; + break; + case VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_BASED_BIT_KHR: + pos = y * width_in_mbs + x; + total = width_in_mbs * height_in_mbs; + break; + default: + return false; + } + + uint32_t first = (uint64_t)ir->index * total / ir->cycle_duration; + uint32_t last = (uint64_t)(ir->index + 1) * total / ir->cycle_duration; + return pos >= first && pos < last; +} + static inline uint32_t khr_perf_query_preamble_offset(const struct anv_query_pool *pool, uint32_t pass) { diff --git a/src/intel/vulkan_hasvk/anv_video.c b/src/intel/vulkan_hasvk/anv_video.c index 0607333..04eb73b 100644 --- a/src/intel/vulkan_hasvk/anv_video.c +++ b/src/intel/vulkan_hasvk/anv_video.c @@ -222,15 +222,39 @@ anv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, enc_caps->maxQualityLevels = 1; enc_caps->encodeInputPictureGranularity.width = ANV_MB_WIDTH; enc_caps->encodeInputPictureGranularity.height = ANV_MB_HEIGHT; - /* Both flags are required by the ffmpeg h264_vulkan encoder. Note - * that bytes-written currently reports the full dstBufferRange rather - * than the real encoded size; reading back the MFC bit counter is not - * yet implemented. */ + /* Bytes written comes from the MFC bitstream byte counter; see the + * feedback query write in anv_h264_encode_video(). */ enc_caps->supportedEncodeFeedbackFlags = VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BUFFER_OFFSET_BIT_KHR | VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BYTES_WRITTEN_BIT_KHR; } + struct VkVideoEncodeIntraRefreshCapabilitiesKHR *ir_caps = + (struct VkVideoEncodeIntraRefreshCapabilitiesKHR *) + vk_find_struct(pCapabilities->pNext, + VIDEO_ENCODE_INTRA_REFRESH_CAPABILITIES_KHR); + if (ir_caps) { + /* Each macroblock's intra/inter decision is made on its own when the + * PAK objects are emitted, so the picture can be split along any of + * these axes. Per-picture-partition mode is not offered: it wants the + * refreshed slice coded as an I slice, header included, rather than as + * intra macroblocks inside a P slice. + */ + ir_caps->intraRefreshModes = + VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_BASED_BIT_KHR | + VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_ROW_BASED_BIT_KHR | + VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_BLOCK_COLUMN_BASED_BIT_KHR; + /* Down to one region per macroblock at the largest coded extent. */ + ir_caps->maxIntraRefreshCycleDuration = + (4096 / ANV_MB_WIDTH) * (4096 / ANV_MB_HEIGHT); + ir_caps->maxIntraRefreshActiveReferencePictures = 1; + /* Regions are macroblock ranges that owe nothing to slice boundaries, + * and a block-based one is not rectangular. + */ + ir_caps->partitionIndependentIntraRefreshRegions = VK_TRUE; + ir_caps->nonRectangularIntraRefreshRegions = VK_TRUE; + } + struct VkVideoEncodeH264CapabilitiesKHR *h264_enc_ext = (struct VkVideoEncodeH264CapabilitiesKHR *) vk_find_struct(pCapabilities->pNext, VIDEO_ENCODE_H264_CAPABILITIES_KHR); diff --git a/src/intel/vulkan_hasvk/anv_video_mfc.c b/src/intel/vulkan_hasvk/anv_video_mfc.c index 60b1632..fcfb897 100644 --- a/src/intel/vulkan_hasvk/anv_video_mfc.c +++ b/src/intel/vulkan_hasvk/anv_video_mfc.c @@ -316,7 +316,8 @@ genX(hasvk_mfc_emit_slice)(struct anv_cmd_buffer *cmd_buffer, uint32_t slice_start_mb, uint32_t slice_end_mb, uint32_t width_in_mbs, uint32_t height_in_mbs, uint32_t qp, bool is_last_slice, - uint32_t dst_page_offset) + uint32_t dst_page_offset, + const struct anv_video_intra_refresh *intra_refresh) { /* A P slice references the previous reconstructed frame; an I slice does * not. B slices are out of scope. */ @@ -426,7 +427,13 @@ genX(hasvk_mfc_emit_slice)(struct anv_cmd_buffer *cmd_buffer, pak.InlineData[1] = (y << 8) | x; /* Cbp4x4Y=0, MbY/XCnt */ pak.InlineData[3] = ((uint32_t)last_mb << 26) | qp; - if (is_p) { + /* A macroblock in the refresh region is coded exactly like an I slice + * one; the rest of the picture keeps the inter path. */ + const bool intra_mb = !is_p || + anv_video_mb_is_intra_refreshed(intra_refresh, x, y, + width_in_mbs, height_in_mbs); + + if (!intra_mb) { /* HASVK_ENC_SKIP_ONLY falls back to the older all-skip path, which * carries no residual at all: useful for isolating whether a P * frame problem is in the prediction or in the residual. */ @@ -449,6 +456,9 @@ genX(hasvk_mfc_emit_slice)(struct anv_cmd_buffer *cmd_buffer, pak.InlineData[6] = 0; /* ref index L1 = 0 */ pak.InlineData[7] = 0; } else { + /* MbType5Bits stays the I slice numbering even inside a P slice, + * where H.264 itself offsets intra mb_types by 5: PAK does that + * remapping, and feeding it the offset value hangs the MFC. */ pak.InlineData[0] = 0x000e0000 /* CbpDcY/U/V = 1 */ | (1u << 13) /* IntraMbFlag */ | (0x17u << 8); /* MbType5Bits = 23: I_16x16 DC */ @@ -462,15 +472,27 @@ genX(hasvk_mfc_emit_slice)(struct anv_cmd_buffer *cmd_buffer, * >= slice_start_mb as well as inside the picture. * bit7 F, bit6 A, bit5 E: left | bit4 B: top | bit3 C: top-right | * bit2 D: top-left. Bits [1:0] hold ChromaIntraPredMode (DC = 0). */ + /* Under constrained intra prediction a neighbour in a P slice also + * has to be intra itself, which here means inside the intra band. + * Without the flag the decoder does treat inter neighbours as + * available, so masking them off would desync the two. */ + const bool cip = pps->flags.constrained_intra_pred_flag; +#define NEIGHBOUR_OK(nb_mb, nb_x, nb_y) \ + ((nb_mb) >= slice_start_mb && \ + (!is_p || !cip || \ + anv_video_mb_is_intra_refreshed(intra_refresh, nb_x, nb_y, \ + width_in_mbs, height_in_mbs))) uint32_t intra_avail = 0; - if (x > 0 && mb - 1 >= slice_start_mb) + if (x > 0 && NEIGHBOUR_OK(mb - 1, x - 1, y)) intra_avail |= (1u << 7) | (1u << 6) | (1u << 5); - if (y > 0 && mb - width_in_mbs >= slice_start_mb) + if (y > 0 && NEIGHBOUR_OK(mb - width_in_mbs, x, y - 1)) intra_avail |= (1u << 4); - if (y > 0 && x < width_in_mbs - 1 && mb - width_in_mbs + 1 >= slice_start_mb) + if (y > 0 && x < width_in_mbs - 1 && + NEIGHBOUR_OK(mb - width_in_mbs + 1, x + 1, y - 1)) intra_avail |= (1u << 3); - if (x > 0 && y > 0 && mb - width_in_mbs - 1 >= slice_start_mb) + if (x > 0 && y > 0 && NEIGHBOUR_OK(mb - width_in_mbs - 1, x - 1, y - 1)) intra_avail |= (1u << 2); +#undef NEIGHBOUR_OK pak.InlineData[6] = intra_avail; pak.InlineData[7] = 0; } diff --git a/src/intel/vulkan_hasvk/genX_cmd_video.c b/src/intel/vulkan_hasvk/genX_cmd_video.c index 6bcdceb..0b1c0db 100644 --- a/src/intel/vulkan_hasvk/genX_cmd_video.c +++ b/src/intel/vulkan_hasvk/genX_cmd_video.c @@ -1132,6 +1132,24 @@ anv_h264_encode_video(struct anv_cmd_buffer *cmd_buffer, encode_info->dstBufferRange, width_in_mbs, height_in_mbs, is_p, ref_address); + /* Intra refresh, if the session was created with a mode and this picture + * carries a cycle position. An I frame refreshes everything by itself, so + * only P frames need a region. */ + struct anv_video_intra_refresh intra_refresh; + const struct anv_video_intra_refresh *intra_refresh_ptr = NULL; + const VkVideoEncodeIntraRefreshInfoKHR *ir_info = + vk_find_struct_const(encode_info->pNext, VIDEO_ENCODE_INTRA_REFRESH_INFO_KHR); + if (is_p && ir_info && ir_info->intraRefreshCycleDuration > 0 && + cmd_buffer->video.vid->vk.intra_refresh_mode != + VK_VIDEO_ENCODE_INTRA_REFRESH_MODE_NONE_KHR) { + intra_refresh = (struct anv_video_intra_refresh) { + .mode = cmd_buffer->video.vid->vk.intra_refresh_mode, + .cycle_duration = ir_info->intraRefreshCycleDuration, + .index = ir_info->intraRefreshIndex, + }; + intra_refresh_ptr = &intra_refresh; + } + /* Emit each slice. Slice s covers the macroblock range * [first_mb_in_slice[s], first_mb_in_slice[s+1]); the last slice runs to the * end of the frame. Each NALU slice entry carries its own QP and slice @@ -1186,7 +1204,8 @@ anv_h264_encode_video(struct anv_cmd_buffer *cmd_buffer, slice_start_mb, slice_end_mb, width_in_mbs, height_in_mbs, (uint32_t)entry->constantQp, - s + 1 == slice_count, dst_page_off); + s + 1 == slice_count, dst_page_off, + intra_refresh_ptr); } /* Write the VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR result here, where the