// Copyright (c) 2020-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"
#if defined(MFX_ENABLE_H265_VIDEO_ENCODE)
#if defined(MFX_ENABLE_ENCTOOLS)

#include "hevcehw_base.h"
#include "hevcehw_base_data.h"
#include "hevcehw_base_enctools.h"
#include "hevcehw_base_task.h"
#include "hevcehw_base_legacy.h"
#include <climits>

using namespace HEVCEHW;
using namespace HEVCEHW::Base;

void HevcEncTools::SetSupported(ParamSupport& blocks)
{
    blocks.m_ebCopySupported[MFX_EXTBUFF_ENCTOOLS_CONFIG].emplace_back(
        [](const mfxExtBuffer* pSrc, mfxExtBuffer* pDst) -> void
    {
        auto& buf_src = *(const mfxExtEncToolsConfig*)pSrc;
        auto& buf_dst = *(mfxExtEncToolsConfig*)pDst;

        MFX_COPY_FIELD(AdaptiveI);
        MFX_COPY_FIELD(AdaptiveB);
        MFX_COPY_FIELD(AdaptiveLTR);
        MFX_COPY_FIELD(AdaptivePyramidQuantB);
        MFX_COPY_FIELD(AdaptivePyramidQuantP);
        MFX_COPY_FIELD(AdaptiveQuantMatrices);
        MFX_COPY_FIELD(AdaptiveRefB);
        MFX_COPY_FIELD(AdaptiveRefP);
        MFX_COPY_FIELD(BRC);
        MFX_COPY_FIELD(BRCBufferHints);
        MFX_COPY_FIELD(SceneChange);
    });
    blocks.m_ebCopySupported[MFX_EXTBUFF_CODING_OPTION2].emplace_back(
        [](const mfxExtBuffer* pSrc, mfxExtBuffer* pDst) -> void
    {
        const auto& buf_src = *(const mfxExtCodingOption2*)pSrc;
        auto& buf_dst = *(mfxExtCodingOption2*)pDst;
        MFX_COPY_FIELD(AdaptiveI);
        MFX_COPY_FIELD(AdaptiveB);
        MFX_COPY_FIELD(LookAheadDepth);
    });
    blocks.m_ebCopySupported[MFX_EXTBUFF_CODING_OPTION3].emplace_back(
        [](const mfxExtBuffer* pSrc, mfxExtBuffer* pDst) -> void
    {
        const auto& buf_src = *(const mfxExtCodingOption3*)pSrc;
        auto& buf_dst = *(mfxExtCodingOption3*)pDst;
        MFX_COPY_FIELD(ExtBrcAdaptiveLTR);
    });
}

void HevcEncTools::SetInherited(ParamInheritance& par)
{
    par.m_ebInheritDefault[MFX_EXTBUFF_ENCTOOLS_CONFIG].emplace_back(
        [](const mfxVideoParam& /*parInit*/
            , const mfxExtBuffer* pSrc
            , const mfxVideoParam& /*parReset*/
            , mfxExtBuffer* pDst)
    {
        auto& buf_src = *(const mfxExtEncToolsConfig*)pSrc;
        auto& buf_dst = *(mfxExtEncToolsConfig*)pDst;

        InheritOption(buf_src.AdaptiveI, buf_dst.AdaptiveI);
        InheritOption(buf_src.AdaptiveB, buf_dst.AdaptiveB);
        InheritOption(buf_src.AdaptiveLTR, buf_dst.AdaptiveLTR);
        InheritOption(buf_src.AdaptivePyramidQuantB, buf_dst.AdaptivePyramidQuantB);
        InheritOption(buf_src.AdaptivePyramidQuantP, buf_dst.AdaptivePyramidQuantP);
        InheritOption(buf_src.AdaptiveQuantMatrices, buf_dst.AdaptiveQuantMatrices);
        InheritOption(buf_src.AdaptiveRefB, buf_dst.AdaptiveRefB);
        InheritOption(buf_src.AdaptiveRefP, buf_dst.AdaptiveRefP);
        InheritOption(buf_src.BRC, buf_dst.BRC);
        InheritOption(buf_src.BRCBufferHints, buf_dst.BRCBufferHints);
        InheritOption(buf_src.SceneChange, buf_dst.SceneChange);
    });
}

inline bool IsLookAhead(const mfxExtEncToolsConfig &config, bool bGameStreaming)
{
    if (!bGameStreaming)
        return false;
    return
        (IsOn(config.AdaptiveI)
        || IsOn(config.AdaptiveB)
        || IsOn(config.SceneChange)
        || IsOn(config.AdaptivePyramidQuantP)
        || IsOn(config.BRCBufferHints)
        || IsOn(config.AdaptiveQuantMatrices));
}

inline bool IsAdaptiveGOP(const mfxExtEncToolsConfig &config)
{
    return (IsOn(config.AdaptiveI)
        ||  IsOn(config.AdaptiveB));
}

inline bool IsAdaptiveQP(const mfxExtEncToolsConfig &config)
{
    return
        (IsOn(config.AdaptivePyramidQuantP)
        ||   IsOn(config.AdaptivePyramidQuantB));
}

inline bool IsAdaptiveRef(const mfxExtEncToolsConfig &config)
{
    return
        (IsOn(config.AdaptiveRefP)
        || IsOn(config.AdaptiveRefB));
}

inline bool IsAdaptiveLTR(const mfxExtEncToolsConfig &config)
{
    return IsOn(config.AdaptiveLTR);
}

inline bool IsBRC(const mfxExtEncToolsConfig &config)
{
    return
        IsOn(config.BRC);
}


inline bool IsEncToolsOptSet(const mfxExtEncToolsConfig& config)
{
    return
        (config.AdaptiveB | config.AdaptiveI | config.AdaptiveLTR | config.AdaptivePyramidQuantB
            | config.AdaptivePyramidQuantP | config.AdaptiveQuantMatrices | config.AdaptiveRefB
            | config.AdaptiveRefP | config.BRC | config.BRCBufferHints | config.SceneChange);
}

bool HEVCEHW::Base::IsEncToolsOptOn(const mfxExtEncToolsConfig &config, bool bGameStreaming)
{
    return
        (IsAdaptiveGOP(config)
            || IsAdaptiveQP(config)
            || IsAdaptiveRef(config)
            || IsAdaptiveLTR(config)
            || IsBRC(config)
            || IsLookAhead(config,bGameStreaming));
}

bool HEVCEHW::Base::IsLPLAEncToolsOn(const mfxExtEncToolsConfig &config, bool bGameStreaming)
{
    return IsLookAhead(config, bGameStreaming);
}

inline mfxU32 GetNumTempLayers(const mfxVideoParam &video)
{
   const  mfxExtAvcTemporalLayers* tempLayers = ExtBuffer::Get(video);

    if (tempLayers == nullptr)
        return 0;

    mfxU32 numTemporalLayer = 0;
    if (tempLayers)
    {
        for (mfxU32 i = 0; i < 8; i++)
        {
            if (tempLayers->Layer[i].Scale != 0)
                numTemporalLayer++;
        }
    }
    return numTemporalLayer;
}

inline bool IsAdaptiveRefAllowed(const mfxVideoParam &video)
{
    const mfxExtCodingOption3 *pExtOpt3 = ExtBuffer::Get(video);
    bool adaptiveRef = (video.mfx.TargetUsage != 7) && !(video.mfx.GopOptFlag & MFX_GOP_STRICT);
    if (pExtOpt3)
    {
        adaptiveRef = adaptiveRef && !(pExtOpt3->NumRefActiveP[0] == 1 || (video.mfx.GopRefDist > 1 && pExtOpt3->NumRefActiveBL0[0] == 1));
        adaptiveRef = adaptiveRef && !IsOff(pExtOpt3->ExtBrcAdaptiveLTR);
    }
    return adaptiveRef;
}

inline bool CheckEncToolsCondition(const mfxVideoParam &video)
{
    return ((video.mfx.FrameInfo.PicStruct == 0
        || video.mfx.FrameInfo.PicStruct == MFX_PICSTRUCT_PROGRESSIVE)
        && GetNumTempLayers(video) <= 1);
}

inline bool  CheckSWEncCondition(const mfxVideoParam &video)
{
    return (
        CheckEncToolsCondition(video)
        && (video.mfx.GopRefDist == 0
            || video.mfx.GopRefDist == 1
            || video.mfx.GopRefDist == 2
            || video.mfx.GopRefDist == 4
            || video.mfx.GopRefDist == 8
            || video.mfx.GopRefDist == 16));
}

