// Copyright (c) 2019-2022 Intel Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include "mfx_common.h"
#include "hevcehw_base_va_packer_lin.h"
#include "mfx_platform_caps.h"

#if defined(MFX_ENABLE_H265_VIDEO_ENCODE)
#include "mfx_common_int.h"
#include "hevcehw_base_va_lin.h"
#include "hevcehw_base_legacy.h"
#if defined(MFX_ENABLE_ENCTOOLS_SW)
#include "hevcehw_base_enctools_qmatrix_lin.h"
#endif

using namespace HEVCEHW;
using namespace HEVCEHW::Base;
using namespace HEVCEHW::Linux;
using namespace HEVCEHW::Linux::Base;

void VAPacker::Query1WithCaps(const FeatureBlocks& /*blocks*/, TPushQ1 Push)
{
    Push(BLK_HardcodeCaps
        , [this](const mfxVideoParam&, mfxVideoParam& par, StorageRW& strg) -> mfxStatus
    {
        eMFXHWType hw        = Glob::VideoCore::Get(strg).GetHWType();
        auto& caps           = Glob::EncodeCaps::Get(strg);
        auto& ddiidSetting = Glob::DDIIDSetting::Get(strg);
        VAID* vaid           = (VAID*)ddiidSetting.DDIID;
        MFX_CHECK_NULL_PTR1(vaid);

        HardcodeCapsCommon(caps, par);

        bool bLP = IsOn(par.mfx.LowPower);

        caps.LCUSizeSupported |=
              (32 >> 4) * (!bLP)
            + (64 >> 4);

        caps.BRCReset                   = 1; // no bitrate resolution control
        caps.BlockSize                  = 1;
        caps.MbQpDataSupport            = 1;
        caps.TUSupport                  = 73;
        if (HEVCECaps::IsTUExtended(hw))
        {
            caps.TUSupport = 107;
        }
        caps.ParallelBRC                = bLP ? 0 : 1;

        caps.MaxEncodedBitDepth |= (!caps.BitDepth8Only);
        caps.YUV444ReconSupport |= (!caps.Color420Only && IsOn(par.mfx.LowPower));
        caps.YUV422ReconSupport &= (!caps.Color420Only && !IsOn(par.mfx.LowPower));
        if (HEVCECaps::IsNative422Supported(hw))
        {
            caps.YUV422ReconSupport = 1;
        }

        return MFX_ERR_NONE;
    });

}

void InitSPS(
    const ExtBuffer::Param<mfxVideoParam>& par
    , const SPS& bs_sps
    , VAEncSequenceParameterBufferHEVC & sps)
{
    sps = {};

    sps.general_profile_idc = bs_sps.general.profile_idc;
    sps.general_level_idc   = bs_sps.general.level_idc;
    sps.general_tier_flag   = bs_sps.general.tier_flag;

    sps.intra_period        = par.mfx.GopPicSize;
    sps.intra_idr_period    = par.mfx.GopPicSize * par.mfx.IdrInterval;
    sps.ip_period           = par.mfx.GopRefDist;

    if (   par.mfx.RateControlMethod != MFX_RATECONTROL_CQP
        && par.mfx.RateControlMethod != MFX_RATECONTROL_ICQ)
    {
        sps.bits_per_second = TargetKbps(par.mfx) * 1000;
    }

    sps.pic_width_in_luma_samples  = (uint16_t)bs_sps.pic_width_in_luma_samples;
    sps.pic_height_in_luma_samples = (uint16_t)bs_sps.pic_height_in_luma_samples;

    sps.seq_fields.bits.chroma_format_idc                   = bs_sps.chroma_format_idc;
    sps.seq_fields.bits.separate_colour_plane_flag          = bs_sps.separate_colour_plane_flag;
    sps.seq_fields.bits.bit_depth_luma_minus8               = bs_sps.bit_depth_luma_minus8;
    sps.seq_fields.bits.bit_depth_chroma_minus8             = bs_sps.bit_depth_chroma_minus8;
    sps.seq_fields.bits.scaling_list_enabled_flag           = bs_sps.scaling_list_enabled_flag;
    sps.seq_fields.bits.strong_intra_smoothing_enabled_flag = bs_sps.strong_intra_smoothing_enabled_flag;
    sps.seq_fields.bits.amp_enabled_flag                    = bs_sps.amp_enabled_flag;
    sps.seq_fields.bits.sample_adaptive_offset_enabled_flag = bs_sps.sample_adaptive_offset_enabled_flag;
    sps.seq_fields.bits.pcm_enabled_flag                    = bs_sps.pcm_enabled_flag;
    sps.seq_fields.bits.pcm_loop_filter_disabled_flag       = 1;//bs_sps.pcm_loop_filter_disabled_flag;
    sps.seq_fields.bits.sps_temporal_mvp_enabled_flag       = bs_sps.temporal_mvp_enabled_flag;

    sps.seq_fields.bits.low_delay_seq    = bs_sps.low_delay_mode;
    sps.seq_fields.bits.hierachical_flag = bs_sps.hierarchical_flag;

    sps.log2_min_luma_coding_block_size_minus3 = (mfxU8)bs_sps.log2_min_luma_coding_block_size_minus3;

    sps.log2_diff_max_min_luma_coding_block_size   = (mfxU8)bs_sps.log2_diff_max_min_luma_coding_block_size;
    sps.log2_min_transform_block_size_minus2       = (mfxU8)bs_sps.log2_min_transform_block_size_minus2;
    sps.log2_diff_max_min_transform_block_size     = (mfxU8)bs_sps.log2_diff_max_min_transform_block_size;
    sps.max_transform_hierarchy_depth_inter        = (mfxU8)bs_sps.max_transform_hierarchy_depth_inter;
    sps.max_transform_hierarchy_depth_intra        = (mfxU8)bs_sps.max_transform_hierarchy_depth_intra;
    sps.pcm_sample_bit_depth_luma_minus1           = (mfxU8)bs_sps.pcm_sample_bit_depth_luma_minus1;
    sps.pcm_sample_bit_depth_chroma_minus1         = (mfxU8)bs_sps.pcm_sample_bit_depth_chroma_minus1;
    sps.log2_min_pcm_luma_coding_block_size_minus3 = (mfxU8)bs_sps.log2_min_pcm_luma_coding_block_size_minus3;
    sps.log2_max_pcm_luma_coding_block_size_minus3 = (mfxU8)(bs_sps.log2_min_pcm_luma_coding_block_size_minus3
        + bs_sps.log2_diff_max_min_pcm_luma_coding_block_size);

    sps.vui_parameters_present_flag                             = bs_sps.vui_parameters_present_flag;
    sps.vui_fields.bits.aspect_ratio_info_present_flag          = bs_sps.vui.aspect_ratio_info_present_flag;
    sps.vui_fields.bits.neutral_chroma_indication_flag          = bs_sps.vui.neutral_chroma_indication_flag;
    sps.vui_fields.bits.field_seq_flag                          = bs_sps.vui.field_seq_flag;
    sps.vui_fields.bits.vui_timing_info_present_flag            = bs_sps.vui.timing_info_present_flag;
    sps.vui_fields.bits.bitstream_restriction_flag              = bs_sps.vui.bitstream_restriction_flag;
    sps.vui_fields.bits.tiles_fixed_structure_flag              = bs_sps.vui.tiles_fixed_structure_flag;
    sps.vui_fields.bits.motion_vectors_over_pic_boundaries_flag = bs_sps.vui.motion_vectors_over_pic_boundaries_flag;
    sps.vui_fields.bits.restricted_ref_pic_lists_flag           = bs_sps.vui.restricted_ref_pic_lists_flag;
    sps.vui_fields.bits.log2_max_mv_length_horizontal           = bs_sps.vui.log2_max_mv_length_horizontal;
    sps.vui_fields.bits.log2_max_mv_length_vertical             = bs_sps.vui.log2_max_mv_length_vertical;


    sps.aspect_ratio_idc             = bs_sps.vui.aspect_ratio_idc;
    sps.sar_width                    = bs_sps.vui.sar_width;
    sps.sar_height                   = bs_sps.vui.sar_height;
    sps.vui_num_units_in_tick        = bs_sps.vui.num_units_in_tick;
    sps.vui_time_scale               = bs_sps.vui.time_scale;
    sps.min_spatial_segmentation_idc = bs_sps.vui.min_spatial_segmentation_idc;
    sps.max_bytes_per_pic_denom      = bs_sps.vui.max_bytes_per_pic_denom;
    sps.max_bits_per_min_cu_denom    = bs_sps.vui.max_bits_per_min_cu_denom;
}

