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

+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;
}