inline mfxEncTools *GetEncTools(mfxVideoParam &video)
{
    return (mfxEncTools *)mfx::GetExtBuffer(video.ExtParam, video.NumExtParam, MFX_EXTBUFF_ENCTOOLS);
}
inline bool IsGameStreaming(const mfxVideoParam& video)
{
    const mfxExtCodingOption3* pExtOpt3 = ExtBuffer::Get(video);
    return (pExtOpt3 && pExtOpt3->ScenarioInfo == MFX_SCENARIO_GAME_STREAMING);
}

inline bool IsEncToolsImplicit(const mfxVideoParam &video)
{
    const mfxExtCodingOption2  *pExtOpt2 = ExtBuffer::Get(video);
    bool etOn = false;
    if (pExtOpt2 && pExtOpt2->LookAheadDepth > 0)
    {
        const mfxExtCodingOption3* pExtOpt3 = ExtBuffer::Get(video);
        etOn = (IsGameStreaming(video) && IsOn(video.mfx.LowPower)); // LPLA
        etOn = etOn ||
            ((video.mfx.GopRefDist == 2 || video.mfx.GopRefDist == 4 || video.mfx.GopRefDist == 8 || video.mfx.GopRefDist == 16) && IsOn(pExtOpt2->ExtBRC) // SW EncTools
                && !(pExtOpt3 && pExtOpt3->ScenarioInfo != MFX_SCENARIO_UNKNOWN));
    }
    return etOn;
}

bool isSWLACondition(const mfxVideoParam& video)
{
    const mfxExtCodingOption2* pExtOpt2 = ExtBuffer::Get(video);
    return (pExtOpt2 && 
        (pExtOpt2->LookAheadDepth > video.mfx.GopRefDist));
}

static void SetDefaultConfig(const mfxVideoParam &video, mfxExtEncToolsConfig &config, bool bMBQPSupport)
{
    const mfxExtCodingOption2  *pExtOpt2 = ExtBuffer::Get(video);
    const mfxExtCodingOption3  *pExtOpt3 = ExtBuffer::Get(video);
    const mfxExtEncToolsConfig *pExtConfig = ExtBuffer::Get(video);
    bool bGameStreaming = IsGameStreaming(video);

    if (!pExtConfig || !IsEncToolsOptSet(*pExtConfig))
    {
        if (IsEncToolsImplicit(video))
        {
            config.AdaptiveI             = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptiveB             = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptivePyramidQuantP = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptivePyramidQuantB = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptiveRefP          = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptiveRefB          = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptiveLTR           = MFX_CODINGOPTION_UNKNOWN;
            config.BRCBufferHints        = MFX_CODINGOPTION_UNKNOWN;
            config.BRC                   = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptiveQuantMatrices = MFX_CODINGOPTION_UNKNOWN;
            config.SceneChange           = MFX_CODINGOPTION_UNKNOWN;
            config.AdaptiveMBQP          = MFX_CODINGOPTION_UNKNOWN;
        }
        else
        {
            config.AdaptiveI             = MFX_CODINGOPTION_OFF;
            config.AdaptiveB             = MFX_CODINGOPTION_OFF;
            config.AdaptivePyramidQuantP = MFX_CODINGOPTION_OFF;
            config.AdaptivePyramidQuantB = MFX_CODINGOPTION_OFF;
            config.AdaptiveRefP          = MFX_CODINGOPTION_OFF;
            config.AdaptiveRefB          = MFX_CODINGOPTION_OFF;
            config.AdaptiveLTR           = MFX_CODINGOPTION_OFF;
            config.BRCBufferHints        = MFX_CODINGOPTION_OFF;
            config.BRC                   = MFX_CODINGOPTION_OFF;
            config.AdaptiveQuantMatrices = MFX_CODINGOPTION_OFF;
            config.SceneChange           = MFX_CODINGOPTION_OFF;
            config.AdaptiveMBQP          = MFX_CODINGOPTION_OFF;
            return;
        }
    }
    else
        config = *pExtConfig;

    if (!bGameStreaming)
    {
        if (CheckSWEncCondition(video))
        {
            bool bAdaptiveI = !(pExtOpt2 && IsOff(pExtOpt2->AdaptiveI)) && !(video.mfx.GopOptFlag & MFX_GOP_STRICT);
            bool bAdaptiveB = !(pExtOpt2 && IsOff(pExtOpt2->AdaptiveB)) && !(video.mfx.GopOptFlag & MFX_GOP_STRICT);
            SetDefaultOpt(config.AdaptiveI, bAdaptiveI);
            SetDefaultOpt(config.AdaptiveB, bAdaptiveB);
            SetDefaultOpt(config.AdaptivePyramidQuantP, false);
            SetDefaultOpt(config.AdaptivePyramidQuantB, true);

            bool bAdaptiveRef = IsAdaptiveRefAllowed(video);
            SetDefaultOpt(config.AdaptiveRefP, bAdaptiveRef);
            SetDefaultOpt(config.AdaptiveRefB, bAdaptiveRef);
            SetDefaultOpt(config.AdaptiveLTR, bAdaptiveRef);
        }
        SetDefaultOpt(config.BRC, (video.mfx.RateControlMethod == MFX_RATECONTROL_CBR ||
            video.mfx.RateControlMethod == MFX_RATECONTROL_VBR));

        bool lplaAssistedBRC = IsOn(config.BRC) && isSWLACondition(video);
        SetDefaultOpt(config.BRCBufferHints, lplaAssistedBRC);
        SetDefaultOpt(config.AdaptiveMBQP,  bMBQPSupport && lplaAssistedBRC && IsOn(pExtOpt2->MBBRC));
        SetDefaultOpt(config.AdaptiveQuantMatrices, false);
    }
#ifdef MFX_ENABLE_ENCTOOLS_LPLA
    else
    {
        bool bLA = (pExtOpt2 && pExtOpt2->LookAheadDepth > 0 &&
            (video.mfx.RateControlMethod == MFX_RATECONTROL_CBR ||
                video.mfx.RateControlMethod == MFX_RATECONTROL_VBR));
        bool bAdaptiveI = (pExtOpt2 && IsOn(pExtOpt2->AdaptiveI)) && !(video.mfx.GopOptFlag & MFX_GOP_STRICT);
        bool bAdaptiveB = (pExtOpt2 && IsOn(pExtOpt2->AdaptiveB)) && !(video.mfx.GopOptFlag & MFX_GOP_STRICT);

       SetDefaultOpt(config.BRCBufferHints, bLA);
       SetDefaultOpt(config.AdaptivePyramidQuantP, bLA);
       SetDefaultOpt(config.AdaptivePyramidQuantB, bLA);
       SetDefaultOpt(config.AdaptiveQuantMatrices, bLA && !IsOff(pExtOpt3->AdaptiveCQM));
       SetDefaultOpt(config.AdaptiveI, bLA && bAdaptiveI);
       SetDefaultOpt(config.AdaptiveB, bLA && bAdaptiveB);
       SetDefaultOpt(config.AdaptiveMBQP, bLA && bMBQPSupport && IsOn(pExtOpt2->MBBRC) );
    }
#endif
}

inline mfxU32 CheckFlag(mfxU16 & flag, bool bCond)
{
    return CheckOrZero<mfxU16>(flag
        , mfxU16(MFX_CODINGOPTION_UNKNOWN)
        , mfxU16(MFX_CODINGOPTION_OFF)
        , mfxU16(MFX_CODINGOPTION_ON * (bCond)));
}