void InitPPS(
    const ExtBuffer::Param<mfxVideoParam>& /*par*/
    , const PPS& bs_pps
    , VAEncPictureParameterBufferHEVC & pps)
{
    pps = {};

    std::for_each(
        std::begin(pps.reference_frames)
        , std::end(pps.reference_frames)
        , [](decltype(*std::begin(pps.reference_frames)) ref) { ref.picture_id = VA_INVALID_ID; });

    pps.last_picture            = 0;
    pps.pic_init_qp             = (mfxU8)(bs_pps.init_qp_minus26 + 26);
    pps.diff_cu_qp_delta_depth  = (mfxU8)bs_pps.diff_cu_qp_delta_depth;
    pps.pps_cb_qp_offset        = (mfxU8)bs_pps.cb_qp_offset;
    pps.pps_cr_qp_offset        = (mfxU8)bs_pps.cr_qp_offset;
    pps.num_tile_columns_minus1 = (mfxU8)bs_pps.num_tile_columns_minus1;
    pps.num_tile_rows_minus1    = (mfxU8)bs_pps.num_tile_rows_minus1;

    auto NumTileDdiToVa = [](mfxU16 n) { return (uint8_t)std::max<int>(0, n - 1); };

    std::transform(
        bs_pps.column_width
        , bs_pps.column_width + pps.num_tile_columns_minus1 + 1u
        , pps.column_width_minus1
        , NumTileDdiToVa);
    std::transform(
        bs_pps.row_height
        , bs_pps.row_height + pps.num_tile_rows_minus1 + 1u
        , pps.row_height_minus1
        , NumTileDdiToVa);

    pps.log2_parallel_merge_level_minus2     = (mfxU8)bs_pps.log2_parallel_merge_level_minus2;
    pps.ctu_max_bitsize_allowed              = 0;
    pps.num_ref_idx_l0_default_active_minus1 = (mfxU8)bs_pps.num_ref_idx_l0_default_active_minus1;
    pps.num_ref_idx_l1_default_active_minus1 = (mfxU8)bs_pps.num_ref_idx_l1_default_active_minus1;
    pps.slice_pic_parameter_set_id           = 0;

    pps.pic_fields.bits.dependent_slice_segments_enabled_flag      = bs_pps.dependent_slice_segments_enabled_flag;
    pps.pic_fields.bits.sign_data_hiding_enabled_flag              = bs_pps.sign_data_hiding_enabled_flag;
    pps.pic_fields.bits.constrained_intra_pred_flag                = bs_pps.constrained_intra_pred_flag;
    pps.pic_fields.bits.transform_skip_enabled_flag                = bs_pps.transform_skip_enabled_flag;
    pps.pic_fields.bits.cu_qp_delta_enabled_flag                   = bs_pps.cu_qp_delta_enabled_flag;
    pps.pic_fields.bits.weighted_pred_flag                         = bs_pps.weighted_pred_flag;
    pps.pic_fields.bits.weighted_bipred_flag                       = bs_pps.weighted_bipred_flag;
    pps.pic_fields.bits.transquant_bypass_enabled_flag             = bs_pps.transquant_bypass_enabled_flag;
    pps.pic_fields.bits.tiles_enabled_flag                         = bs_pps.tiles_enabled_flag;
    pps.pic_fields.bits.entropy_coding_sync_enabled_flag           = bs_pps.entropy_coding_sync_enabled_flag;
    pps.pic_fields.bits.loop_filter_across_tiles_enabled_flag      = bs_pps.loop_filter_across_tiles_enabled_flag;
    pps.pic_fields.bits.pps_loop_filter_across_slices_enabled_flag = bs_pps.loop_filter_across_slices_enabled_flag;
    pps.pic_fields.bits.scaling_list_data_present_flag             = bs_pps.scaling_list_data_present_flag;
    pps.pic_fields.bits.screen_content_flag                        = 0;
    pps.pic_fields.bits.enable_gpu_weighted_prediction             = 0;
    pps.pic_fields.bits.no_output_of_prior_pics_flag               = 0;
}

