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 <[email protected]>
This commit is contained in:
elwrclandClaude Opus 5 committed 2026-08-21 07:35:13 +03:00
1 parent 113661753e
commit 0716678e70
6 files changed
+126 -12

No files matched your search

+4
View File
@@ -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
+2 -1
View File
@@ -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,
+44
View File
@@ -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)
{
+28 -4
View File
@@ -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);
+28 -6
View File
@@ -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;
}
+20 -1
View File
@@ -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