/* * Copyright © 2024 Intel Corporation * * SPDX-License-Identifier: MIT * * H.264 encode: MFC/MFX static state emission for the AVC encode pipeline. * * This lives in a separate translation unit because genX_pack.h and * genX_video_pack.h share the same include guard (GFX7_IVB_PACK_H); a TU that * includes both silently drops the second. The MFX and MFC encode commands * need genX_video_pack.h, mirroring the decode split between genX_cmd_video.c * and genX_cmd_buffer.c. * * Field names are taken from the decode paths in genX_cmd_video.c where the * same commands are used. MFX_AVC_SLICE_STATE is the exception: it is not used * by decode, so its fields are matched against the VA-API reference encoder * (gen6_mfc_avc_slice_state, intel-vaapi-driver) rather than an in-tree user. * * Validated for I and P frames in CQP mode, single reference, no motion * estimation. B frames, multiple references and rate control are not emitted. */ #include #include "anv_private.h" #include "genxml/gen_macros.h" #include "anv_genX.h" #include "genxml/genX_video_pack.h" #include "util/vl_zscan_data.h" #include "anv_video_h264_bitstream.h" /* Debug switch: code P macroblocks as pure skips, carrying no residual. */ static bool anv_video_enc_skip_only(void) { static int enabled = -1; if (enabled < 0) enabled = getenv("HASVK_ENC_SKIP_ONLY") != NULL; return enabled; } void genX(hasvk_mfc_emit_static_state)(struct anv_cmd_buffer *cmd_buffer, const struct anv_image *source_image, struct anv_address recon_address, const StdVideoH264SequenceParameterSet *sps, const StdVideoH264PictureParameterSet *pps, struct anv_address dst_address, uint32_t dst_page_offset, uint64_t dst_buffer_range, uint32_t width_in_mbs, uint32_t height_in_mbs, bool is_p, const struct anv_video_reference_state *refs) { struct anv_video_session *vid = cmd_buffer->video.vid; anv_batch_emit(&cmd_buffer->batch, GENX(MFX_PIPE_MODE_SELECT), sel) { sel.StandardSelect = SS_AVC; sel.CodecSelect = Encode; /* PAK requires the long-format interface and writes its output through * the stream-out path. Deblocking is disabled here, so reconstruction * uses the pre-deblocking destination. */ sel.DecoderShortFormatMode = LongFormatDriverInterface; sel.StreamOutEnable = true; sel.PreDeblockingOutputEnable = true; } anv_batch_emit(&cmd_buffer->batch, GENX(MFX_SURFACE_STATE), ss) { ss.Width = source_image->vk.extent.width - 1; ss.Height = height_in_mbs * 16 - 1; ss.SurfaceFormat = PLANAR_420_8; ss.InterleaveChroma = 1; ss.SurfacePitch = source_image->planes[0].primary_surface.isl.row_pitch_B - 1; ss.TiledSurface = source_image->planes[0].primary_surface.isl.tiling != ISL_TILING_LINEAR; ss.TileWalk = TW_YMAJOR; ss.YOffsetforUCb = MIN2((source_image->planes[1].primary_surface.memory_range.offset - source_image->planes[0].primary_surface.memory_range.offset) / source_image->planes[0].primary_surface.isl.row_pitch_B, 32767u); ss.YOffsetforVCr = 0; } struct anv_address mv_object_address = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_ENC_INPUT_MV].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_ENC_INPUT_MV].offset }; anv_batch_emit(&cmd_buffer->batch, GENX(MFX_IND_OBJ_BASE_ADDR_STATE), index_obj) { index_obj.MFCIndirectPAKBSEObjectAddress = dst_address; /* Base for the per-MB indirect PAK-MV input data (inter/skip MBs). The * MFC sets up its MV fetch unit from this base even when a given MB reads * no MV data, so it must be a valid address rather than null. */ index_obj.MFXIndirectMVObjectAddress = mv_object_address; #if GFX_VERx10 == 70 /* AccessUpperBound and CacheabilityControl exist under these names only * on Gen7; Gen7.5 has only the MOCS field and Gen8 renames the bound. * Encode is Gen7-only, so gate the extra fields accordingly. */ index_obj.MFCIndirectPAKBSEObjectAccessUpperBound = anv_address_add(dst_address, dst_page_offset + dst_buffer_range); index_obj.MFCIndirectPAKBSEObjectCacheabilityControl = 2; index_obj.MFXIndirectMVObjectCacheabilityControl = 2; #endif } /* Zero the first 32 bytes of the indirect MV buffer so every P macroblock, * which reads its 8MV (32-byte) block from offset 0, sees a zero motion * vector (co-located prediction). The MFC executes these serialising writes * before it fetches the MV data for the PAK objects that follow. */ if (is_p) { for (unsigned i = 0; i < 32; i += 8) genX(emit_query_mi_write_u64)(cmd_buffer, anv_address_add(mv_object_address, i), 0); } /* Scratch buffers are bound at video-session level via * vkBindVideoSessionMemoryKHR (ANV_VID_MEM_H264_ENC_*). The reconstructed * picture (pre-deblocking destination) is the caller's setup DPB slot. * * This block (PIPE_BUF_ADDR_STATE .. REF_IDX_STATE) is Gen7-only: field * names and array layouts differ across gens (e.g. DirectMVBufferAddress[] * vs the per-index fields on Gen7.5, and MFX_FQM_STATE not existing at all * on Gen7.5/Gen8). */ #if GFX_VERx10 == 70 anv_batch_emit(&cmd_buffer->batch, GENX(MFX_PIPE_BUF_ADDR_STATE), buf) { buf.PreDeblockingDestinationAddress = recon_address; buf.OriginalUncompressedPictureSourceCacheabilityControl = 2; buf.StreamOutDataDestinationCacheabilityControl = 2; buf.IntraRowStoreScratchBufferCacheabilityControl = 2; buf.DeblockingFilterRowStoreScratchBufferCacheabilityControl = 2; buf.MBStatusBufferCacheabilityControl = 2; memset(buf.ReferencePictureCacheabilityControl, 0, sizeof(buf.ReferencePictureCacheabilityControl)); buf.OriginalUncompressedPictureSourceAddress = anv_image_address(source_image, &source_image->planes[0].primary_surface.memory_range); buf.StreamOutDataDestinationAddress = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_ENC_MB_STATUS].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_ENC_MB_STATUS].offset }; buf.IntraRowStoreScratchBufferAddress = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_INTRA_ROW_STORE].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_INTRA_ROW_STORE].offset }; buf.DeblockingFilterRowStoreScratchBufferAddress = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_DEBLOCK_FILTER_ROW_STORE].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_DEBLOCK_FILTER_ROW_STORE].offset }; buf.MBStatusBufferAddress = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_ENC_MB_STATUS].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_ENC_MB_STATUS].offset }; /* PRM Vol2 Pt3, MFX_PIPE_BUF_ADDR_STATE, ReferencePictureAddress[0..15]: * "AVC: Always specifies all 16 addresses ... All invalid addresses must * be set to a valid address RefAddr[0] by the driver." Leaving these at * zero risks a fault even when references are unused (I-frame), so fill * them with the valid, mapped recon address. */ for (unsigned i = 0; i < ARRAY_SIZE(buf.ReferencePictureAddress); i++) buf.ReferencePictureAddress[i] = recon_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) { /* Only the BSD/MPC row store is used by PAK; MPR row store and bitplane * read buffer are left null, matching gen6_mfc_bsp_buf_base_addr_state. */ bsp.BSDMPCRowStoreScratchBufferAddress = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_BSD_MPC_ROW_SCRATCH].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_BSD_MPC_ROW_SCRATCH].offset }; bsp.BSDMPCRowStoreScratchBufferCacheabilityControl = 2; } #endif /* GFX_VERx10 == 70 */ anv_batch_emit(&cmd_buffer->batch, GENX(MFX_AVC_IMG_STATE), avc_img) { avc_img.FrameWidth = MIN2(width_in_mbs - 1, 255u); avc_img.FrameHeight = MIN2(height_in_mbs - 1, 254u); avc_img.FrameSize = width_in_mbs * height_in_mbs - 1; avc_img.ImageStructure = FramePicture; avc_img.WeightedBiPredictionIDC = pps->weighted_bipred_idc; avc_img.WeightedPredictionEnable = pps->flags.weighted_pred_flag; avc_img.FirstChromaQPOffset = CLAMP(pps->chroma_qp_index_offset, -12, 12); avc_img.SecondChromaQPOffset = CLAMP(pps->second_chroma_qp_index_offset, -12, 12); avc_img.FieldPicture = false; avc_img.MBAFFMode = false; avc_img.FrameMBOnly = sps->flags.frame_mbs_only_flag; avc_img._8x8IDCTTransformMode = pps->flags.transform_8x8_mode_flag; avc_img.Direct8x8Inference = sps->flags.direct_8x8_inference_flag; avc_img.ConstrainedIntraPrediction = pps->flags.constrained_intra_pred_flag; avc_img.NonReferencePicture = false; avc_img.EntropyCodingSyncEnable = pps->flags.entropy_coding_mode_flag; avc_img.ChromaFormatIDC = sps->chroma_format_idc; avc_img.MVUnpackedEnable = true; 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.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; avc_img.PicOrderCountType = sps->pic_order_cnt_type; avc_img.DeblockingFilterControlPresent = pps->flags.deblocking_filter_control_present_flag; avc_img.RedundantPicCountPresent = pps->flags.redundant_pic_cnt_present_flag; avc_img.Log2MaxFrameNumber = MIN2((uint32_t)sps->log2_max_frame_num_minus4, 12u); avc_img.Log2MaxPicOrderCountLSB = MIN2((uint32_t)sps->log2_max_pic_order_cnt_lsb_minus4, 12u); avc_img.CurrentPictureFrameNumber = 0; /* single IDR frame */ } /* Quantizer matrices. With no custom scaling list in the PPS, * vk_video_derive_h264_scaling_list() returns the flat default (all 16). */ StdVideoH264ScalingLists scaling_lists; vk_video_derive_h264_scaling_list(sps, pps, &scaling_lists); anv_batch_emit(&cmd_buffer->batch, GENX(MFX_QM_STATE), qm) { qm.DWordLength = 16; qm.AVC = AVC_4x4_Intra_MATRIX; for (unsigned m = 0; m < 3; m++) for (unsigned q = 0; q < 16; q++) qm.ForwardQuantizerMatrix[m * 16 + vl_zscan_normal_16[q]] = scaling_lists.ScalingList4x4[m][q]; } /* Inter matrices are a separate set of registers: ScalingList4x4[3..5] are * the inter Y/Cb/Cr lists, [0..2] the intra ones. Without this the hardware * quantizes every inter residual with whatever those registers happen to * hold, which leaves I frames correct and P residuals corrupted. */ anv_batch_emit(&cmd_buffer->batch, GENX(MFX_QM_STATE), qm) { qm.DWordLength = 16; qm.AVC = AVC_4x4_Inter_MATRIX; for (unsigned m = 0; m < 3; m++) for (unsigned q = 0; q < 16; q++) qm.ForwardQuantizerMatrix[m * 16 + vl_zscan_normal_16[q]] = scaling_lists.ScalingList4x4[3 + m][q]; } if (pps->flags.transform_8x8_mode_flag) { anv_batch_emit(&cmd_buffer->batch, GENX(MFX_QM_STATE), qm) { qm.DWordLength = 16; qm.AVC = AVC_8x8_Intra_MATRIX; for (unsigned q = 0; q < 64; q++) qm.ForwardQuantizerMatrix[vl_zscan_normal[q]] = scaling_lists.ScalingList8x8[0][q]; } anv_batch_emit(&cmd_buffer->batch, GENX(MFX_QM_STATE), qm) { qm.DWordLength = 16; qm.AVC = AVC_8x8_Inter_MATRIX; for (unsigned q = 0; q < 64; q++) qm.ForwardQuantizerMatrix[vl_zscan_normal[q]] = scaling_lists.ScalingList8x8[1][q]; } } /* MFX_FQM_STATE (forward quantizer matrix) does not exist on Gen7.5/Gen8; * the block below is Gen7-only. For a flat scaling list the VA-API encoder * (gen7_mfc_avc_fqm_state) programs the constant 0x10001000; a custom PPS * scaling list would require the proper reciprocal and is not handled. */ #if GFX_VERx10 == 70 anv_batch_emit(&cmd_buffer->batch, GENX(MFX_FQM_STATE), fqm) { fqm.DWordLength = 32; fqm.AVC = AVC_4x4_Intra_MATRIX; for (unsigned q = 0; q < 32; q++) fqm.ForwardQuantizerMatrix[q] = 0x10001000; } anv_batch_emit(&cmd_buffer->batch, GENX(MFX_FQM_STATE), fqm) { fqm.DWordLength = 32; fqm.AVC = AVC_4x4_Inter_MATRIX; for (unsigned q = 0; q < 32; q++) fqm.ForwardQuantizerMatrix[q] = 0x10001000; } if (pps->flags.transform_8x8_mode_flag) { anv_batch_emit(&cmd_buffer->batch, GENX(MFX_FQM_STATE), fqm) { fqm.DWordLength = 32; fqm.AVC = AVC_8x8_Intra_MATRIX; for (unsigned q = 0; q < 32; q++) fqm.ForwardQuantizerMatrix[q] = 0x10001000; } anv_batch_emit(&cmd_buffer->batch, GENX(MFX_FQM_STATE), fqm) { fqm.DWordLength = 32; fqm.AVC = AVC_8x8_Inter_MATRIX; for (unsigned q = 0; q < 32; q++) fqm.ForwardQuantizerMatrix[q] = 0x10001000; } } /* Direct MV buffers. I-frames scan no reference direct MVs, so only the * current-picture write targets (top/bottom) are set. The POC list is * filled as gen6_mfc_avc_directmode_state does (POCList[i] = i/2). */ anv_batch_emit(&cmd_buffer->batch, GENX(MFX_AVC_DIRECTMODE_STATE), dmv) { memset(dmv.DirectMVBufferAddress, 0, sizeof(dmv.DirectMVBufferAddress)); dmv.DirectMVBufferWriteAddress[0] = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_ENC_DIRECT_MV_TOP].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_ENC_DIRECT_MV_TOP].offset }; dmv.DirectMVBufferWriteAddress[1] = (struct anv_address) { vid->vid_mem[ANV_VID_MEM_H264_ENC_DIRECT_MV_BOTTOM].mem->bo, vid->vid_mem[ANV_VID_MEM_H264_ENC_DIRECT_MV_BOTTOM].offset }; for (unsigned i = 0; i < 32; i++) dmv.POCList[i] = i / 2; } /* 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) { 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 */ } void genX(hasvk_mfc_emit_slice)(struct anv_cmd_buffer *cmd_buffer, const StdVideoH264SequenceParameterSet *sps, const StdVideoH264PictureParameterSet *pps, const StdVideoEncodeH264PictureInfo *pic_info, const StdVideoEncodeH264SliceHeader *slice_hdr, 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, 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); /* 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.NumberofReferencePicturesinInterpredictionList0 = is_p ? 1 : 0; slice.SliceQuantizationParameter = qp; slice.CABACInitIDC = is_p ? slice_hdr->cabac_init_idc : 0; slice.DisableDeblockingFilterIndicator = 1; slice.SliceAlphaC0OffsetDiv2 = 0; slice.SliceBetaOffsetDiv2 = 0; slice.SliceStartMBNumber = slice_start_mb; slice.SliceHorizontalPosition = slice_start_mb % width_in_mbs; slice.SliceVerticalPosition = slice_start_mb / width_in_mbs; slice.NextSliceHorizontalPosition = slice_end_mb % width_in_mbs; slice.NextSliceVerticalPosition = slice_end_mb / width_in_mbs; slice.LastSliceGroup = is_last_slice; slice.HeaderInsertionPresent = true; slice.SliceDataInsertionPresent = true; slice.TailInsertionPresent = true; slice.EmulationByteSliceInsertEnable = true; slice.CABACZeroWordInsertionEnable = false; /* Without these the inserted slice header/tail are not emitted into the * bitstream (gen6_mfc_avc_slice_state). */ slice.ResetRateControlCounter = true; slice.RateControlStableTolerance = 4; /* All slices point at the frame's bitstream start; the MFC advances the * write pointer internally between slices because LoadBitstreamPointerPer- * Slice is left 0 in MFX_AVC_IMG_STATE (pointer loaded once per frame). * The start is a byte offset from the 4KiB-aligned PAK-BSE object base in * MFX_IND_OBJ_BASE_ADDR_STATE, so it carries the sub-page part of the * destination buffer offset that the page-granular base cannot encode. */ slice.IndirectPAKBSEDataStartAddress = dst_page_offset; } /* Slice header RBSP. SkipEmulationByteCount = 5 covers the 4-byte start * code plus the 1-byte NAL header. */ uint8_t slice_header_buf[64]; uint32_t slice_header_bits = anv_h264_build_encode_slice_header(slice_header_buf, sizeof(slice_header_buf), sps, pps, pic_info, slice_hdr); if (slice_header_bits == 0) return; uint32_t slice_header_dws = DIV_ROUND_UP(slice_header_bits, 32); /* PRM Vol2 Pt3, MFX_PAK_INSERT_OBJECT, DataBitsInLastDW: valid range is * [1,32]. A naive bits % 32 yields 0 when bits is a multiple of 32, which * is illegal; map that to 32. */ uint32_t slice_header_bits_in_last_dw = slice_header_bits & 0x1f; if (slice_header_bits_in_last_dw == 0) slice_header_bits_in_last_dw = 32; uint32_t *header_dst = anv_batch_emitn(&cmd_buffer->batch, 2 + slice_header_dws, GENX(MFX_PAK_INSERT_OBJECT), .EmulationByteBitsInsertEnable = true, .SkipEmulationByteCount = 5, .DataBitsInLastDW = slice_header_bits_in_last_dw, .LastHeader = true, .EndofSlice = false); if (header_dst) memcpy(header_dst + 2, slice_header_buf, slice_header_dws * 4); /* One MFC_AVC_PAK_OBJECT per macroblock. Field layout per PRM Vol2 Pt3 * §2.3.1.1 "PAK Object Inline Data Description". An I slice codes every MB as * I_16x16 DC; a P slice codes every MB as P_Skip (see below). * * InlineData[0] (DW3): I-frame path: * bit 13 IntraMbFlag = 1. * bits 12:8 MbType5Bits = 23: the raw AVC mb_type (PRM: "unified * encoding ... according to AVC Spec", not mb_type-1). Per * Table 7-11, I_16x16 mb_type = 1 + PredMode + 4*CBPChroma + * 12*CBPLuma, so 23 = I_16x16_2_2_1 (PredMode = DC). * bits 19:17 CbpDcY/U/V = 1: DC sub-blocks coded. * InlineData[4] (DW7) bits 1:0: LumaIntraMode[0] = DC (only [1:0] valid for * Intra16x16). * InlineData[6] (DW9): IntraStruct = per-MB IntraPredAvailFlags plus * ChromaIntraPredMode = DC in bits [1:0]. * * P-frame path: every MB is a coded BP_L0_16x16 inter MB with a zero motion * vector, so PAK transforms and codes the residual against the co-located * reference block. Per PRM §2.3.1.1, DW3 bit13 IntraMbFlag=0 (inter), * bits12:8 MbType5Bits=1 (BP_L0_16x16), bits22:20 MvFormat=100 (8MV, reading * the zeroed 32-byte block from the indirect MV buffer; MvFormat=0 hangs the * MFC). Unlike Intra16x16 there is no luma DC block, but chroma keeps its 2x2 * DC one. DW7-9 carry the inter sub-mb shape (0 = 16x16) and reference * indices L0/L1 (0 for the single reference). * * There is no motion estimation yet: a moving block still costs a full * residual rather than a motion vector. */ for (uint32_t mb = slice_start_mb; mb < slice_end_mb; mb++) { uint32_t x = mb % width_in_mbs; uint32_t y = mb / width_in_mbs; bool last_mb = (mb == slice_end_mb - 1); anv_batch_emit(&cmd_buffer->batch, GENX(MFC_AVC_PAK_OBJECT), pak) { pak.IndirectPAKMVDataLength = 0; pak.IndirectPAKMVDataStartAddressOffset = 0; pak.InlineData[1] = (y << 8) | x; /* Cbp4x4Y=0, MbY/XCnt */ pak.InlineData[3] = ((uint32_t)last_mb << 26) | qp; /* 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. */ pak.IndirectPAKMVDataLength = 32; /* 8MV * 4 bytes */ if (anv_video_enc_skip_only()) { pak.InlineData[0] = (1u << 8) /* MbType5Bits = 1: BP_L0_16x16 */ | (4u << 20) /* MvFormat = 100 (8MV) */ | (1u << 2); /* SkipMbFlag: force P_Skip */ pak.InlineData[1] = (y << 8) | x; /* Cbp4x4Y = 0 for skip */ pak.InlineData[2] = 0; /* Cbp4x4V/U = 0 for skip */ } else { pak.InlineData[0] = (1u << 8) /* MbType5Bits = 1: BP_L0_16x16 */ | (4u << 20) /* MvFormat = 100 (8MV) */ | (3u << 18); /* CbpDcU/V: chroma DC coded */ pak.InlineData[1] = 0xFFFF0000 | (y << 8) | x; /* all luma 4x4 */ pak.InlineData[2] = 0x000F000F; /* all chroma AC */ } pak.InlineData[4] = 0; /* inter sub-mb shape = 16x16 */ pak.InlineData[5] = 0; /* ref index L0 = 0 */ 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 */ pak.InlineData[1] = 0xFFFF0000 | (y << 8) | x; pak.InlineData[2] = 0x000F000F; pak.InlineData[4] = 2; /* LumaIntraMode[0] = DC */ pak.InlineData[5] = 0; /* IntraPredAvailFlags must reflect real neighbor availability so PAK * DC prediction matches the decoder; intra prediction does not cross * slice boundaries, so a neighbour counts only when its MB address is * >= 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 && NEIGHBOUR_OK(mb - 1, x - 1, y)) intra_avail |= (1u << 7) | (1u << 6) | (1u << 5); if (y > 0 && NEIGHBOUR_OK(mb - width_in_mbs, x, y - 1)) intra_avail |= (1u << 4); 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 && 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; } } } /* Slice tail: two zero bytes, marking end of slice (gen6_mfc_avc). */ uint32_t tail_data[2] = { 0, 0 }; uint32_t *tail_dst = anv_batch_emitn(&cmd_buffer->batch, 2 + 2, GENX(MFX_PAK_INSERT_OBJECT), .EmulationByteBitsInsertEnable = false, .SkipEmulationByteCount = 2, .DataBitsInLastDW = 8, .LastHeader = true, .EndofSlice = true); if (tail_dst) memcpy(tail_dst + 2, tail_data, sizeof(tail_data)); }