void InitSSH(
    const ExtBuffer::Param<mfxVideoParam>& /*par*/
    , Glob::SliceInfo::TRef sinfo
    , std::vector<VAEncSliceParameterBufferHEVC> & slices)
{
    slices.resize(sinfo.size());
    std::transform(sinfo.begin(), sinfo.end(), slices.begin()
        , [](const SliceInfo& si)
    {
        VAEncSliceParameterBufferHEVC s = {};
        s.slice_segment_address = si.SegmentAddress;
        s.num_ctu_in_slice = si.NumLCU;
        return s;
    });

    if (!slices.empty())
        slices.rbegin()->slice_fields.bits.last_slice_of_pic_flag = 1;
}

void AddVaMiscHRD(
    const Glob::VideoParam::TRef& par
    , std::list<std::vector<mfxU8>>& buf)
{
    auto& hrd = AddVaMisc<VAEncMiscParameterHRD>(VAEncMiscParameterTypeHRD, buf);

    hrd.initial_buffer_fullness = InitialDelayInKB(par.mfx) * 8000;
    hrd.buffer_size             = BufferSizeInKB(par.mfx) * 8000;
}

void AddVaMiscRC(
    const Glob::VideoParam::TRef& par
    , const Glob::PPS::TRef& bs_pps
    , const Task::Common::TRef & task
    , std::list<std::vector<mfxU8>>& buf
    , bool bResetBRC = false)
{
    auto& rc = AddVaMisc<VAEncMiscParameterRateControl>(VAEncMiscParameterTypeRateControl, buf);

    uint32_t bNeedRateParam =
           par.mfx.RateControlMethod != MFX_RATECONTROL_CQP
        && par.mfx.RateControlMethod != MFX_RATECONTROL_ICQ;

    rc.bits_per_second = bNeedRateParam * MaxKbps(par.mfx) * 1000;

    if (rc.bits_per_second)
        rc.target_percentage = uint32_t(100.0 * (mfxF64)TargetKbps(par.mfx) / (mfxF64)MaxKbps(par.mfx));

    rc.rc_flags.bits.reset = bNeedRateParam && bResetBRC;

    const mfxExtCodingOption2& CO2 = ExtBuffer::Get(par);

#ifdef PARALLEL_BRC_support
    rc.rc_flags.bits.enable_parallel_brc =
        bNeedRateParam
        && (par.AsyncDepth > 1)
        && (par.mfx.GopRefDist > 1)
        && (par.mfx.GopRefDist <= 8)
        && (CO2.BRefType == MFX_B_REF_PYRAMID);
#endif //PARALLEL_BRC_support

    rc.ICQ_quality_factor = uint32_t((par.mfx.RateControlMethod == MFX_RATECONTROL_ICQ) * par.mfx.ICQQuality);
    rc.initial_qp = bs_pps.init_qp_minus26 + 26;

    //  MBBRC control
    // Control VA_RC_MB 0: default, 1: enable, 2: disable, other: reserved
    rc.rc_flags.bits.mb_rate_control = IsOn(CO2.MBBRC) + IsOff(CO2.MBBRC) * 2;

    // TCBRC control
#if VA_CHECK_VERSION(1, 10, 0)
    rc.target_frame_size = task.TCBRCTargetFrameSize;
#endif

    if (IsOn(par.mfx.LowPower) && (par.mfx.RateControlMethod != MFX_RATECONTROL_CQP))
    {
        const mfxExtCodingOption3& CO3 = ExtBuffer::Get(par);
        rc.min_qp = task.m_minQP - 6*(CO3.TargetBitDepthLuma - 8);
        if (rc.min_qp > 51)
            rc.min_qp = 10;

        rc.max_qp = task.m_maxQP - 6*(CO3.TargetBitDepthLuma - 8);
        if (rc.max_qp > 51)
            rc.max_qp = 51;
    }


#ifdef MFX_ENABLE_QVBR
    const mfxExtCodingOption3& CO3 = ExtBuffer::Get(par);
    if (CO3.WinBRCSize)
    {
        rc.rc_flags.bits.frame_tolerance_mode = 1; //sliding window
        rc.window_size = 1000;
        rc.bits_per_second = CO3.WinBRCMaxAvgKbps * 1000;
        rc.target_percentage = uint32_t(100.0 * (mfxF64)TargetKbps(par.mfx) / (mfxF64)CO3.WinBRCMaxAvgKbps);
    }

    rc.quality_factor = uint32_t((par.mfx.RateControlMethod == MFX_RATECONTROL_QVBR) * CO3.QVBRQuality);
#endif
}