static mfxU32 CorrectVideoParams(mfxVideoParam & video, mfxExtEncToolsConfig & supportedConfig)
{
    mfxExtCodingOption2   *pExtOpt2 = ExtBuffer::Get(video);
    mfxExtCodingOption3   *pExtOpt3 = ExtBuffer::Get(video);
    mfxExtBRC             *pBRC = ExtBuffer::Get(video);
    mfxExtEncToolsConfig  *pConfig = ExtBuffer::Get(video);

    bool bIsEncToolsEnabled = CheckEncToolsCondition(video);
    mfxU32 changed = 0;

#ifdef MFX_ENABLE_ENCTOOLS_LPLA
    if (IsGameStreaming(video))
    {
        // Closed GOP for GS by default unless IdrInterval is set to max
        // Open GOP with arbitrary IdrInterval if GOP is strict (GopOptFlag == MFX_GOP_STRICT)
        if (video.mfx.GopOptFlag == 0 && video.mfx.IdrInterval != USHRT_MAX)
        {
            changed++;
            video.mfx.GopOptFlag = MFX_GOP_CLOSED;
        }
    }
#endif

    if (pConfig)
    {
        bool bAdaptiveI = !(pExtOpt2 && IsOff(pExtOpt2->AdaptiveI)) && !(video.mfx.GopOptFlag & MFX_GOP_STRICT);
        bool bAdaptiveB = !(pExtOpt2 && IsOff(pExtOpt2->AdaptiveB)) && !(video.mfx.GopOptFlag & MFX_GOP_STRICT);
        bool bAdaptiveRef = IsAdaptiveRefAllowed(video);

        changed += CheckFlag(pConfig->AdaptiveI, bIsEncToolsEnabled && bAdaptiveI);
        changed += CheckFlag(pConfig->AdaptiveB, bIsEncToolsEnabled && bAdaptiveB);
        changed += CheckFlag(pConfig->AdaptivePyramidQuantB, bIsEncToolsEnabled);
        changed += CheckFlag(pConfig->AdaptivePyramidQuantP, bIsEncToolsEnabled);

        changed += CheckFlag(pConfig->AdaptiveRefP, bIsEncToolsEnabled  && bAdaptiveRef);
        changed += CheckFlag(pConfig->AdaptiveRefB, bIsEncToolsEnabled  && bAdaptiveRef);
        changed += CheckFlag(pConfig->AdaptiveLTR, bIsEncToolsEnabled  && bAdaptiveRef);
        changed += CheckFlag(pConfig->SceneChange, bIsEncToolsEnabled);
        changed += CheckFlag(pConfig->BRCBufferHints, bIsEncToolsEnabled);
        changed += CheckFlag(pConfig->AdaptiveQuantMatrices, bIsEncToolsEnabled);
        changed += CheckFlag(pConfig->BRC, bIsEncToolsEnabled);

        changed += CheckFlag(pConfig->AdaptiveI, IsOn(supportedConfig.AdaptiveI));
        changed += CheckFlag(pConfig->AdaptiveB, IsOn(supportedConfig.AdaptiveB));
        changed += CheckFlag(pConfig->AdaptivePyramidQuantB, IsOn(supportedConfig.AdaptivePyramidQuantB));
        changed += CheckFlag(pConfig->AdaptivePyramidQuantP, IsOn(supportedConfig.AdaptivePyramidQuantP));
        changed += CheckFlag(pConfig->AdaptiveRefP, IsOn(supportedConfig.AdaptiveRefP));
        changed += CheckFlag(pConfig->AdaptiveRefB, IsOn(supportedConfig.AdaptiveRefB));
        changed += CheckFlag(pConfig->AdaptiveLTR, IsOn(supportedConfig.AdaptiveLTR));
        changed += CheckFlag(pConfig->SceneChange, IsOn(supportedConfig.SceneChange));
        changed += CheckFlag(pConfig->BRCBufferHints, IsOn(supportedConfig.BRCBufferHints));
        changed += CheckFlag(pConfig->AdaptiveQuantMatrices, IsOn(supportedConfig.AdaptiveQuantMatrices));
        changed += CheckFlag(pConfig->BRC, IsOn(supportedConfig.BRC));
        changed += CheckFlag(pConfig->AdaptiveMBQP, IsOn(supportedConfig.AdaptiveMBQP));
    }
    if (pExtOpt2)
    {
        changed += CheckFlag(pExtOpt2->AdaptiveI, IsOn(supportedConfig.AdaptiveI));
        changed += CheckFlag(pExtOpt2->AdaptiveB, IsOn(supportedConfig.AdaptiveB));
        changed += CheckFlag(pExtOpt2->ExtBRC, IsOn(supportedConfig.BRC));
        changed += CheckFlag(pExtOpt2->MBBRC, IsOn(supportedConfig.AdaptiveMBQP));
    }
    if (pExtOpt3)
        changed += CheckFlag(pExtOpt3->ExtBrcAdaptiveLTR, IsOn(supportedConfig.AdaptiveLTR));

    //ExtBRC isn't compatible with EncTools
    if (pBRC && bIsEncToolsEnabled &&
        (pBRC->pthis ||
         pBRC->Init ||
         pBRC->Close ||
         pBRC->Update ||
         pBRC->GetFrameCtrl ||
         pBRC->Reset ||
         pBRC->GetFrameCtrl))
    {
        pBRC->pthis = nullptr;
        pBRC->Init = nullptr;
        pBRC->Close = nullptr;
        pBRC->Update = nullptr;
        pBRC->GetFrameCtrl = nullptr;
        pBRC->Reset = nullptr;
        changed++;
    }

    return changed;
}

