/*
* Copyright (c) 2020-2021, 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.
*/
//!
//! \file     decode_hevc_downsampling_packet.cpp
//! \brief    Defines the interface for hevc decode down sampling sub packet
//!
#include "decode_hevc_downsampling_packet.h"
#include "decode_hevc_basic_feature.h"

#ifdef _DECODE_PROCESSING_SUPPORTED

namespace decode
{

HevcDownSamplingPkt::HevcDownSamplingPkt(DecodePipeline *pipeline, CodechalHwInterface *hwInterface)
    : DecodeDownSamplingPkt(pipeline, hwInterface)
{}

MOS_STATUS HevcDownSamplingPkt::Init()
{
    DECODE_CHK_STATUS(DecodeDownSamplingPkt::Init());

#if (_DEBUG || _RELEASE_INTERNAL)
    m_dbgOvrdWidthInMinCb =
        ReadUserFeature(__MEDIA_USER_FEATURE_VALUE_SCALABILITY_OVERRIDE_SPLIT_WIDTH_IN_MINCB, m_osInterface->pOsContext).u32Data ?
        true : false;
#endif

    return MOS_STATUS_SUCCESS;
}

MOS_STATUS HevcDownSamplingPkt::SetSfcMode(MEDIA_SFC_INTERFACE_MODE &mode)
{
    mode.veboxSfcEnabled = 0;
    mode.vdboxSfcEnabled = 1;
    return MOS_STATUS_SUCCESS;
}

MOS_STATUS HevcDownSamplingPkt::InitSfcParams(VDBOX_SFC_PARAMS &sfcParams)
{
    DECODE_FUNC_CALL();

    DECODE_CHK_STATUS(DecodeDownSamplingPkt::InitSfcParams(sfcParams));

    sfcParams.input.width  = m_basicFeature->m_width;
    sfcParams.input.height = m_basicFeature->m_height;

    HevcBasicFeature *hevcBasicFeature = dynamic_cast<HevcBasicFeature *>(m_basicFeature);
    DECODE_CHK_NULL(hevcBasicFeature);
    sfcParams.videoParams.hevc.lcuSize = hevcBasicFeature->m_ctbSize;

    SCALABILITY_PARAMS &scalabilityParams = sfcParams.videoParams.scalabilityParams;
    if (m_pipeline->GetPipeNum() <= 1)
    {
        scalabilityParams.numPipe    = 1;
        scalabilityParams.curPipe    = 0;
        scalabilityParams.engineMode = MhwSfcInterface::sfcScalabModeSingle;
    }
    else
    {
        DECODE_CHK_STATUS(InitSfcScalabParams(scalabilityParams));
    }

    return MOS_STATUS_SUCCESS;
}

MOS_STATUS HevcDownSamplingPkt::InitSfcScalabParams(SCALABILITY_PARAMS &scalabilityParams)
{
    DECODE_FUNC_CALL();

    HevcPipeline *hevcPipeline = dynamic_cast<HevcPipeline *>(m_pipeline);
    DECODE_CHK_NULL(hevcPipeline);
    HevcBasicFeature *hevcBasicFeature = dynamic_cast<HevcBasicFeature *>(m_basicFeature);
    DECODE_CHK_NULL(hevcBasicFeature);

    CodecRectangle &inputSurfaceRegion  = m_downSampling->m_inputSurfaceRegion;
    CodecRectangle &outputSurfaceRegion = m_downSampling->m_outputSurfaceRegion;

    auto curPipe = hevcPipeline->GetCurrentPipe();
    auto numPipe = hevcPipeline->GetPipeNum();
    auto curPass = hevcPipeline->GetCurrentPass();

    scalabilityParams.numPipe = numPipe;
    scalabilityParams.curPipe = curPipe;

    if (curPipe == 0)
    {
        scalabilityParams.engineMode = MhwSfcInterface::sfcScalabModeLeftCol;
    }
    else if (curPipe == numPipe - 1)
    {
        scalabilityParams.engineMode = MhwSfcInterface::sfcScalabModeRightCol;
    }
    else
    {
        scalabilityParams.engineMode = MhwSfcInterface::sfcScalabModeMiddleCol;
    }

    uint32_t tileColIndex = 0;
    uint32_t tileColCount = 0;

    DECODE_CHK_STATUS(InitSfcScalabSrcParams(
        *hevcPipeline, *hevcBasicFeature, scalabilityParams, tileColIndex, tileColCount));

    DECODE_CHK_STATUS(InitSfcScalabDstParams(
        *hevcPipeline, *hevcBasicFeature, scalabilityParams, tileColIndex, tileColCount));

    return MOS_STATUS_SUCCESS;
}

MOS_STATUS HevcDownSamplingPkt::InitSfcScalabSrcParams(
    HevcPipeline &hevcPipeline, HevcBasicFeature &hevcBasicFeature,
    SCALABILITY_PARAMS &scalabilityParams, uint32_t &tileColIndex, uint32_t &tileColCount)
{
    DECODE_FUNC_CALL();

    CodecRectangle &inputSurfaceRegion  = m_downSampling->m_inputSurfaceRegion;
    CodecRectangle &outputSurfaceRegion = m_downSampling->m_outputSurfaceRegion;

    auto curPipe = hevcPipeline.GetCurrentPipe();
    auto numPipe = hevcPipeline.GetPipeNum();
    auto curPass = hevcPipeline.GetCurrentPass();

    if (hevcPipeline.GetDecodeMode() == HevcPipeline::realTileDecodeMode)
    {
        scalabilityParams.tileType = MhwSfcInterface::sfcScalabRealTile;

        DECODE_CHK_NULL(hevcBasicFeature.m_hevcPicParams);
        PCODEC_HEVC_PIC_PARAMS hevcPicParams = hevcBasicFeature.m_hevcPicParams;
        tileColIndex = curPipe + curPass * numPipe;
        tileColCount = hevcPicParams->num_tile_columns_minus1 + 1;

        if (hevcPicParams->uniform_spacing_flag)
        {
            scalabilityParams.srcStartX = tileColIndex * hevcBasicFeature.m_widthInCtb / tileColCount *
                hevcBasicFeature.m_ctbSize;
            scalabilityParams.srcEndX   = (tileColIndex + 1) * hevcBasicFeature.m_widthInCtb / tileColCount *
                hevcBasicFeature.m_ctbSize - 1;
        }
        else
        {
            scalabilityParams.srcStartX = 0;
            for (uint32_t i = 0; i < tileColIndex; i++)
            {
                scalabilityParams.srcStartX +=
                    (hevcPicParams->column_width_minus1[i] + 1) * hevcBasicFeature.m_ctbSize;
            }
            if (tileColIndex == hevcPicParams->num_tile_columns_minus1)
            {
                scalabilityParams.srcEndX = inputSurfaceRegion.m_x + inputSurfaceRegion.m_width - 1;
            }
            else
            {
                scalabilityParams.srcEndX = scalabilityParams.srcStartX +
                    (hevcPicParams->column_width_minus1[tileColIndex] + 1) * hevcBasicFeature.m_ctbSize - 1;
            }
        }
    }
    else if (hevcPipeline.GetDecodeMode() == HevcPipeline::virtualTileDecodeMode)
    {
        scalabilityParams.tileType = MhwSfcInterface::sfcScalabVirtualTile;

        tileColIndex = curPipe;
        tileColCount = numPipe;

        scalabilityParams.srcStartX = (tileColIndex * hevcBasicFeature.m_widthInCtb) / tileColCount *
            hevcBasicFeature.m_ctbSize;
        if (tileColIndex == tileColCount - 1)
        {
            scalabilityParams.srcEndX = inputSurfaceRegion.m_x + inputSurfaceRegion.m_width - 1;
        }
        else
        {
            scalabilityParams.srcEndX = (tileColIndex + 1) * hevcBasicFeature.m_widthInCtb / tileColCount *
                hevcBasicFeature.m_ctbSize - 1;
        }
#if (_DEBUG || _RELEASE_INTERNAL)
        if (m_dbgOvrdWidthInMinCb > 0 && numPipe == 2)
        {
            if (curPipe == 1)
            {
                scalabilityParams.srcStartX = m_dbgOvrdWidthInMinCb * hevcBasicFeature.m_minCtbSize;
                scalabilityParams.srcEndX   = inputSurfaceRegion.m_x + inputSurfaceRegion.m_width - 1;
            }
            else
            {
                scalabilityParams.srcStartX = 0;
                scalabilityParams.srcEndX   = m_dbgOvrdWidthInMinCb * hevcBasicFeature.m_minCtbSize - 1;
            }
        }
#endif
    }
    else
    {
        DECODE_ASSERTMESSAGE("Invalid decode mode, expect real tile mode and virtual tile mode.");
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // Clamp srcStartX, srcEndX into source region
    if (scalabilityParams.srcStartX < inputSurfaceRegion.m_x)
    {
        scalabilityParams.srcStartX = inputSurfaceRegion.m_x;
    }
    if (scalabilityParams.srcEndX > inputSurfaceRegion.m_x + inputSurfaceRegion.m_width - 1)
    {
        scalabilityParams.srcEndX = inputSurfaceRegion.m_x + inputSurfaceRegion.m_width - 1;
    }

    return MOS_STATUS_SUCCESS;
}

MOS_STATUS HevcDownSamplingPkt::InitSfcScalabDstParams(
    HevcPipeline &hevcPipeline, HevcBasicFeature &hevcBasicFeature,
    SCALABILITY_PARAMS &scalabilityParams, const uint32_t &tileColIndex, const uint32_t &tileColCount)
{
    DECODE_FUNC_CALL();

    CodecRectangle &inputSurfaceRegion  = m_downSampling->m_inputSurfaceRegion;
    CodecRectangle &outputSurfaceRegion = m_downSampling->m_outputSurfaceRegion;

    if (tileColIndex == 0)
    {
        m_firstValidTileIndex = 0;
        m_lastValidTileIndex  = tileColCount - 1;
        m_dstXLandingCount    = 0;
    }

    // start ildb offset correction
    uint32_t srcEndXCorr = scalabilityParams.srcEndX - m_ildbXOffset;

    uint32_t tileOffsetX;
    uint32_t tileEndX;
    if ((inputSurfaceRegion.m_x + inputSurfaceRegion.m_width - 1) <= srcEndXCorr)
    {
        tileOffsetX = 0;
        tileEndX    = inputSurfaceRegion.m_x + inputSurfaceRegion.m_width;
    }
    else
    {
        tileOffsetX = (hevcBasicFeature.m_hevcPicParams->chroma_format_idc == HCP_CHROMA_FORMAT_YUV444) ?
            m_tileOffsetX444 : m_tileOffsetXBasic;
        tileEndX    = srcEndXCorr;
    }

    uint32_t dstEndX = 0;
    if ((srcEndXCorr - inputSurfaceRegion.m_x) >= (tileOffsetX + 1))
    {
        constexpr int32_t phaseShiftMin = -(1 << (4 + 19));
        constexpr int32_t phaseShiftMax = (1 << (4 + 19)) - 1;
        int32_t xPhaseShift = MOS_F_ROUND((double(inputSurfaceRegion.m_width) / outputSurfaceRegion.m_width - 1.0) / 2.0 * 524288.0F);
        uint32_t xPhaseShiftClamped = MOS_CLAMP_MIN_MAX(xPhaseShift, phaseShiftMin, phaseShiftMax);
        uint64_t oneBySf = uint64_t(double(uint64_t(inputSurfaceRegion.m_width) * 524288 / outputSurfaceRegion.m_width));

        while (true)
        {
            if (m_dstXLandingCount == 0)
            {
                m_firstValidTileIndex = tileColIndex;
            }

            double tempDestCntx = (((double)m_dstXLandingCount * (double)oneBySf) + xPhaseShiftClamped);
            if (tempDestCntx < 0)
            {
                tempDestCntx = 0;
            }
            double xLandingPoint = double((tempDestCntx + (double(1 << (m_oneBySfFractionPrecision - m_betaPrecision - 1)))) / 524288
                + inputSurfaceRegion.m_x);

            if (xLandingPoint >= double(tileEndX - tileOffsetX))
            {
                dstEndX = m_dstXLandingCount - 1;
                break;
            }
            else
            {
                m_dstXLandingCount++;
            }
        }
    }

    if (tileOffsetX == 0)
    {
        m_lastValidTileIndex = tileColIndex;
    }

    // Last column end at destination region right border.
    if (tileColIndex == m_lastValidTileIndex)
    {
        dstEndX = outputSurfaceRegion.m_x + outputSurfaceRegion.m_width - 1;
    }

    if (tileColIndex <= m_firstValidTileIndex)
    {
        scalabilityParams.dstStartX = 0;
        scalabilityParams.dstEndX   = dstEndX;
    }
    else if (tileColIndex <= m_lastValidTileIndex)
    {
        scalabilityParams.dstStartX = m_lastDstEndX + 1;
        scalabilityParams.dstEndX   = dstEndX;
    }
    else
    {
        scalabilityParams.dstStartX = 0;
        scalabilityParams.dstEndX   = 0;
    }

    m_lastDstEndX = dstEndX;

    return MOS_STATUS_SUCCESS;
}

}

#endif  // !_DECODE_PROCESSING_SUPPORTED