void AddVaMiscPBRC(
    const Glob::VideoParam::TRef& par
    , std::list<std::vector<mfxU8>>& buf)
{
#ifdef PARALLEL_BRC_support
    auto& pbrc = AddVaMisc<VAEncMiscParameterParallelRateControl>(
        VAEncMiscParameterTypeParallelBRC, buf);
    const mfxExtCodingOption2& CO2 = ExtBuffer::Get(par);

    if (CO2.BRefType != MFX_B_REF_PYRAMID)
    {
        pbrc.num_b_in_gop[0] = par.mfx.GopRefDist - 1;
        pbrc.num_b_in_gop[1] = 0;
        pbrc.num_b_in_gop[2] = 0;

        return;
    }

    if (par.mfx.GopRefDist <= 8)
    {
        static uint32_t B[9] = { 0,0,1,1,1,1,1,1,1 };
        static uint32_t B1[9] = { 0,0,0,1,2,2,2,2,2 };
        static uint32_t B2[9] = { 0,0,0,0,0,1,2,3,4 };

        mfxI32 numBpyr = par.mfx.GopPicSize / par.mfx.GopRefDist;
        mfxI32 lastBpyrW = par.mfx.GopPicSize%par.mfx.GopRefDist;

        pbrc.num_b_in_gop[0] = numBpyr * B[par.mfx.GopRefDist] + B[lastBpyrW];;
        pbrc.num_b_in_gop[1] = numBpyr * B1[par.mfx.GopRefDist] + B1[lastBpyrW];
        pbrc.num_b_in_gop[2] = numBpyr * B2[par.mfx.GopRefDist] + B2[lastBpyrW];

        return;
    }

    pbrc.num_b_in_gop[0] = 0;
    pbrc.num_b_in_gop[1] = 0;
    pbrc.num_b_in_gop[2] = 0;
#else
    std::ignore = par;
    std::ignore = buf;
#endif //PARALLEL_BRC_support
}

void AddVaMiscFR(
    const Glob::VideoParam::TRef& par
    , std::list<std::vector<mfxU8>>& buf)
{
    auto& fr = AddVaMisc<VAEncMiscParameterFrameRate>(VAEncMiscParameterTypeFrameRate, buf);

    PackMfxFrameRate(par.mfx.FrameInfo.FrameRateExtN, par.mfx.FrameInfo.FrameRateExtD, fr.framerate);
}

void AddVaMiscTU(
    const Glob::VideoParam::TRef& par
    , std::list<std::vector<mfxU8>>& buf)
{
    auto& tu = AddVaMisc<VAEncMiscParameterBufferQualityLevel>(VAEncMiscParameterTypeQualityLevel, buf);

    tu.quality_level = par.mfx.TargetUsage;
}

void AddVaMiscQualityParams(
    const Glob::VideoParam::TRef& par
    , std::list<std::vector<mfxU8>>& buf)
{
    const mfxExtCodingOption3& CO3 = ExtBuffer::Get(par);
    auto& quality_param = AddVaMisc<VAEncMiscParameterEncQuality>(VAEncMiscParameterTypeEncQuality, buf);

    quality_param.PanicModeDisable = IsOff(CO3.BRCPanicMode);
}

void AddVaMiscMaxSliceSize(
    const Glob::VideoParam::TRef& par
    , std::list<std::vector<mfxU8>>& buf)
{
    const mfxExtCodingOption2& CO2 = ExtBuffer::Get(par);
    auto& maxSliceSize_param = AddVaMisc<VAEncMiscParameterMaxSliceSize>(VAEncMiscParameterTypeMaxSliceSize, buf);

    maxSliceSize_param.max_slice_size = CO2.MaxSliceSize;
}


void UpdatePPS(
    const TaskCommonPar & task
    , const Slice & sh
    , const std::vector<VASurfaceID> & rec
    , VAEncPictureParameterBufferHEVC & pps)
{
    pps.pic_fields.bits.idr_pic_flag = !!(task.FrameType & MFX_FRAMETYPE_IDR);
    pps.pic_fields.bits.coding_type = task.CodingType;
    pps.pic_fields.bits.reference_pic_flag = !!(task.FrameType & MFX_FRAMETYPE_REF);

    pps.collocated_ref_pic_index = 0xff;
    if (sh.temporal_mvp_enabled_flag)
        pps.collocated_ref_pic_index = task.RefPicList[!sh.collocated_from_l0_flag][sh.collocated_ref_idx];

    pps.decoded_curr_pic.picture_id     = rec.at(task.Rec.Idx);
    pps.decoded_curr_pic.pic_order_cnt  = task.POC;
    pps.decoded_curr_pic.flags          = 0;
    pps.nal_unit_type                   = task.SliceNUT;

    auto pDpbBegin = task.DPB.Active;
    auto pDpbEnd = task.DPB.Active + mfx::size(task.DPB.Active);
    auto pDpbValidEnd = std::find_if(pDpbBegin, pDpbEnd
        , [](decltype(*pDpbBegin) ref) { return ref.Rec.Idx == IDX_INVALID; });

    std::transform(pDpbBegin, pDpbValidEnd, pps.reference_frames
        , [&](decltype(*pDpbBegin) ref)
    {
        VAPictureHEVC vaRef = {};
        vaRef.picture_id = rec.at(ref.Rec.Idx);
        vaRef.pic_order_cnt = ref.POC;
        vaRef.flags = VA_PICTURE_HEVC_LONG_TERM_REFERENCE * !!ref.isLTR;
        return vaRef;
    });

    VAPictureHEVC vaRefInvalid = {};
    vaRefInvalid.picture_id = VA_INVALID_SURFACE;
    vaRefInvalid.flags = VA_PICTURE_HEVC_INVALID;

    std::fill(
        pps.reference_frames + (pDpbValidEnd - pDpbBegin)
        , pps.reference_frames + mfx::size(pps.reference_frames)
        , vaRefInvalid);
}

