From 7acab34fe800cb66a8331701792af574730c3378 Mon Sep 17 00:00:00 2001 From: elwrcl Date: Sun, 23 Aug 2026 03:06:02 +0300 Subject: [PATCH] Reapply "hasvk: Encode B pictures on gen7." This reverts commit 26405fe38c2c9c9d079f87e2d564f77fe39135fa. --- src/intel/vulkan_hasvk/anv_private.h | 13 ++++++++ src/intel/vulkan_hasvk/anv_video.c | 30 ++++++++++++++----- .../vulkan_hasvk/anv_video_h264_bitstream.c | 23 ++++++++++---- src/intel/vulkan_hasvk/anv_video_mfc.c | 26 +++++++++------- src/intel/vulkan_hasvk/genX_cmd_video.c | 8 +++++ 5 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/intel/vulkan_hasvk/anv_private.h b/src/intel/vulkan_hasvk/anv_private.h index a61146f..a01817a 100644 --- a/src/intel/vulkan_hasvk/anv_private.h +++ b/src/intel/vulkan_hasvk/anv_private.h @@ -3663,6 +3663,17 @@ anv_image_dpb_address(const struct anv_image_view *iv, &x_offset_sa, &y_offset_sa); + /* isl hands back a tile-aligned byte offset plus whatever is left over + * inside that tile. MFX takes a plain address for the picture, so the + * remainder has to be folded in: dropping it puts every layer whose start + * is not tile-aligned at the wrong place -- for a 176x144 NV12 array that + * is 16 rows off on every odd layer, which corrupts the reference each + * picture builds on. */ + const struct isl_surf *_surf = &iv->image->planes[0].primary_surface.isl; + offset_B += (uint64_t)y_offset_sa * _surf->row_pitch_B; + offset_B += (uint64_t)x_offset_sa * + (isl_format_get_layout(_surf->format)->bpb / 8); + return anv_address_add(addr, offset_B); } @@ -3876,6 +3887,8 @@ struct anv_video_reference_state { bool slot_valid[ANV_VIDEO_MAX_DPB_SLOTS]; uint8_t l0_slot[ANV_VIDEO_MAX_DPB_SLOTS]; uint8_t l0_count; + uint8_t l1_slot[ANV_VIDEO_MAX_DPB_SLOTS]; + uint8_t l1_count; }; /* One picture's slice of an intra refresh cycle: the macroblocks of region diff --git a/src/intel/vulkan_hasvk/anv_video.c b/src/intel/vulkan_hasvk/anv_video.c index 2cf5a31..6f87429 100644 --- a/src/intel/vulkan_hasvk/anv_video.c +++ b/src/intel/vulkan_hasvk/anv_video.c @@ -93,6 +93,16 @@ anv_CreateVideoSessionParametersKHR(VkDevice _device, if (!params) return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY); + /* The 8x8 transform is a High profile option this encoder does not + * implement: every macroblock is coded 16x16. Leaving the client's flag + * set would put transform_8x8_mode_flag=1 in the PPS we emit, and a decoder + * would then look for a transform_size_8x8_flag the PAK never writes, + * losing bitstream sync at the first macroblock. */ + if (params->op == VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR) { + for (unsigned i = 0; i < params->h264_enc.h264_pps_count; i++) + params->h264_enc.h264_pps[i].base.flags.transform_8x8_mode_flag = 0; + } + *pVideoSessionParameters = vk_video_session_parameters_to_handle(params); return VK_SUCCESS; } @@ -142,17 +152,18 @@ anv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, } } } else { - /* Baseline and Main are supported. Main is accepted so that CABAC - * (entropy_coding_mode_flag == 1, forbidden in Baseline) can be selected; - * the PAK performs the entropy coding. Main's inter-prediction features - * (B slices) are still not emitted — only I frames are produced. High is - * rejected (8x8 transform / scaling lists not emitted). */ + /* Baseline, Main and High. Main brings CABAC (entropy_coding_mode_flag, + * forbidden in Baseline), which the PAK does itself. High adds the 8x8 + * transform and custom scaling lists, and neither is mandatory: both are + * opted into per PPS, so a High stream that leaves them off is valid and + * is what this encoder produces. */ const VkVideoEncodeH264ProfileInfoKHR *h264_profile = vk_find_struct_const(pVideoProfile->pNext, VIDEO_ENCODE_H264_PROFILE_INFO_KHR); if (h264_profile) { switch (h264_profile->stdProfileIdc) { case STD_VIDEO_H264_PROFILE_IDC_BASELINE: case STD_VIDEO_H264_PROFILE_IDC_MAIN: + case STD_VIDEO_H264_PROFILE_IDC_HIGH: break; default: return VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR; @@ -208,7 +219,10 @@ anv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, * 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; + /* Two, so a B picture can name one reference in each list. Prediction + * still only uses reference index 0 of whichever list a macroblock is + * coded against. */ + pCapabilities->maxActiveReferencePictures = 2; struct VkVideoEncodeCapabilitiesKHR *enc_caps = (struct VkVideoEncodeCapabilitiesKHR *) @@ -274,8 +288,8 @@ anv_GetPhysicalDeviceVideoCapabilitiesKHR(VkPhysicalDevice physicalDevice, * slice splitting). */ h264_enc_ext->maxSliceCount = 68; /* 1088/16, covers up to 1080p */ h264_enc_ext->maxPPictureL0ReferenceCount = 1; - h264_enc_ext->maxBPictureL0ReferenceCount = 0; - h264_enc_ext->maxL1ReferenceCount = 0; + h264_enc_ext->maxBPictureL0ReferenceCount = 1; + h264_enc_ext->maxL1ReferenceCount = 1; h264_enc_ext->maxTemporalLayerCount = 1; h264_enc_ext->expectDyadicTemporalLayerPattern = VK_FALSE; h264_enc_ext->minQp = 0; diff --git a/src/intel/vulkan_hasvk/anv_video_h264_bitstream.c b/src/intel/vulkan_hasvk/anv_video_h264_bitstream.c index 02f2af2..c824730 100644 --- a/src/intel/vulkan_hasvk/anv_video_h264_bitstream.c +++ b/src/intel/vulkan_hasvk/anv_video_h264_bitstream.c @@ -108,7 +108,10 @@ anv_h264_build_encode_slice_header(uint8_t *out_buf, uint32_t out_cap, bool is_idr = pic_info->flags.IdrPicFlag; unsigned nal_unit_type = is_idr ? 5 : 1; - unsigned nal_ref_idc = 1; /* I-frame is always a reference */ + /* A picture nothing will reference must say so, or a decoder keeps it in + * the DPB and its frame_num accounting drifts: an IDR is always a + * reference, otherwise the client's is_reference decides. */ + unsigned nal_ref_idc = (is_idr || pic_info->flags.is_reference) ? 1 : 0; bw_put_bit(&bw, 0); /* forbidden_zero_bit */ bw_put_u(&bw, nal_ref_idc, 2); @@ -139,14 +142,24 @@ anv_h264_build_encode_slice_header(uint8_t *out_buf, uint32_t out_cap, if (pps->flags.redundant_pic_cnt_present_flag) bw_put_ue(&bw, 0); - /* P-slice reference-list syntax (H.264 7.3.3). B slices are out of scope, so - * direct_spatial_mv_pred_flag is never emitted. */ - bool is_p = (slice_hdr->slice_type == STD_VIDEO_H264_SLICE_TYPE_P); + /* Inter reference-list syntax (H.264 7.3.3). */ + bool is_b = (slice_hdr->slice_type == STD_VIDEO_H264_SLICE_TYPE_B); + bool is_p = is_b || (slice_hdr->slice_type == STD_VIDEO_H264_SLICE_TYPE_P); + if (is_b) { + /* direct_spatial_mv_pred_flag = 1: spatial direct mode, which derives + * its motion from neighbours instead of the co-located picture's stored + * vectors, so no direct MV buffer has to be read back. */ + bw_put_bit(&bw, 1); + } if (is_p) { - /* num_ref_idx_active_override_flag = 0: use the PPS default L0 count. */ + /* num_ref_idx_active_override_flag = 0: use the PPS default counts. */ bw_put_bit(&bw, 0); /* ref_pic_list_modification_flag_l0 = 0: default frame_num-ordered list. */ bw_put_bit(&bw, 0); + if (is_b) { + /* ref_pic_list_modification_flag_l1 = 0: default POC-ordered list. */ + bw_put_bit(&bw, 0); + } } /* pred_weight_table is absent (weighted_pred_flag is off). */ diff --git a/src/intel/vulkan_hasvk/anv_video_mfc.c b/src/intel/vulkan_hasvk/anv_video_mfc.c index f1f5600..fbfd65e 100644 --- a/src/intel/vulkan_hasvk/anv_video_mfc.c +++ b/src/intel/vulkan_hasvk/anv_video_mfc.c @@ -192,9 +192,9 @@ genX(hasvk_mfc_emit_static_state)(struct anv_cmd_buffer *cmd_buffer, avc_img.TrellisQuantizationChromaDisable = false; avc_img.IntraMBConformanceMaxSize = 0xEE8; avc_img.InterMBConformanceMaxSize = 0xBB8; - avc_img.NumberofReferenceFrames = is_p ? 1 : 0; - avc_img.NumberofActiveReferencePicturesfromL0 = is_p ? 1 : 0; - avc_img.NumberofActiveReferencePicturesfromL1 = 0; + avc_img.NumberofReferenceFrames = refs->l0_count + refs->l1_count; + avc_img.NumberofActiveReferencePicturesfromL0 = refs->l0_count; + avc_img.NumberofActiveReferencePicturesfromL1 = refs->l1_count; avc_img.InitialQPValue = 0; /* per-slice QP comes from SLICE_STATE */ avc_img.PicOrderPresent = pps->flags.bottom_field_pic_order_in_frame_present_flag; avc_img.DeltaPicOrderAlwaysZero = sps->flags.delta_pic_order_always_zero_flag; @@ -304,11 +304,12 @@ genX(hasvk_mfc_emit_static_state)(struct anv_cmd_buffer *cmd_buffer, 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) { - unsigned n = MIN2(refs->l0_count, - ARRAY_SIZE(ref_idx.ReferenceListEntry)); + const uint8_t *slots = list == 0 ? refs->l0_slot : refs->l1_slot; + unsigned count = list == 0 ? refs->l0_count : refs->l1_count; + if (is_p) { + unsigned n = MIN2(count, ARRAY_SIZE(ref_idx.ReferenceListEntry)); for (unsigned i = 0; i < n; i++) - ref_idx.ReferenceListEntry[i] = 0x20 | (refs->l0_slot[i] << 1); + ref_idx.ReferenceListEntry[i] = 0x20 | (slots[i] << 1); } } } @@ -328,16 +329,19 @@ genX(hasvk_mfc_emit_slice)(struct anv_cmd_buffer *cmd_buffer, 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. */ - bool is_p = (slice_hdr->slice_type == STD_VIDEO_H264_SLICE_TYPE_P); + /* An I slice predicts from nothing; P and B slices both predict from L0, + * and a B slice additionally has L1 available. */ + bool is_b = (slice_hdr->slice_type == STD_VIDEO_H264_SLICE_TYPE_B); + bool is_p = is_b || (slice_hdr->slice_type == STD_VIDEO_H264_SLICE_TYPE_P); /* MFX_AVC_SLICE_STATE is not used by decode; fields are matched against the * VA-API reference encoder (gen6_mfc_avc_slice_state). The slice covers the * macroblock range [slice_start_mb, slice_end_mb) in raster order. */ anv_batch_emit(&cmd_buffer->batch, GENX(MFX_AVC_SLICE_STATE), slice) { - slice.SliceType = is_p ? PSlice : ISlice; /* P=0,B=1,I=2 */ + slice.SliceType = is_b ? BSlice : + is_p ? PSlice : ISlice; /* P=0,B=1,I=2 */ slice.NumberofReferencePicturesinInterpredictionList0 = is_p ? 1 : 0; + slice.NumberofReferencePicturesinInterpredictionList1 = is_b ? 1 : 0; slice.SliceQuantizationParameter = qp; slice.CABACInitIDC = is_p ? slice_hdr->cabac_init_idc : 0; slice.DisableDeblockingFilterIndicator = 1; diff --git a/src/intel/vulkan_hasvk/genX_cmd_video.c b/src/intel/vulkan_hasvk/genX_cmd_video.c index 68878e7..1f722f0 100644 --- a/src/intel/vulkan_hasvk/genX_cmd_video.c +++ b/src/intel/vulkan_hasvk/genX_cmd_video.c @@ -1237,6 +1237,14 @@ anv_h264_encode_video(struct anv_cmd_buffer *cmd_buffer, break; refs.l0_slot[refs.l0_count++] = s; } + n = MIN2(ref_lists->num_ref_idx_l1_active_minus1 + 1, + ANV_VIDEO_MAX_DPB_SLOTS); + for (unsigned i = 0; i < n; i++) { + uint8_t s = ref_lists->RefPicList1[i]; + if (s >= ANV_VIDEO_MAX_DPB_SLOTS || !refs.slot_valid[s]) + break; + refs.l1_slot[refs.l1_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. */