static mfxStatus InitEncToolsCtrl(
    mfxVideoParam const & par
    , mfxEncToolsCtrl *ctrl)
{
    MFX_CHECK_NULL_PTR1(ctrl);

    const mfxExtCodingOption  *pCO  = ExtBuffer::Get(par);
    const mfxExtCodingOption2 *pCO2 = ExtBuffer::Get(par);
    const mfxExtCodingOption3 *pCO3 = ExtBuffer::Get(par);
    const mfxExtCodingOptionDDI* pCODDI = ExtBuffer::Get(par);

    ctrl->CodecId = par.mfx.CodecId;
    ctrl->CodecProfile = par.mfx.CodecProfile;
    ctrl->CodecLevel = par.mfx.CodecLevel;
    ctrl->LowPower = par.mfx.LowPower;
    ctrl->AsyncDepth = par.AsyncDepth;

    ctrl->FrameInfo = par.mfx.FrameInfo;
    ctrl->IOPattern = par.IOPattern;
    ctrl->MaxDelayInFrames = pCO2 ? pCO2->LookAheadDepth : 0 ;

    ctrl->NumRefP = pCODDI ? std::min(par.mfx.NumRefFrame, pCODDI->NumActiveRefP) : par.mfx.NumRefFrame;
    ctrl->MaxGopSize = par.mfx.GopPicSize;
    ctrl->MaxGopRefDist = par.mfx.GopRefDist;
    ctrl->MaxIDRDist = par.mfx.GopPicSize * par.mfx.IdrInterval;
    // HEVC Defaults to CRA for IdrInterval == 0
    if (par.mfx.IdrInterval == 0 && par.mfx.GopPicSize != 0) {
        ctrl->MaxIDRDist = par.mfx.GopPicSize * (0xffff / par.mfx.GopPicSize);
    }
    ctrl->BRefType = pCO2 ? pCO2->BRefType : 0;

    ctrl->ScenarioInfo = pCO3 ? pCO3->ScenarioInfo : 0;

    ctrl->GopOptFlag = (mfxU8)par.mfx.GopOptFlag;

    // Rate control info
    mfxU32 mult = par.mfx.BRCParamMultiplier ? par.mfx.BRCParamMultiplier : 1;
    bool   BRC = (par.mfx.RateControlMethod == MFX_RATECONTROL_CBR ||
        par.mfx.RateControlMethod == MFX_RATECONTROL_VBR);

    ctrl->RateControlMethod = par.mfx.RateControlMethod;  //CBR, VBR, CRF,CQP

    if (!BRC)
    {
        ctrl->QPLevel[0] = par.mfx.QPI;
        ctrl->QPLevel[1] = par.mfx.QPP;
        ctrl->QPLevel[2] = par.mfx.QPB;
    }
    else
    {
        ctrl->TargetKbps = par.mfx.TargetKbps * mult;
        ctrl->MaxKbps = par.mfx.MaxKbps * mult;

        if (pCO)
        {
            ctrl->HRDConformance = MFX_BRC_NO_HRD;
            if (!IsOff(pCO->NalHrdConformance) && !IsOff(pCO->VuiNalHrdParameters))
                ctrl->HRDConformance = MFX_BRC_HRD_STRONG;
            else if (IsOn(pCO->NalHrdConformance) && IsOff(pCO->VuiNalHrdParameters))
                ctrl->HRDConformance = MFX_BRC_HRD_WEAK;
        }
        else
            ctrl->HRDConformance = MFX_BRC_HRD_STRONG;

        ctrl->BufferSizeInKB   = par.mfx.BufferSizeInKB*mult;          //Bitstream size
        if (ctrl->HRDConformance)
        {
            ctrl->InitialDelayInKB = par.mfx.InitialDelayInKB*mult;    //if HRDConformance is ON
        }
        else
        {
            ctrl->ConvergencePeriod = 0;     //if HRDConformance is OFF, 0 - the period is whole stream,
            ctrl->Accuracy = 10;              //if HRDConformance is OFF
        }

        mfxU32 maxFrameSize = pCO2 ? pCO2->MaxFrameSize : 0;
        ctrl->WinBRCMaxAvgKbps = pCO3 ? pCO3->WinBRCMaxAvgKbps*mult : 0;
        ctrl->WinBRCSize = pCO3 ? pCO3->WinBRCSize : 0;
        ctrl->MaxFrameSizeInBytes[0] = (pCO3 && pCO3->MaxFrameSizeI) ? pCO3->MaxFrameSizeI : maxFrameSize;     // MaxFrameSize limitation
        ctrl->MaxFrameSizeInBytes[1] = (pCO3 && pCO3->MaxFrameSizeP) ? pCO3->MaxFrameSizeP : maxFrameSize;
        ctrl->MaxFrameSizeInBytes[2] = maxFrameSize;

        ctrl->MinQPLevel[0] = pCO2 ? pCO2->MinQPI : 0;       //QP range  limitations
        ctrl->MinQPLevel[1] = pCO2 ? pCO2->MinQPP : 0;
        ctrl->MinQPLevel[2] = pCO2 ? pCO2->MinQPB : 0;

        ctrl->MaxQPLevel[0] = pCO2 ? pCO2->MaxQPI : 0;       //QP range limitations
        ctrl->MaxQPLevel[1] = pCO2 ? pCO2->MaxQPP : 0;
        ctrl->MaxQPLevel[2] = pCO2 ? pCO2->MaxQPB : 0;

        ctrl->PanicMode = pCO3 ? pCO3->BRCPanicMode : 0;
    }

    // LaScale here
    ctrl->LaScale = 0;
    ctrl->LaQp = 30;
    if (ctrl->ScenarioInfo == MFX_SCENARIO_GAME_STREAMING) 
    {
        mfxU16 crW = par.mfx.FrameInfo.CropW ? par.mfx.FrameInfo.CropW : par.mfx.FrameInfo.Width;
        if (crW >= 720) ctrl->LaScale = 2;
    }
    else 
    {
        mfxU16 crH = par.mfx.FrameInfo.CropH ? par.mfx.FrameInfo.CropH : par.mfx.FrameInfo.Height;
        mfxU16 crW = par.mfx.FrameInfo.CropW ? par.mfx.FrameInfo.CropW : par.mfx.FrameInfo.Width;
        mfxU16 maxDim = std::max(crH, crW);
        mfxU16 minDim = std::min(crH, crW);
        constexpr mfxU16 LaScale = 2;
        if (maxDim >= 720 && 
            minDim >= (128<<LaScale)) //encoder limitation, 128 and up is fine
        {
            ctrl->LaScale = LaScale;
        }
        ctrl->LaQp = 26;
    }

    return MFX_ERR_NONE;
}
static  bool isEncTools(const mfxVideoParam& par)
{
    const mfxExtEncToolsConfig* pConfig = ExtBuffer::Get(par);
    bool bEncTools = false;
    if (pConfig)
    {
        mfxExtEncToolsConfig config = {};
        SetDefaultConfig(par, config, true);
        bEncTools = IsEncToolsOptOn(config, IsGameStreaming(par));
    }
    else
        bEncTools = IsEncToolsImplicit(par);

    return bEncTools;
}
void HevcEncTools::Query1NoCaps(const FeatureBlocks& blocks, TPushQ1 Push)
{
    Push(BLK_SetDefaultsCallChain,
        [this](const mfxVideoParam&, mfxVideoParam&, StorageRW& strg) -> mfxStatus
        {
            auto& defaults = Glob::Defaults::GetOrConstruct(strg);
            auto& bSet = defaults.SetForFeature[GetID()];
            MFX_CHECK(!bSet, MFX_ERR_NONE);
            defaults.GetMBBRC.Push(
                [](Defaults::TChain<mfxU16>::TExt prev
                    , const Defaults::Param& par)
                {
                    bool bEncTools = isEncTools(par.mvp);
                    if (bEncTools && !IsGameStreaming(par.mvp) && isSWLACondition(par.mvp))
                    {
                        const mfxExtCodingOption2* pCO2 = ExtBuffer::Get(par.mvp);
                        const mfxExtEncToolsConfig* pConfig = ExtBuffer::Get(par.mvp);
                        bool bDisableMBQP = pConfig && (IsOff(pConfig->AdaptiveMBQP) || IsOff(pConfig->BRC));
                        return (mfxU16)((pCO2 && pCO2->MBBRC) ?
                            pCO2->MBBRC :
                            bDisableMBQP ? MFX_CODINGOPTION_OFF : MFX_CODINGOPTION_ON);
                    }
                    return mfxU16(prev(par));
                });
            return MFX_ERR_NONE;
        });
    Push(BLK_Check,
        [&blocks](const mfxVideoParam&, mfxVideoParam& par, StorageW& ) -> mfxStatus
    {
        bool bEncTools = isEncTools(par);
        mfxU32 changed = 0;
        MFX_CHECK(bEncTools, MFX_ERR_NONE);

        mfxEncTools *pEncTools = GetEncTools(par);
        bool bCreated = false;

        if (!pEncTools)
        {
            pEncTools = MFXVideoENCODE_CreateEncTools(par);
            bCreated = !!pEncTools;
        }

        mfxEncToolsCtrl ctrl = {};
        mfxExtEncToolsConfig supportedConfig = {};

        mfxStatus sts = InitEncToolsCtrl(par, &ctrl);
        MFX_CHECK_STS(sts);

        pEncTools->GetSupportedConfig(pEncTools->Context, &supportedConfig, &ctrl);

        changed += CorrectVideoParams(par, supportedConfig);
        if (bCreated)
            MFXVideoENCODE_DestroyEncTools(pEncTools);

        MFX_CHECK(!changed, MFX_WRN_INCOMPATIBLE_VIDEO_PARAM);
        return MFX_ERR_NONE;
    });
}

void HevcEncTools::SetDefaults(const FeatureBlocks& /*blocks*/, TPushSD Push)
{
    Push(BLK_SetDefaults
        , [](mfxVideoParam& par, StorageW& global, StorageRW&)
    {
        mfxExtEncToolsConfig *pConfig = ExtBuffer::Get(par);
        auto& caps = Glob::EncodeCaps::Get(global);
        if (pConfig)
            SetDefaultConfig(par, *pConfig, caps.MBBRCSupport);
    });
}

void HevcEncTools::Reset(const FeatureBlocks& /*blocks*/, TPushR Push)
{
    Push(BLK_ResetCheck
        , [](
            const mfxVideoParam& /*par*/
            , StorageRW& global
            , StorageRW&) -> mfxStatus
    {
        auto& init = Glob::RealState::Get(global);
        auto& parOld = Glob::VideoParam::Get(init);
        auto& parNew = Glob::VideoParam::Get(global);

        mfxExtEncToolsConfig *pConfigOld = ExtBuffer::Get(parOld);
        mfxExtEncToolsConfig *pConfigNew = ExtBuffer::Get(parNew);

        if (pConfigOld && pConfigNew)
        {
            MFX_CHECK(
                pConfigOld->AdaptiveB == pConfigNew->AdaptiveB
                && pConfigOld->AdaptiveI == pConfigNew->AdaptiveI
                && pConfigOld->AdaptiveRefP == pConfigNew->AdaptiveRefP
                && pConfigOld->AdaptiveRefB == pConfigNew->AdaptiveRefB
                && pConfigOld->AdaptiveLTR == pConfigNew->AdaptiveLTR
                && pConfigOld->AdaptivePyramidQuantP == pConfigNew->AdaptivePyramidQuantP
                && pConfigOld->AdaptivePyramidQuantB == pConfigNew->AdaptivePyramidQuantB
                && pConfigOld->BRC == pConfigNew->BRC
                && pConfigOld->BRCBufferHints == pConfigNew->BRCBufferHints
                && pConfigOld->AdaptiveQuantMatrices == pConfigNew->AdaptiveQuantMatrices
                , MFX_ERR_INCOMPATIBLE_VIDEO_PARAM);
        }

        return MFX_ERR_NONE;
    });
}