void VAPacker::InitInternal(const FeatureBlocks& /*blocks*/, TPushII Push)
{
    Push(BLK_SetCallChains
        , [this](StorageRW& strg, StorageRW&) -> mfxStatus
    {
        MFX_CHECK(!strg.Contains(CC::Key), MFX_ERR_NONE);

        const auto& par = Glob::VideoParam::Get(strg);
        auto& cc = CC::GetOrConstruct(strg);

        cc.InitSPS.Push([&par](
            CallChains::TInitSPS::TExt
            , const StorageR& glob
            , VAEncSequenceParameterBufferHEVC& sps)
        {
            const auto& bs_sps = Glob::SPS::Get(glob);
            InitSPS(par, bs_sps, sps);
        });
        cc.UpdatePPS.Push([this](
            CallChains::TUpdatePPS::TExt
            , const StorageR& global
            , const StorageR& s_task
            , const VAEncSequenceParameterBufferHEVC& /*sps*/
            , VAEncPictureParameterBufferHEVC& pps)
        {
            UpdatePPS(Task::Common::Get(s_task), Task::SSH::Get(s_task), GetResources(RES_REF), pps);
        });

        return MFX_ERR_NONE;
    });
}

void VAPacker::InitAlloc(const FeatureBlocks& /*blocks*/, TPushIA Push)
{
    Push(BLK_CreateService
        , [this](StorageRW& strg, StorageRW& local)->mfxStatus
    {
        const auto& par = Glob::VideoParam::Get(strg);
        const auto& caps = Glob::EncodeCaps::Get(strg);
       
        if (Legacy::GetMBQPMode(caps, par))
        {
            const mfxExtHEVCParam& HEVCPar = ExtBuffer::Get(par);
            auto pInfo = make_storable<MBQPAllocFrameInfo>(MBQPAllocFrameInfo{});
            mfxU32 blkSz = 8 << caps.BlockSize;
            pInfo->width  = (HEVCPar.PicWidthInLumaSamples  + blkSz - 1) / blkSz;
            pInfo->height = (HEVCPar.PicHeightInLumaSamples + blkSz - 1) / blkSz;
            pInfo->pitch = mfx::align2_value(pInfo->width, 64);
            pInfo->height_aligned = mfx::align2_value(pInfo->height, 4);
            pInfo->block_width = blkSz;
            pInfo->block_height = blkSz;
            pInfo->Info.Height = (mfxU16) pInfo->height_aligned;
            pInfo->Info.Width = (mfxU16)pInfo->pitch;
            pInfo->Info.FourCC = MFX_FOURCC_P8;
            pInfo->Type = (MFX_MEMTYPE_FROM_ENCODE
                | MFX_MEMTYPE_SYSTEM_MEMORY
                | MFX_MEMTYPE_INTERNAL_FRAME);

            strg.Insert(Glob::MBQPAllocInfo::Key, std::move(pInfo));
        } 
        return MFX_ERR_NONE;
    });
    Push(BLK_Init
        , [this](StorageRW& strg, StorageRW& local) -> mfxStatus
    {
        auto& core = Glob::VideoCore::Get(strg);

        const auto& par     = Glob::VideoParam::Get(strg);
        const auto& bs_pps  = Glob::PPS::Get(strg);
        const mfxExtCodingOption2& CO2 = ExtBuffer::Get(par);

        auto& cc = CC::GetOrConstruct(strg);
        cc.InitSPS(strg, m_sps);

        InitPPS(par, bs_pps, m_pps);
        InitSSH(par, Glob::SliceInfo::Get(strg), m_slices);
        if (par.mfx.RateControlMethod != MFX_RATECONTROL_CQP &&
            par.mfx.RateControlMethod != MFX_RATECONTROL_ICQ) {
            cc.AddPerSeqMiscData[VAEncMiscParameterTypeHRD].Push([this, &par](
                VAPacker::CallChains::TAddMiscData::TExt
                , const StorageR& strg
                , const StorageR& local
                , std::list<std::vector<mfxU8>>& data)
            {
                AddVaMiscHRD(par, m_vaPerSeqMiscData);
                return true;
            });
        }
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeParallelBRC].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscPBRC(par, m_vaPerSeqMiscData);
            return true;
        });
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeFrameRate].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscFR(par, m_vaPerSeqMiscData);
            return true;
        });
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeQualityLevel].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscTU(par, m_vaPerSeqMiscData);
            return true;
        });
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeEncQuality].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscQualityParams(par, m_vaPerSeqMiscData);
            return true;
        });
        if (CO2.MaxSliceSize)
        {
	        cc.AddPerSeqMiscData[VAEncMiscParameterTypeMaxSliceSize].Push([this, &par](
		    VAPacker::CallChains::TAddMiscData::TExt
		    , const StorageR&
		    , const StorageR&
		    , std::list<std::vector<mfxU8>>& data)
	        {
		        AddVaMiscMaxSliceSize(par, m_vaPerSeqMiscData);
		        return true;
	        });
        }

        auto& vaInitPar = Tmp::DDI_InitParam::GetOrConstruct(local);
        auto& bsInfo    = Glob::AllocBS::Get(strg);

        SetMaxBs(bsInfo.GetInfo().Width * bsInfo.GetInfo().Height);

        for (auto& AddMisc : cc.AddPerSeqMiscData)
        {
            if (AddMisc.second(strg, local, m_vaPerSeqMiscData))
            {
                auto& misc = m_vaPerSeqMiscData.back();
                vaInitPar.push_back(PackVaMiscPar(misc));
            }
        }

        const auto& caps = Glob::EncodeCaps::Get(strg);

        mfxStatus sts = Register(core, Glob::AllocRec::Get(strg).GetResponse(), RES_REF);
        MFX_CHECK_STS(sts);

        sts = Register(core, bsInfo.GetResponse(), RES_BS);
        MFX_CHECK_STS(sts)

        auto& resources = Glob::DDI_Resources::GetOrConstruct(strg);
        DDIExecParam xPar;

        xPar.Function = MFX_FOURCC_NV12;
        PackResources(xPar.Resource, RES_REF);
        resources.push_back(xPar);

        xPar.Function = MFX_FOURCC_P8;
        PackResources(xPar.Resource, RES_BS);
        resources.push_back(xPar);

        InitFeedback(bsInfo.GetResponse().NumFrameActual);

        GetFeedbackInterface(Glob::DDI_Feedback::GetOrConstruct(strg));

        Glob::DDI_SubmitParam::GetOrConstruct(strg);

        cc.ReadFeedback.Push([this](
                CallChains::TReadFeedback::TExt
                , const StorageR& /*global*/
                , StorageW& s_task
                , const VACodedBufferSegment& fb)
        {
            return ReadFeedback(fb, Task::Common::Get(s_task).BsDataLength);
        });

        cc.AddPerPicMiscData[VAEncMiscParameterTypeSkipFrame].Push([this](
            CallChains::TAddMiscData::TExt
            , const StorageR&
            , const StorageR& s_task
            , std::list<std::vector<mfxU8>>& data)
        {
            if (Task::Common::Get(s_task).SkipCMD & SKIPCMD_NeedNumSkipAdding)
            {
                auto& sf = AddVaMisc<VAEncMiscParameterSkipFrame>(VAEncMiscParameterTypeSkipFrame, data);
                sf.num_skip_frames  = uint8_t(m_numSkipFrames);
                sf.size_skip_frames = m_sizeSkipFrames;
                sf.skip_frame_flag  = !!m_numSkipFrames;

                return true;
            }

            return false;
        });
	cc.AddPerPicMiscData[VAEncMiscParameterTypeRateControl].Push([this](
            CallChains::TAddMiscData::TExt
            , const StorageR& global
            , const StorageR& s_task
            , std::list<std::vector<mfxU8>>& data)
        {
            const auto& par    = Glob::VideoParam::Get(global);
            const auto& bs_pps = Glob::PPS::Get(global);
            auto& task = Task::Common::Get(s_task);

            AddVaMiscRC(par, bs_pps, task, data, !!(m_resetHintFlags & RF_BRC_RESET));
            return true;
        });
	cc.AddPerPicMiscData[VAEncMiscParameterTypeRIR].Push([](
                VAPacker::CallChains::TAddMiscData::TExt
                , const StorageR&
                , const StorageR& s_task
                , std::list<std::vector<mfxU8>>& data)
        {

	     auto& task = Task::Common::Get(s_task);

	     if (task.IRState.refrType)
	     {
                 auto& vaRIR = AddVaMisc<VAEncMiscParameterRIR>(VAEncMiscParameterTypeRIR, data);
                 vaRIR.rir_flags.value = task.IRState.refrType;
                 vaRIR.intra_insertion_location = task.IRState.IntraLocation;
                 vaRIR.intra_insert_size = task.IRState.IntraSize;
                 vaRIR.qp_delta_for_inserted_intra = mfxU8(task.IRState.IntRefQPDelta);
                 return true;
	     }

	     return false;
        });

        return MFX_ERR_NONE;
    });
}

