mirror of
https://github.com/elwrcl/intel-hard-graphics.git
synced 2026-10-31 19:08:03 +03:00
This reverts commit 81cf1c16db.
The B frame work was never verified beyond its own four tests: every attempt
to run the whole encode family since has aborted on a device loss. The
bisect that seemed to clear it was worthless, because only the first run
after a reboot was on a healthy GPU and every later measurement was taken on
a ring that had already hung.
Going back to the last commit with a completed run behind it: 120 of 120
executed, 92 passing, none failing. B pictures can come back once they can
be verified one change at a time, on a machine with no other GPU client to
lose.
523 lines
27 KiB
C
523 lines
27 KiB
C
/*
|
|
* 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 <string.h>
|
|
|
|
#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));
|
|
}
|