void HevcEncTools::ResetState(const FeatureBlocks& /*blocks*/, TPushRS Push)
{
    Push(BLK_Reset
        , [this](
            StorageRW& global
            , StorageRW&) -> mfxStatus
    {
        auto& par = Glob::VideoParam::Get(global);

        if (m_pEncTools && m_pEncTools->Reset)
        {
            m_EncToolCtrl = {};
            mfxExtBuffer* ExtParam;
            mfxExtEncoderResetOption rOpt = {};

            if (Glob::ResetHint::Get(global).Flags & RF_IDR_REQUIRED)
            {
                rOpt.Header.BufferId = MFX_EXTBUFF_ENCODER_RESET_OPTION;
                rOpt.Header.BufferSz = sizeof(rOpt);
                rOpt.StartNewSequence = MFX_CODINGOPTION_ON;
                ExtParam = &rOpt.Header;
                m_EncToolCtrl.NumExtParam = 1;
                m_EncToolCtrl.ExtParam = &ExtParam;
            }
            auto sts = InitEncToolsCtrl(par, &m_EncToolCtrl);
            MFX_CHECK_STS(sts);

            sts = m_pEncTools->Reset(m_pEncTools->Context, &m_EncToolConfig, &m_EncToolCtrl);
            MFX_CHECK_STS(sts);
        }

        return MFX_ERR_NONE;
    });
}

inline void InitEncToolsCtrlExtDevice(mfxEncToolsCtrlExtDevice & extDevice
                            , mfxHandleType h_type
                            , mfxHDL device_handle)
{
    extDevice.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_DEVICE;
    extDevice.Header.BufferSz = sizeof(mfxEncToolsCtrlExtDevice);
    extDevice.HdlType = h_type;
    extDevice.DeviceHdl = device_handle;
}

inline void InitEncToolsCtrlExtAllocator(mfxEncToolsCtrlExtAllocator & extAllocator
                            , mfxFrameAllocator &allocator)
{
    extAllocator.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_ALLOCATOR;
    extAllocator.Header.BufferSz = sizeof(mfxEncToolsCtrlExtAllocator);
    extAllocator.pAllocator = &allocator;
}

inline mfxHandleType GetHandleType(eMFXVAType vaType)
{
    switch (vaType)
    {
    case MFX_HW_D3D9:  return MFX_HANDLE_D3D9_DEVICE_MANAGER;
    case MFX_HW_VAAPI: return MFX_HANDLE_VA_DISPLAY;
    default:
      break;
    }
    return MFX_HANDLE_D3D11_DEVICE;
}

void HevcEncTools::QueryIOSurf(const FeatureBlocks&, TPushQIS Push)
{
    Push(BLK_QueryIOSurf
        , [](const mfxVideoParam& parInput, mfxFrameAllocRequest& req, StorageRW& strg) -> mfxStatus
    {
            ExtBuffer::Param<mfxVideoParam> par = parInput;
            auto& caps = Glob::EncodeCaps::Get(strg);

            bool bEncTools = isEncTools(par);
            mfxU32 changed = 0;
            MFX_CHECK(bEncTools, MFX_ERR_NONE);

            mfxEncTools* pEncTools = GetEncTools(par);
            bool bCreated = false;

            if (!pEncTools)
            {
                pEncTools = MFXVideoENCODE_CreateEncTools(par);
                bCreated = !!pEncTools;
            }
            MFX_CHECK_NULL_PTR1(pEncTools);

            mfxEncToolsCtrl ctrl = {};
            mfxExtEncToolsConfig supportedConfig = {};

            mfxStatus sts = InitEncToolsCtrl(par, &ctrl);
            MFX_CHECK_STS(sts);

            pEncTools->GetSupportedConfig(pEncTools->Context, &supportedConfig, &ctrl);

            changed += CorrectVideoParams(par, supportedConfig);

            mfxExtEncToolsConfig config = {};
            SetDefaultConfig(par, config, caps.MbQpDataSupport);           
 
            mfxU32 maxDelay = 0;
            pEncTools->GetDelayInFrames(pEncTools->Context, &config, &ctrl, &maxDelay);
            
            mfxExtCodingOption2* pCO2 = ExtBuffer::Get(par);
            if (pCO2)
                maxDelay = (mfxU32)std::max<mfxI32>(0, maxDelay - pCO2->LookAheadDepth); //LA is used in base_legacy
            req.NumFrameMin += (mfxU16)maxDelay;
            req.NumFrameSuggested += (mfxU16)maxDelay;

            if (bCreated)
                MFXVideoENCODE_DestroyEncTools(pEncTools);

            MFX_CHECK(!changed, MFX_WRN_INCOMPATIBLE_VIDEO_PARAM);
            return MFX_ERR_NONE;
    });
}

mfxStatus HevcEncTools::SubmitPreEncTask(StorageW&  /*global*/, StorageW& s_task)
{
    MFX_CHECK(m_pEncTools && m_pEncTools->Submit, MFX_ERR_NONE);

    mfxEncToolsTaskParam task_par = {};
    auto&      task = Task::Common::Get(s_task);
    std::vector<mfxExtBuffer*> extParams;
    mfxEncToolsFrameToAnalyze extFrameData = {};
    if (task.pSurfIn)
    {
        extFrameData.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_FRAME_TO_ANALYZE;
        extFrameData.Header.BufferSz = sizeof(extFrameData);
        extFrameData.Surface = task.pSurfIn;
        extParams.push_back(&extFrameData.Header);
        task_par.ExtParam = extParams.data();
    }
    task_par.DisplayOrder = task.DisplayOrder;
    task_par.NumExtParam = (mfxU16)extParams.size();

    auto sts = m_pEncTools->Submit(m_pEncTools->Context, &task_par);
    if (sts == MFX_ERR_MORE_DATA) sts = MFX_ERR_NONE;
    return (sts);
}