void VAPacker::ResetState(const FeatureBlocks& /*blocks*/, TPushRS Push)
{
    Push(BLK_Reset
        , [this](StorageRW& strg, StorageRW& local) -> mfxStatus
    {
        const auto& par = Glob::VideoParam::Get(strg);
        const auto& bs_pps = Glob::PPS::Get(strg);
        const mfxExtCodingOption2& CO2 = ExtBuffer::Get(par);

        auto& cc = CC::GetOrConstruct(strg);
        cc.InitSPS(strg, m_sps);

        const auto& reset_hint = Glob::ResetHint::Get(strg);
        m_resetHintFlags = reset_hint.Flags;

        InitPPS(par, bs_pps, m_pps);
        InitSSH(par, Glob::SliceInfo::Get(strg), m_slices);

        m_vaPerSeqMiscData.clear();

        if (par.mfx.RateControlMethod != MFX_RATECONTROL_CQP &&
            par.mfx.RateControlMethod != MFX_RATECONTROL_ICQ) {
            cc.AddPerSeqMiscData[VAEncMiscParameterTypeHRD].Push([this, &par](
                VAPacker::CallChains::TAddMiscData::TExt
                , const StorageR& strg
                , const StorageR& local
                , std::list<std::vector<mfxU8>>& data)
            {
                AddVaMiscHRD(par, m_vaPerSeqMiscData);
                return true;
            });
        }
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeParallelBRC].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscPBRC(par, m_vaPerSeqMiscData);
            return true;
        });
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeFrameRate].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscFR(par, m_vaPerSeqMiscData);
            return true;
        });
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeQualityLevel].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscTU(par, m_vaPerSeqMiscData);
            return true;
        });
        cc.AddPerSeqMiscData[VAEncMiscParameterTypeEncQuality].Push([this, &par](
            VAPacker::CallChains::TAddMiscData::TExt
            , const StorageR& strg
            , const StorageR& local
            , std::list<std::vector<mfxU8>>& data)
        {
            AddVaMiscQualityParams(par, m_vaPerSeqMiscData);
            return true;
        });
        if (CO2.MaxSliceSize)
        {
	        cc.AddPerSeqMiscData[VAEncMiscParameterTypeMaxSliceSize].Push([this, &par](
		    VAPacker::CallChains::TAddMiscData::TExt
		    , const StorageR&
		    , const StorageR&
		    , std::list<std::vector<mfxU8>>& data)
	        {
		        AddVaMiscMaxSliceSize(par, m_vaPerSeqMiscData);
		        return true;
	        });
        }

        auto& vaInitPar = Tmp::DDI_InitParam::GetOrConstruct(local);
        vaInitPar.clear();

        for (auto& AddMisc : cc.AddPerSeqMiscData)
        {
            if (AddMisc.second(strg, local, m_vaPerSeqMiscData))
            {
                auto& misc = m_vaPerSeqMiscData.back();
                vaInitPar.push_back(PackVaMiscPar(misc));
            }
        }

        return MFX_ERR_NONE;
    });
}

