hasvk: Track DPB slots instead of assuming a single reference.

The encoder ignored VkVideoReferenceSlotInfoKHR entirely: it took
pReferenceSlots[0] as the one reference, wrote its address into frame store 0
and hardcoded the L0 list to that store.  Two DPB slots were all it could
honestly advertise, which is fewer than a GOP of any length needs.

Record each slot the operation names under its own index, since a slot maps
onto the frame store of the same index and MFX_PIPE_BUF_ADDR_STATE holds 16
of those, and build the L0 entries from StdVideoEncodeH264ReferenceListsInfo,
whose RefPicList0 already carries slot indices.  Clients that pass reference
slots without a list keep the old behaviour.

maxDpbSlots therefore goes to 16.  maxActiveReferencePictures stays at 1:
every macroblock is still coded against reference index 0, so a picture may
be kept in the DPB but only the one it names first is predicted from.

dEQP-VK.video.encode.h264.intra_refresh_* goes from 12 Pass and 32 stopped at
the maxDpbSlots >= GOP frame count precondition to 36 Pass, the remaining 8
being per-picture-partition mode, which is not offered.  The family as a whole
is 76 of 120 with no failures.  ffmpeg's h264_vulkan encoder no longer trips
its slot_index assertion and produces a full GOP.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
elwrclandClaude Opus 5 committed 2026-08-21 07:55:40 +03:00
1 parent 0716678e70
commit 9cb5c4385b
5 files changed
+84 -29

No files matched your search