constexpr mfxU32 ENCTOOLS_QUERY_TIMEOUT = 5000;
mfxStatus HevcEncTools::BRCGetCtrl(StorageW& global , StorageW& s_task,
    mfxEncToolsBRCQuantControl &extQuantCtrl , mfxEncToolsBRCHRDPos  &extHRDPos,
    mfxEncToolsHintQPMap   &qpMapHint)
{
    MFX_CHECK(IsOn (m_EncToolConfig.BRC) && m_pEncTools && m_pEncTools->Submit, MFX_ERR_NONE);

    mfxEncToolsTaskParam task_par = {};
    auto&      task = Task::Common::Get(s_task);
    std::vector<mfxExtBuffer*> extParams;
    mfxEncToolsBRCFrameParams  extFrameData = {};
    mfxEncToolsBRCBufferHint extBRCHints = {};
    task_par.DisplayOrder = task.DisplayOrder;

    {
        // input params
        extFrameData.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_BRC_FRAME_PARAM;
        extFrameData.Header.BufferSz = sizeof(extFrameData);
        extFrameData.EncodeOrder = task.EncodedOrder;
        extFrameData.FrameType = task.FrameType;
        extFrameData.PyramidLayer = (mfxU16) task.PyramidLevel;
        extFrameData.SceneChange = task.GopHints.SceneChange;
        extFrameData.SpatialComplexity = task.GopHints.SpatialComplexity;
        extFrameData.PersistenceMapNZ = task.GopHints.PersistenceMapNZ;
        memcpy(extFrameData.PersistenceMap, task.GopHints.PersistenceMap, sizeof(extFrameData.PersistenceMap));

        extParams.push_back(&extFrameData.Header);

        if (task.BrcHints.LaAvgEncodedBits)
        {
            extBRCHints.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_BRC_BUFFER_HINT;
            extBRCHints.Header.BufferSz = sizeof(extBRCHints);
            extBRCHints.AvgEncodedSizeInBits = task.BrcHints.LaAvgEncodedBits;
            extBRCHints.CurEncodedSizeInBits = task.BrcHints.LaCurEncodedBits;
            extBRCHints.DistToNextI = task.BrcHints.LaDistToNextI;
            extParams.push_back(&extBRCHints.Header);
        }

        if (task.GopHints.QPModulaton || task.GopHints.MiniGopSize)
        {
            mfxEncToolsHintPreEncodeGOP  extPreGop = {};
            extPreGop.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_HINT_GOP;
            extPreGop.Header.BufferSz = sizeof(extPreGop);
            extPreGop.QPModulation = (mfxU16)task.GopHints.QPModulaton;
            extPreGop.MiniGopSize = (mfxU16)task.GopHints.MiniGopSize;
            extParams.push_back(&extPreGop.Header);
        }


        task_par.ExtParam = extParams.data();
        task_par.NumExtParam = (mfxU16)extParams.size();

        auto sts = m_pEncTools->Submit(m_pEncTools->Context, &task_par);
        MFX_CHECK_STS(sts);
    }
    {
        extParams.clear();

        // output params
        extQuantCtrl = {};
        extQuantCtrl.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_BRC_QUANT_CONTROL;
        extQuantCtrl.Header.BufferSz = sizeof(extQuantCtrl);
        extParams.push_back(&extQuantCtrl.Header);

        extHRDPos = {};
        extHRDPos.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_BRC_HRD_POS;
        extHRDPos.Header.BufferSz = sizeof(extHRDPos);
        extParams.push_back(&extHRDPos.Header);

        auto& par = Glob::VideoParam::Get(global);
        auto& caps = Glob::EncodeCaps::Get(global);
        std::unique_ptr<FrameLocker> qpMap = nullptr;

        if (Legacy::GetMBQPMode(caps, par) == MBQPMode_FromEncToolsBRC &&
            global.Contains(Glob::AllocMBQP::Key))
        {
            qpMapHint.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_HINT_QPMAP;
            qpMapHint.Header.BufferSz = sizeof(qpMapHint);
            if (!task.CUQP.Mid)
            {
                task.CUQP = Glob::AllocMBQP::Get(global).Acquire();
                MFX_CHECK(task.CUQP.Mid, MFX_ERR_UNDEFINED_BEHAVIOR);
            }
            auto& allocInfo = Glob::MBQPAllocInfo::Get(global);
            auto& core = Glob::VideoCore::Get(global);
            qpMap = std::make_unique <FrameLocker>(core, task.CUQP.Mid);
            MFX_CHECK(qpMap->Y, MFX_ERR_LOCK_MEMORY);
            qpMapHint.ExtQpMap.BlockSize = (mfxU16)allocInfo.block_width;
            qpMapHint.ExtQpMap.QP = qpMap->Y;
            qpMapHint.ExtQpMap.Mode = MFX_MBQP_MODE_QP_VALUE;
            qpMapHint.ExtQpMap.NumQPAlloc = allocInfo.height_aligned * allocInfo.pitch;
            qpMapHint.QpMapPitch = (mfxU16)allocInfo.pitch;
            extParams.push_back(&qpMapHint.Header);
        }
 
        task_par.NumExtParam = (mfxU16)extParams.size();
        task_par.ExtParam = extParams.data();

        auto sts = m_pEncTools->Query(m_pEncTools->Context, &task_par, ENCTOOLS_QUERY_TIMEOUT);
        MFX_CHECK_STS(sts);

        if (qpMapHint.QpMapFilled)
            task.bCUQPMap = true;

    }
    return MFX_ERR_NONE;
 }

mfxStatus HevcEncTools::QueryPreEncTask(StorageW&  global, StorageW& s_task)
{
    MFX_CHECK(m_pEncTools && m_pEncTools->Query, MFX_ERR_NONE);

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

    mfxEncToolsTaskParam task_par = {};
    std::vector<mfxExtBuffer*> extParams;
    mfxEncToolsHintPreEncodeSceneChange preEncodeSChg = {};
    mfxEncToolsHintPreEncodeGOP preEncodeGOP = {};
    mfxEncToolsBRCBufferHint bufHint = {};
    mfxEncToolsHintQuantMatrix cqmHint = {};
    mfxEncToolsHintQPMap qpMapHint = {};

    bool isLABRC = (m_EncToolCtrl.ScenarioInfo == MFX_SCENARIO_UNKNOWN &&
                     IsOn(m_EncToolConfig.BRCBufferHints) &&
                     IsOn(m_EncToolConfig.BRC));

    MFX_CHECK(task.DisplayOrder!= (mfxU32)(-1), MFX_ERR_NONE);
    task_par.DisplayOrder = task.DisplayOrder;
    task_par.NumExtParam = 0;

    if (IsOn(m_EncToolConfig.BRC))
    {
        preEncodeSChg.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_HINT_SCENE_CHANGE;
        preEncodeSChg.Header.BufferSz = sizeof(preEncodeSChg);
        extParams.push_back(&preEncodeSChg.Header);
    }

    if (IsOn(m_EncToolConfig.AdaptiveI) ||
        IsOn(m_EncToolConfig.AdaptiveB) ||
        IsOn(m_EncToolConfig.AdaptivePyramidQuantP) ||
        IsOn(m_EncToolConfig.AdaptivePyramidQuantB))
    {
        preEncodeGOP.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_HINT_GOP;
        preEncodeGOP.Header.BufferSz = sizeof(preEncodeGOP);
        extParams.push_back(&preEncodeGOP.Header);
    }

#if defined MFX_ENABLE_ENCTOOLS_LPLA
    if (IsOn(m_EncToolConfig.AdaptiveQuantMatrices))
    {
        cqmHint.MatrixType = CQM_HINT_USE_FLAT_MATRIX;
        cqmHint.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_HINT_MATRIX;
        cqmHint.Header.BufferSz = sizeof(cqmHint);
        extParams.push_back(&cqmHint.Header);
    }
#endif

    if (IsOn(m_EncToolConfig.BRCBufferHints))
    {
        bufHint.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_BRC_BUFFER_HINT;
        bufHint.Header.BufferSz = sizeof(bufHint);
        bufHint.OutputMode = mfxU16(isLABRC ? MFX_BUFFERHINT_OUTPUT_DISPORDER : MFX_BUFFERHINT_OUTPUT_ENCORDER);
        extParams.push_back(&bufHint.Header);
    }
 
    auto& par  = Glob::VideoParam::Get(global);
    auto& caps = Glob::EncodeCaps::Get(global);
    std::unique_ptr <FrameLocker> qpMap = nullptr;

	if (Legacy::GetMBQPMode(caps, par) == MBQPMode_FromEncToolsLA &&
		global.Contains(Glob::AllocMBQP::Key))
	{
		qpMapHint.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_HINT_QPMAP;
		qpMapHint.Header.BufferSz = sizeof(qpMapHint);
		if (!task.CUQP.Mid)
		{
			task.CUQP = Glob::AllocMBQP::Get(global).Acquire();
			MFX_CHECK(task.CUQP.Mid, MFX_ERR_UNDEFINED_BEHAVIOR);
		}
		auto& allocInfo = Glob::MBQPAllocInfo::Get(global);
		auto& core = Glob::VideoCore::Get(global);
		qpMap = std::make_unique <FrameLocker>(core, task.CUQP.Mid);
		MFX_CHECK(qpMap->Y, MFX_ERR_LOCK_MEMORY);
		qpMapHint.ExtQpMap.BlockSize = (mfxU16)allocInfo.block_width;
		qpMapHint.ExtQpMap.QP = qpMap->Y;
		qpMapHint.ExtQpMap.Mode = MFX_MBQP_MODE_QP_DELTA;
		qpMapHint.ExtQpMap.NumQPAlloc = allocInfo.height_aligned * allocInfo.pitch;
		qpMapHint.QpMapPitch = (mfxU16)allocInfo.pitch;
		extParams.push_back(&qpMapHint.Header);
	}

	task_par.ExtParam = extParams.data();
	task_par.NumExtParam = (mfxU16)extParams.size();
    MFX_CHECK(task_par.NumExtParam, MFX_ERR_NONE);

    auto sts = m_pEncTools->Query(m_pEncTools->Context, &task_par, ENCTOOLS_QUERY_TIMEOUT);
    if (sts == MFX_ERR_MORE_DATA) sts = MFX_ERR_NONE;
    MFX_CHECK_STS(sts);

    task.GopHints.MiniGopSize = preEncodeGOP.MiniGopSize;
    task.GopHints.FrameType = preEncodeGOP.FrameType;
    task.GopHints.SceneChange = preEncodeSChg.SceneChangeFlag;
    task.GopHints.SpatialComplexity = preEncodeSChg.SpatialComplexity;
    task.GopHints.PersistenceMapNZ = preEncodeSChg.PersistenceMapNZ;
    if (qpMapHint.QpMapFilled)
        task.bCUQPMap = true;
    memcpy(task.GopHints.PersistenceMap, preEncodeSChg.PersistenceMap, sizeof(task.GopHints.PersistenceMap));

#if defined(MFX_ENABLE_ENCTOOLS_LPLA)
    if (IsOn(m_EncToolConfig.BRCBufferHints) && !isLABRC)
    {
        //Low power look ahead with HW BRC
        mfxLplastatus laStatus = {};
        laStatus.QpModulation = (mfxU8)preEncodeGOP.QPModulation;
        laStatus.MiniGopSize = (mfxU8)preEncodeGOP.MiniGopSize;

        switch (cqmHint.MatrixType)
        {
        case MFX_QUANT_MATRIX_WEAK:
            laStatus.CqmHint = CQM_HINT_USE_CUST_MATRIX1;
            break;
        case MFX_QUANT_MATRIX_MEDIUM:
            laStatus.CqmHint = CQM_HINT_USE_CUST_MATRIX2;
            break;
        case MFX_QUANT_MATRIX_STRONG:
            laStatus.CqmHint = CQM_HINT_USE_CUST_MATRIX3;
            break;
        case MFX_QUANT_MATRIX_EXTREME:
            laStatus.CqmHint = CQM_HINT_USE_CUST_MATRIX4;
            break;
        case CQM_HINT_INVALID:
            laStatus.CqmHint = CQM_HINT_INVALID;
            break;
        case MFX_QUANT_MATRIX_FLAT:
        default:
            laStatus.CqmHint = CQM_HINT_USE_FLAT_MATRIX;
        }

        laStatus.TargetFrameSize = bufHint.OptimalFrameSizeInBytes;
        LpLaStatus.push_back(laStatus); //lpLaStatus is got in encoded order
    }
    else
#endif
    {
        if (IsOn(m_EncToolConfig.AdaptivePyramidQuantB) || IsOn(m_EncToolConfig.AdaptivePyramidQuantP))
            task.GopHints.QPModulaton = (mfxU8)preEncodeGOP.QPModulation;

        if (isLABRC)
        {
            task.BrcHints.LaAvgEncodedBits = bufHint.AvgEncodedSizeInBits;
            task.BrcHints.LaCurEncodedBits = bufHint.CurEncodedSizeInBits;
            task.BrcHints.LaDistToNextI = bufHint.DistToNextI;
        }
     }
    return sts;
}