void UpdateSlices(
    const TaskCommonPar & task
    , const Slice & sh
    , const VAEncPictureParameterBufferHEVC & pps
    , std::vector<VAEncSliceParameterBufferHEVC> & slices)
{
    std::for_each(slices.begin(), slices.end()
        , [&](VAEncSliceParameterBufferHEVC& slice)
    {
        slice.slice_type                 = sh.type;
        slice.slice_pic_parameter_set_id = pps.slice_pic_parameter_set_id;

        if (slice.slice_type != 2)
        {
            slice.num_ref_idx_l0_active_minus1 = task.NumRefActive[0]==0 ? 0 : task.NumRefActive[0] - 1;

            if (slice.slice_type == 0)
                slice.num_ref_idx_l1_active_minus1 = task.NumRefActive[1]==0 ? 0 : task.NumRefActive[1] - 1;
            else
                slice.num_ref_idx_l1_active_minus1 = 0;

        }
        else
            slice.num_ref_idx_l0_active_minus1 = slice.num_ref_idx_l1_active_minus1 = 0;


        VAPictureHEVC vaRefInvalid = {};
        vaRefInvalid.picture_id = VA_INVALID_SURFACE;
        vaRefInvalid.flags      = VA_PICTURE_HEVC_INVALID;

        auto GetRef = [&](mfxU8 idx)
        {
            if (idx < mfx::size(pps.reference_frames))
                return pps.reference_frames[idx];
            return vaRefInvalid;
        };

        std::fill(slice.ref_pic_list0, slice.ref_pic_list0 + mfx::size(slice.ref_pic_list0), vaRefInvalid);
        std::fill(slice.ref_pic_list1, slice.ref_pic_list1 + mfx::size(slice.ref_pic_list1), vaRefInvalid);

        std::transform(
            task.RefPicList[0]
            , task.RefPicList[0] + task.NumRefActive[0]
            , slice.ref_pic_list0
            , GetRef);
        std::transform(
            task.RefPicList[1]
            , task.RefPicList[1] + task.NumRefActive[1]
            , slice.ref_pic_list1
            , GetRef);

        slice.max_num_merge_cand     = 5 - sh.five_minus_max_num_merge_cand;
        slice.slice_qp_delta         = sh.slice_qp_delta;
        slice.slice_cb_qp_offset     = sh.slice_cb_qp_offset;
        slice.slice_cr_qp_offset     = sh.slice_cr_qp_offset;
        slice.slice_beta_offset_div2 = sh.beta_offset_div2;
        slice.slice_tc_offset_div2   = sh.tc_offset_div2;

        slice.slice_fields.bits.dependent_slice_segment_flag          = sh.dependent_slice_segment_flag;
        slice.slice_fields.bits.slice_temporal_mvp_enabled_flag       = sh.temporal_mvp_enabled_flag;
        slice.slice_fields.bits.slice_sao_luma_flag                   = sh.sao_luma_flag;
        slice.slice_fields.bits.slice_sao_chroma_flag                 = sh.sao_chroma_flag;
        slice.slice_fields.bits.num_ref_idx_active_override_flag =
                slice.num_ref_idx_l0_active_minus1 != pps.num_ref_idx_l0_default_active_minus1 ||
                slice.num_ref_idx_l1_active_minus1 != pps.num_ref_idx_l1_default_active_minus1;
        slice.slice_fields.bits.mvd_l1_zero_flag                      = sh.mvd_l1_zero_flag;
        slice.slice_fields.bits.cabac_init_flag                       = sh.cabac_init_flag;
        slice.slice_fields.bits.slice_deblocking_filter_disabled_flag = sh.deblocking_filter_disabled_flag;
        slice.slice_fields.bits.collocated_from_l0_flag               = sh.collocated_from_l0_flag;
    });
}