+2 -1
View File
@@ -86,7 +86,8 @@ void genX(hasvk_mfc_emit_static_state)(struct anv_cmd_buffer *cmd_buffer,
uint32_t dst_page_offset,
uint64_t dst_buffer_range,
uint32_t width_in_mbs, uint32_t height_in_mbs,
bool is_p, struct anv_address ref_address);
bool is_p,
const struct anv_video_reference_state *refs);
/* Emits one slice: MFX_AVC_SLICE_STATE, the slice-header insert, the per-MB
* PAK objects for the macroblock range [slice_start_mb, slice_end_mb), and the
+16
View File
@@ -3852,6 +3852,22 @@ struct anv_video_session_params {
struct vk_video_session_parameters vk;
};
/* MFX_PIPE_BUF_ADDR_STATE has 16 reference picture addresses, so the hardware
* can hold 16 frame stores. A DPB slot maps straight onto a frame store of
* the same index, which keeps the reference lists trivial to build.
*/
#define ANV_VIDEO_MAX_DPB_SLOTS 16
/* The reference pictures one encode operation can reach: where each DPB slot
* lives, and which slots its L0 list names, in order.
*/
struct anv_video_reference_state {
struct anv_address slot_address[ANV_VIDEO_MAX_DPB_SLOTS];
bool slot_valid[ANV_VIDEO_MAX_DPB_SLOTS];
uint8_t l0_slot[ANV_VIDEO_MAX_DPB_SLOTS];
uint8_t l0_count;
};
/* One picture's slice of an intra refresh cycle: the macroblocks of region
* `index` out of `cycle_duration` are coded intra, the rest inter.
*/
+6 -5
View File
@@ -202,11 +202,12 @@ anv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice,
pCapabilities->stdHeaderVersion.specVersion =
VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_SPEC_VERSION;
} else {
/* Limits reflecting what the encoder emits: I and P frames, CQP, single
* reference. P frames use one reference (the previous reconstructed
* frame) plus the current setup slot, so two DPB slots and one active
* reference. B frames and multi-reference are not implemented. */
pCapabilities->maxDpbSlots = 2;
/* Limits reflecting what the encoder emits: I and P frames, CQP. A DPB
* slot maps onto a frame store of the same index and there are 16 of
* those, so a long GOP can keep every picture it likes around. Only one
* of them can be predicted from, though: each macroblock is coded against
* reference index 0, so multi-reference and B frames are still out. */
pCapabilities->maxDpbSlots = ANV_VIDEO_MAX_DPB_SLOTS;
pCapabilities->maxActiveReferencePictures = 1;
struct VkVideoEncodeCapabilitiesKHR *enc_caps =
+21 -12
View File
@@ -49,7 +49,8 @@ genX(hasvk_mfc_emit_static_state)(struct anv_cmd_buffer *cmd_buffer,
uint32_t dst_page_offset,
uint64_t dst_buffer_range,
uint32_t width_in_mbs, uint32_t height_in_mbs,
bool is_p, struct anv_address ref_address)
bool is_p,
const struct anv_video_reference_state *refs)
{
struct anv_video_session *vid = cmd_buffer->video.vid;
@@ -149,10 +150,13 @@ genX(hasvk_mfc_emit_static_state)(struct anv_cmd_buffer *cmd_buffer,
* them with the valid, mapped recon address. */
for (unsigned i = 0; i < ARRAY_SIZE(buf.ReferencePictureAddress); i++)
buf.ReferencePictureAddress[i] = recon_address;
/* Frame store 0 is the P-frame's reference (previous reconstructed
* frame); REF_IDX_STATE L0[0] maps ref index 0 to this frame store. */
if (is_p)
buf.ReferencePictureAddress[0] = ref_address;
/* Frame store i holds DPB slot i, so a slot the picture can reference
* goes straight into its own entry; the rest keep the recon address the
* PRM asks for. */
for (unsigned i = 0; i < ANV_VIDEO_MAX_DPB_SLOTS; i++) {
if (refs->slot_valid[i])
buf.ReferencePictureAddress[i] = refs->slot_address[i];
}
}
anv_batch_emit(&cmd_buffer->batch, GENX(MFX_BSP_BUF_BASE_ADDR_STATE), bsp) {
@@ -290,17 +294,22 @@ genX(hasvk_mfc_emit_static_state)(struct anv_cmd_buffer *cmd_buffer,
dmv.POCList[i] = i / 2;
}
/* Reference index lists. Entries are set to the "non-existing" marker 0x80
* (intel_mfc_avc_ref_idx_state). For a P frame, L0[0] points at frame store
* 0: entry = (is_long_term<<6)|((top^bottom^1)<<5)|(frame_store_id<<1)|... ;
* for a short-term progressive frame at store 0 this is 0x20
* (intel_get_ref_idx_state_1). L1 is unused (P has no backward references). */
/* Reference index lists. Unused entries carry the "non-existing" marker
* 0x80 (intel_mfc_avc_ref_idx_state). A used one is
* (is_long_term<<6)|((top^bottom^1)<<5)|(frame_store_id<<1), which for a
* short-term progressive frame comes out as 0x20|(frame_store_id<<1)
* (intel_get_ref_idx_state_1). L1 stays empty: B frames are out of scope,
* so there are no backward references. */
for (unsigned list = 0; list < 2; list++) {
anv_batch_emit(&cmd_buffer->batch, GENX(MFX_AVC_REF_IDX_STATE), ref_idx) {
ref_idx.ReferencePictureListSelect = list;
memset(ref_idx.ReferenceListEntry, 0x80, sizeof(ref_idx.ReferenceListEntry));
if (is_p && list == 0)
ref_idx.ReferenceListEntry[0] = 0x20;
if (is_p && list == 0) {
unsigned n = MIN2(refs->l0_count,
ARRAY_SIZE(ref_idx.ReferenceListEntry));
for (unsigned i = 0; i < n; i++)
ref_idx.ReferenceListEntry[i] = 0x20 | (refs->l0_slot[i] << 1);
}
}
}
#endif /* GFX_VERx10 == 70 */
+39 -11
View File
@@ -1110,27 +1110,55 @@ anv_h264_encode_video(struct anv_cmd_buffer *cmd_buffer,
anv_image_dpb_address(recon_iv,
encode_info->pSetupReferenceSlot->pPictureResource->baseArrayLayer);
/* P frame: the reconstructed picture of the previous frame, supplied as
* pReferenceSlots[0], is the single L0 reference. Absence of a reference
* slot means this is an I frame. */
/* Reference pictures. Every slot this operation names gets its address
* recorded under its own index, and the L0 list is taken from the picture's
* reference lists, whose entries are DPB slot indices. No reference slot at
* all means this is an I frame. */
bool is_p = encode_info->referenceSlotCount > 0 &&
encode_info->pReferenceSlots &&
encode_info->pReferenceSlots[0].pPictureResource;
struct anv_address ref_address = ANV_NULL_ADDRESS;
if (is_p) {
struct anv_video_reference_state refs;
memset(&refs, 0, sizeof(refs));
for (uint32_t i = 0; i < encode_info->referenceSlotCount; i++) {
const VkVideoReferenceSlotInfoKHR *slot = &encode_info->pReferenceSlots[i];
if (!slot->pPictureResource || slot->slotIndex < 0 ||
slot->slotIndex >= ANV_VIDEO_MAX_DPB_SLOTS)
continue;
const struct anv_image_view *ref_iv =
anv_image_view_from_handle(
encode_info->pReferenceSlots[0].pPictureResource->imageViewBinding);
ref_address =
anv_image_dpb_address(ref_iv,
encode_info->pReferenceSlots[0].pPictureResource->baseArrayLayer);
anv_image_view_from_handle(slot->pPictureResource->imageViewBinding);
refs.slot_address[slot->slotIndex] =
anv_image_dpb_address(ref_iv, slot->pPictureResource->baseArrayLayer);
refs.slot_valid[slot->slotIndex] = true;
}
if (is_p) {
const StdVideoEncodeH264ReferenceListsInfo *ref_lists =
h264_pic_info->pStdPictureInfo->pRefLists;
if (ref_lists) {
unsigned n = MIN2(ref_lists->num_ref_idx_l0_active_minus1 + 1,
ANV_VIDEO_MAX_DPB_SLOTS);
for (unsigned i = 0; i < n; i++) {
uint8_t s = ref_lists->RefPicList0[i];
if (s >= ANV_VIDEO_MAX_DPB_SLOTS || !refs.slot_valid[s])
break;
refs.l0_slot[refs.l0_count++] = s;
}
}
/* A client that supplies reference slots but no list still means the
* first one, which is what the encoder used to assume unconditionally. */
if (refs.l0_count == 0 &&
encode_info->pReferenceSlots[0].slotIndex >= 0 &&
encode_info->pReferenceSlots[0].slotIndex < ANV_VIDEO_MAX_DPB_SLOTS) {
refs.l0_slot[refs.l0_count++] =
encode_info->pReferenceSlots[0].slotIndex;
}
}
/* Frame-level static state, emitted once before any slice. */
genX(hasvk_mfc_emit_static_state)(cmd_buffer, img, recon_address, sps, pps,
dst_address, dst_page_off,
encode_info->dstBufferRange,
width_in_mbs, height_in_mbs, is_p, ref_address);
width_in_mbs, height_in_mbs, is_p, &refs);
/* 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