mfxStatus HevcEncTools::BRCUpdate(StorageW&  , StorageW& s_task, mfxEncToolsBRCStatus & brcStatus)
{
    MFX_CHECK(IsOn(m_EncToolConfig.BRC) && m_pEncTools && m_pEncTools->Query, MFX_ERR_NONE);

    auto& task = Task::Common::Get(s_task);
    mfxEncToolsTaskParam task_par = {};
    std::vector<mfxExtBuffer*> extParams;

    MFX_CHECK(task.DisplayOrder != (mfxU32)(-1), MFX_ERR_NONE);
    task_par.DisplayOrder = task.DisplayOrder;

    {
        mfxEncToolsBRCEncodeResult extEncRes;
        extEncRes.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_BRC_ENCODE_RESULT;
        extEncRes.Header.BufferSz = sizeof(extEncRes);
        extEncRes.CodedFrameSize = task.MinFrameSize > task.BsDataLength ? task.MinFrameSize : task.BsDataLength;
        extEncRes.QpY = (mfxU16)task.QpY;
        extEncRes.NumRecodesDone = task.NumRecode;

        extParams.push_back(&extEncRes.Header);
        task_par.NumExtParam = (mfxU16)extParams.size();
        task_par.ExtParam = extParams.data();
        auto sts = m_pEncTools->Submit(m_pEncTools->Context, &task_par);
        MFX_CHECK_STS(sts);
    }
    {
        extParams.clear();
        brcStatus = {};
        brcStatus.Header.BufferId = MFX_EXTBUFF_ENCTOOLS_BRC_STATUS;
        brcStatus.Header.BufferSz = sizeof(brcStatus);

        extParams.push_back(&brcStatus.Header);
        task_par.NumExtParam = (mfxU16)extParams.size();
        task_par.ExtParam = extParams.data();
        auto sts = m_pEncTools->Query(m_pEncTools->Context, &task_par, ENCTOOLS_QUERY_TIMEOUT);
        MFX_CHECK_STS(sts)
    }

    return MFX_ERR_NONE;
}