void VAPacker::SubmitTask(const FeatureBlocks& /*blocks*/, TPushST Push)
{
    Push(BLK_SubmitTask
        , [this](StorageW& global, StorageW& s_task) -> mfxStatus
    {
        auto& core = Glob::VideoCore::Get(global);
        auto& task      = Task::Common::Get(s_task);
        bool  bSkipCurr =
            !(task.SkipCMD & SKIPCMD_NeedDriverCall)
            && (task.SkipCMD & SKIPCMD_NeedCurrentFrameSkipping);

        m_numSkipFrames  += !!(task.SkipCMD & SKIPCMD_NeedNumSkipAdding) * task.ctrl.SkipFrame;
        m_numSkipFrames  += bSkipCurr;
        m_sizeSkipFrames += bSkipCurr * task.BsDataLength;

        MFX_CHECK(task.SkipCMD & SKIPCMD_NeedDriverCall, MFX_ERR_NONE);

        auto& sh = Task::SSH::Get(s_task);
        auto& ph = Glob::PackedHeaders::Get(global);

        if (task.RepackHeaders & INSERT_PPS)
        {
            InitPPS(Glob::VideoParam::Get(global), Glob::PPS::Get(global), m_pps);
        }

        auto& cc = CC::Get(global);
        if (cc.UpdateSPS)
        {
            cc.UpdateSPS(global, m_sps);
        }
        cc.UpdatePPS(global, s_task, m_sps, m_pps);
        UpdateSlices(task, sh, m_pps, m_slices);

        m_pps.coded_buf = GetResources(RES_BS).at(task.BS.Idx);

        auto& par = Glob::DDI_SubmitParam::Get(global);
        par.clear();
        
        par.push_back(PackVaBuffer(VAEncSequenceParameterBufferType, m_sps));
        par.push_back(PackVaBuffer(VAEncPictureParameterBufferType, m_pps));

        if(m_sps.seq_fields.bits.scaling_list_enabled_flag)
        {
            m_qm = {};
            par.push_back(PackVaBuffer(VAQMatrixBufferType, m_qm));
        }

        std::transform(m_slices.begin(), m_slices.end(), std::back_inserter(par)
            , [&](VAEncSliceParameterBufferHEVC& s) { return PackVaBuffer(VAEncSliceParameterBufferType, s); });

        if (task.CUQP.Idx != IDX_INVALID && task.bCUQPMap)
        {
            FrameLocker lock(core, task.CUQP.Mid);
            MFX_CHECK(lock.Y, MFX_ERR_LOCK_MEMORY);
            auto& req = Glob::MBQPAllocInfo::Get(global);

            par.push_back(PackVaBuffer(
                VAEncQPBufferType
                , lock.Y
                , req.pitch
                , req.height_aligned));
        }

        m_vaPerPicMiscData.clear();
        m_vaPackedHeaders.clear();

        AddPackedHeaderIf(!!(task.InsertHeaders & INSERT_AUD)
            , ph.AUD[mfx::clamp<mfxU8>(task.CodingType, 1, 3) - 1], par);

        AddPackedHeaderIf(!!(task.InsertHeaders & INSERT_VPS)
            , ph.VPS, par, VAEncPackedHeaderHEVC_VPS);

        AddPackedHeaderIf(!!(task.InsertHeaders & INSERT_SPS)
            , ph.SPS, par, VAEncPackedHeaderHEVC_SPS);

        AddPackedHeaderIf(!!(task.InsertHeaders & INSERT_PPS)
            , ph.PPS, par, VAEncPackedHeaderHEVC_PPS);
#if defined(MFX_ENABLE_ENCTOOLS_SW)
        if (cc.PackETSWAdaptiveCqmPPS && cc.PackETSWAdaptiveCqmPPS(global, s_task))
        {
            for (mfxU32 i = 0; i < ET_CQM_NUM_CUST_MATRIX; ++i)
            {
                AddPackedHeaderIf(true, ph.CqmPPS.at(i), par, VAEncPackedHeaderHEVC_PPS);
            }
        }
#endif

        AddPackedHeaderIf((!!(task.InsertHeaders & INSERT_SEI) || task.ctrl.NumPayload) && ph.PrefixSEI.BitLen
            , ph.PrefixSEI, par/*, VAEncPackedHeaderHEVC_SEI*/);

        std::for_each(
            ph.SSH.begin()
            , ph.SSH.begin() + std::min<size_t>(m_slices.size(), ph.SSH.size())
            , [&](PackedData& pd)
        {
            AddPackedHeaderIf(true, pd, par, VAEncPackedHeaderHEVC_Slice);
        });

        for (auto& AddMisc : cc.AddPerPicMiscData)
        {
            if (AddMisc.second(global, s_task, m_vaPerPicMiscData))
            {
                auto& misc = m_vaPerPicMiscData.back();
                par.push_back(PackVaBuffer(VAEncMiscParameterBufferType, misc.data(), (mfxU32)misc.size()));
            }
        }

        SetFeedback(task.StatusReportId, *(VASurfaceID*)task.HDLRaw.first, GetResources(RES_BS).at(task.BS.Idx));

        m_numSkipFrames  = 0;
        m_sizeSkipFrames = 0;

        return MFX_ERR_NONE;
    });
}

void VAPacker::QueryTask(const FeatureBlocks& /*blocks*/, TPushQT Push)
{
    Push(BLK_QueryTask
        , [](StorageW& global, StorageW& s_task) -> mfxStatus
    {
        auto& fb = Glob::DDI_Feedback::Get(global);
        MFX_CHECK(!fb.bNotReady, MFX_TASK_BUSY);

        auto& task = Task::Common::Get(s_task);

        if (!(task.SkipCMD & SKIPCMD_NeedDriverCall))
            return MFX_ERR_NONE;

        auto pFB = fb.Get(task.StatusReportId);
        MFX_CHECK(pFB, MFX_TASK_BUSY);

        auto& rtErr = Glob::RTErr::Get(global);

        auto sts = CC::Get(global).ReadFeedback(global, s_task, *(const VACodedBufferSegment*)pFB);
        SetIf(rtErr, sts < MFX_ERR_NONE, sts);

        if (CC::Get(global).UpdateEncQP)
        {
            CC::Get(global).UpdateEncQP(global, task, (((const VACodedBufferSegment*)pFB)->status & VA_CODED_BUF_STATUS_PICTURE_AVE_QP_MASK));
        }

#if defined(MFX_ENABLE_LPLA_BASE)
        auto& cc = CC::Get(global);
        if (cc.UpdateCqmHint)
        {
            cc.UpdateCqmHint(task, *(const VACodedBufferSegment*)pFB);
        }
#endif

        fb.Remove(task.StatusReportId);

        return sts;
    });
}

#endif //defined(MFX_ENABLE_H265_VIDEO_ENCODE)