void HevcEncTools::InitInternal(const FeatureBlocks& /*blocks*/, TPushII Push)
{
     Push(BLK_Init
        , [this](StorageRW& strg, StorageRW&) -> mfxStatus
    {
        auto& par = Glob::VideoParam::Get(strg);
        auto& caps = Glob::EncodeCaps::Get(strg);

        mfxExtEncToolsConfig* pConfig = ExtBuffer::Get(par);
        bool bEncTools = (pConfig) ?
            IsEncToolsOptOn(*pConfig, IsGameStreaming(par)) :
            false;
        MFX_CHECK(bEncTools, MFX_ERR_NONE);
        MFX_CHECK(!m_pEncTools, MFX_ERR_NONE);

        mfxEncTools*                encTools = GetEncTools(par);
        mfxEncToolsCtrlExtDevice    extBufDevice = {};
        mfxEncToolsCtrlExtAllocator extBufAlloc = {};
        mfxExtBuffer* ExtParam[2] = {};

        memset(&m_EncToolCtrl, 0, sizeof(mfxEncToolsCtrl));
        auto vaType = Glob::VideoCore::Get(strg).GetVAType();

        if (vaType!= MFX_HW_NO)
        {
            mfxFrameAllocator *pFrameAlloc = nullptr;
            mfxHDL device_handle = {};
            mfxHandleType h_type = GetHandleType(vaType);
            auto sts = Glob::VideoCore::Get(strg).GetHandle(h_type, &device_handle);
            MFX_CHECK_STS(sts);
            pFrameAlloc = (mfxFrameAllocator*)Glob::VideoCore::Get(strg).QueryCoreInterface(MFXIEXTERNALLOC_GUID);//(&frameAlloc);

            InitEncToolsCtrlExtDevice(extBufDevice, h_type, device_handle);
            if (pFrameAlloc)
                InitEncToolsCtrlExtAllocator(extBufAlloc, *pFrameAlloc);

            ExtParam[0] = &extBufDevice.Header;
            ExtParam[1] = &extBufAlloc.Header;
            m_EncToolCtrl.ExtParam = ExtParam;
            m_EncToolCtrl.NumExtParam = 2;
        }
        auto sts = InitEncToolsCtrl(par, &m_EncToolCtrl);
        MFX_CHECK_STS(sts);

        m_bEncToolsInner = false;
        if (!(encTools && encTools->Context))
        {
            encTools = MFXVideoENCODE_CreateEncTools(par);
            m_bEncToolsInner = !!encTools;
        }
        if (encTools)
        {
            mfxExtEncToolsConfig supportedConfig = {};

            encTools->GetSupportedConfig(encTools->Context, &supportedConfig, &m_EncToolCtrl);

            if (CorrectVideoParams(par, supportedConfig))
                MFX_RETURN(MFX_ERR_INCOMPATIBLE_VIDEO_PARAM);

            SetDefaultConfig(par, m_EncToolConfig,caps.MbQpDataSupport);

            sts = encTools->Init(encTools->Context, &m_EncToolConfig, &m_EncToolCtrl);
            MFX_CHECK_STS(sts);

            sts = encTools->GetActiveConfig(encTools->Context, &m_EncToolConfig);
            MFX_CHECK_STS(sts);

            encTools->GetDelayInFrames(encTools->Context, &m_EncToolConfig, &m_EncToolCtrl, &m_maxDelay);

            auto& taskMgrIface = TaskManager::TMInterface::Get(strg);
            auto& tm = taskMgrIface.Manager;

            S_ET_SUBMIT = tm.AddStage(tm.S_NEW);
            S_ET_QUERY = tm.AddStage(S_ET_SUBMIT);

            m_pEncTools = encTools;
        }

        m_destroy = [this]()
        {
            if (m_bEncToolsInner)
                MFXVideoENCODE_DestroyEncTools(m_pEncTools);
            m_bEncToolsInner = false;
        };

        return MFX_ERR_NONE;
    });


    // Add S_ET_SUBMIT and S_ET_QUERY stages for EncTools
    Push(BLK_AddTask
        , [this](StorageRW& global, StorageRW&) -> mfxStatus
    {
        MFX_CHECK(S_ET_SUBMIT != mfxU16(-1) && S_ET_QUERY != mfxU16(-1), MFX_ERR_NONE);
        auto& taskMgrIface = TaskManager::TMInterface::Get(global);
        auto& tm = taskMgrIface.Manager;

        auto  ETSubmit = [&](
            TaskManager::ExtTMInterface::TAsyncStage::TExt
            , StorageW& global
            , StorageW& /*s_task*/) -> mfxStatus
        {
            std::unique_lock<std::mutex> closeGuard(tm.m_closeMtx);

            if(tm.m_nRecodeTasks)
            {
                return MFX_ERR_NONE;
            }

            if (StorageW* pTask = tm.GetTask(tm.Stage(S_ET_SUBMIT)))
            {
                SubmitPreEncTask(global, *pTask);
                tm.MoveTaskForward(tm.Stage(S_ET_SUBMIT), tm.FixedTask(*pTask));
            }

            return MFX_ERR_NONE;
        };

        auto  ETQuery = [&](
            TaskManager::ExtTMInterface::TAsyncStage::TExt
            , StorageW&  global
            , StorageW&  s_task) -> mfxStatus
        {
            std::unique_lock<std::mutex> closeGuard(tm.m_closeMtx);
            bool       bFlush = !tm.IsInputTask(s_task);

            if(tm.m_nRecodeTasks)
            {
                return MFX_ERR_NONE;
            }

            // Delay For LookAhead Depth
            MFX_CHECK(tm.m_stages.at(tm.Stage(S_ET_QUERY)).size() >= std::max(m_maxDelay,1U)  || bFlush,MFX_ERR_NONE);

            StorageW* pTask = tm.GetTask(tm.Stage(S_ET_QUERY));
            MFX_CHECK(pTask, MFX_ERR_NONE);
            auto sts = QueryPreEncTask(global, *pTask);
            MFX_CHECK_STS(sts);

            tm.MoveTaskForward(tm.Stage(S_ET_QUERY), tm.FixedTask(*pTask));

            return MFX_ERR_NONE;
        };

        taskMgrIface.AsyncStages[tm.Stage(S_ET_SUBMIT)].Push(ETSubmit);
        taskMgrIface.AsyncStages[tm.Stage(S_ET_QUERY)].Push(ETQuery);

        // Extend Num of tasks and size of buffer.
        taskMgrIface.ResourceExtra += (mfxU16)m_maxDelay;

        return MFX_ERR_NONE;
    });
    Push(BLK_UpdateTask
        , [this](StorageRW& global, StorageRW&) -> mfxStatus
    {
        MFX_CHECK(m_pEncTools, MFX_ERR_NONE);
        auto& taskMgrIface = TaskManager::TMInterface::Get(global);

        auto  UpdateTask = [&](
            TaskManager::ExtTMInterface::TUpdateTask::TExt
            , StorageW* dstTask) -> mfxStatus
        {

            if (dstTask)
            {
                auto& dst_task = Task::Common::Get(*dstTask);
                if (LpLaStatus.size() > 0)
                {
                    dst_task.LplaStatus = *(LpLaStatus.begin());
                    LpLaStatus.pop_front();
                }
            }
            return MFX_ERR_NONE;
        };
        taskMgrIface.UpdateTask.Push(UpdateTask);

        return MFX_ERR_NONE;
    });
}

void HevcEncTools::SubmitTask(const FeatureBlocks& /*blocks*/, TPushST Push)
{
    Push(BLK_GetFrameCtrl
        , [this](
            StorageW& global
            , StorageW& s_task) -> mfxStatus
    {
        MFX_CHECK(IsOn(m_EncToolConfig.BRC) && m_pEncTools && m_pEncTools->Submit, MFX_ERR_NONE);

        auto&      par = Glob::VideoParam::Get(global);
        auto&      task = Task::Common::Get(s_task);
        auto&      sh = Task::SSH::Get(s_task);
        auto&      sps = Glob::SPS::Get(global);
        auto&      pps = Glob::PPS::Get(global);

        bool bNegativeQpAllowed = !IsOn(par.mfx.LowPower);

        mfxI32 minQP = (-6 * sps.bit_depth_luma_minus8) * bNegativeQpAllowed;
        mfxI32 maxQP = 51;

        mfxEncToolsBRCQuantControl quantCtrl = {};
        mfxEncToolsHintQPMap   qpMapHint = {};
        mfxEncToolsBRCHRDPos  HRDPos = {};

        BRCGetCtrl(global, s_task, quantCtrl, HRDPos, qpMapHint);

        SetDefault(HRDPos.InitialCpbRemovalDelay, task.initial_cpb_removal_delay);
        SetDefault(HRDPos.InitialCpbRemovalDelayOffset, task.initial_cpb_removal_offset);

        task.initial_cpb_removal_delay = HRDPos.InitialCpbRemovalDelay;
        task.initial_cpb_removal_offset = HRDPos.InitialCpbRemovalDelayOffset;

        task.QpY = mfxI8(mfx::clamp((mfxI32)quantCtrl.QpY + (-6 * sps.bit_depth_luma_minus8) * bNegativeQpAllowed, minQP, maxQP));
        sh.slice_qp_delta = mfxI8(task.QpY - (pps.init_qp_minus26 + 26));

        sh.temporal_mvp_enabled_flag &= !(par.AsyncDepth > 1 && task.NumRecode); // WA



        return MFX_ERR_NONE;
    });
}

void HevcEncTools::QueryTask(const FeatureBlocks& /*blocks*/, TPushQT Push)
{
    Push(BLK_Update
        , [this](
            StorageW& global
            , StorageW& s_task) -> mfxStatus
    {
        MFX_CHECK(IsOn(m_EncToolConfig.BRC) && m_pEncTools && m_pEncTools->Query, MFX_ERR_NONE);

        auto& task = Task::Common::Get(s_task);
        mfxEncToolsBRCStatus brcSts = {};

        auto sts = BRCUpdate(global, s_task, brcSts);
        MFX_CHECK_STS(sts);
        task.bSkip = false;

        switch (brcSts.FrameStatus.BRCStatus)
        {
        case MFX_BRC_OK:
            break;
        case MFX_BRC_PANIC_SMALL_FRAME:
            task.MinFrameSize = brcSts.FrameStatus.MinFrameSize;
            task.NumRecode++;

            sts = BRCUpdate(global, s_task, brcSts);
            MFX_CHECK_STS(sts);
            MFX_CHECK(brcSts.FrameStatus.BRCStatus == MFX_BRC_OK, MFX_ERR_UNDEFINED_BEHAVIOR);
            break;
        case MFX_BRC_PANIC_BIG_FRAME:
            task.bSkip = true;
        case MFX_BRC_BIG_FRAME:
        case MFX_BRC_SMALL_FRAME:
            task.bRecode = true;
            break;
        default:
            MFX_RETURN(MFX_ERR_UNDEFINED_BEHAVIOR);
        }

        task.bForceSync |= task.bSkip;

        return MFX_ERR_NONE;
    });
}

void HevcEncTools::FreeTask(const FeatureBlocks& /*blocks*/, TPushQT Push)
{
    Push(BLK_Discard
        , [this](StorageW& /*global*/, StorageW& s_task)->mfxStatus
    {
        MFX_CHECK(m_pEncTools && m_pEncTools->Discard, MFX_ERR_NONE);

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

        return m_pEncTools->Discard(m_pEncTools->Context, task.DisplayOrder);
    });
}

void HevcEncTools::Close(const FeatureBlocks& /*blocks*/, TPushCLS Push)
{
    Push(BLK_Close
        , [this](StorageW& /*global*/)
    {
        if (m_pEncTools && m_pEncTools->Close)
            m_pEncTools->Close(m_pEncTools->Context);
    });
}

#endif //defined(MFX_ENABLE_ENCTOOLS)
#endif //defined(MFX_ENABLE_H265_VIDEO_ENCODE)
