/****************************************************************************
* Copyright (C) 2014-2015 Intel Corporation.   All Rights Reserved.
*
* 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 (including the next
* paragraph) 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 format_conversion.h
*
* @brief API implementation
*
******************************************************************************/
#include "format_types.h"
#include "format_traits.h"

//////////////////////////////////////////////////////////////////////////
/// @brief Load SIMD packed pixels in SOA format and converts to
///        SOA RGBA32_FLOAT format.
/// @param pSrc - source data in SOA form
/// @param dst - output data in SOA form
template<SWR_FORMAT SrcFormat>
INLINE void LoadSOA(const uint8_t *pSrc, simdvector &dst)
{
    // fast path for float32
    if ((FormatTraits<SrcFormat>::GetType(0) == SWR_TYPE_FLOAT) && (FormatTraits<SrcFormat>::GetBPC(0) == 32))
    {
        auto lambda = [&](int comp)
        {
            simdscalar vComp = _simd_load_ps((const float*)(pSrc + comp*sizeof(simdscalar)));

            dst.v[FormatTraits<SrcFormat>::swizzle(comp)] = vComp;
        };

        UnrollerL<0, FormatTraits<SrcFormat>::numComps, 1>::step(lambda);
        return;
    }

    auto lambda = [&](int comp)
    {
        // load SIMD components
        simdscalar vComp = FormatTraits<SrcFormat>::loadSOA(comp, pSrc);

        // unpack
        vComp = FormatTraits<SrcFormat>::unpack(comp, vComp);

        // convert
        if (FormatTraits<SrcFormat>::isNormalized(comp))
        {
            vComp = _simd_cvtepi32_ps(_simd_castps_si(vComp));
            vComp = _simd_mul_ps(vComp, _simd_set1_ps(FormatTraits<SrcFormat>::toFloat(comp)));
        }

        dst.v[FormatTraits<SrcFormat>::swizzle(comp)] = vComp;

        pSrc += (FormatTraits<SrcFormat>::GetBPC(comp) * KNOB_SIMD_WIDTH) / 8;
    };

    UnrollerL<0, FormatTraits<SrcFormat>::numComps, 1>::step(lambda);
}

//////////////////////////////////////////////////////////////////////////
/// @brief Clamps the given component based on the requirements on the 
///        Format template arg
/// @param vComp - SIMD vector of floats
/// @param Component - component
template<SWR_FORMAT Format>
INLINE simdscalar Clamp(simdscalar vComp, uint32_t Component)
{
    if (FormatTraits<Format>::isNormalized(Component))
    {
        if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_UNORM)
        {
            vComp = _simd_max_ps(vComp, _simd_setzero_ps());
        }

        if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_SNORM)
        {
            vComp = _simd_max_ps(vComp, _simd_set1_ps(-1.0f));
        }
        vComp = _simd_min_ps(vComp, _simd_set1_ps(1.0f));
    }
    else if (FormatTraits<Format>::GetBPC(Component) < 32)
    {
        if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_UINT)
        {
            int iMax = (1 << FormatTraits<Format>::GetBPC(Component)) - 1;
            int iMin = 0;
            simdscalari vCompi = _simd_castps_si(vComp);
            vCompi = _simd_max_epu32(vCompi, _simd_set1_epi32(iMin));
            vCompi = _simd_min_epu32(vCompi, _simd_set1_epi32(iMax));
            vComp = _simd_castsi_ps(vCompi);
        }
        else if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_SINT)
        {
            int iMax = (1 << (FormatTraits<Format>::GetBPC(Component) - 1)) - 1;
            int iMin = -1 - iMax;
            simdscalari vCompi = _simd_castps_si(vComp);
            vCompi = _simd_max_epi32(vCompi, _simd_set1_epi32(iMin));
            vCompi = _simd_min_epi32(vCompi, _simd_set1_epi32(iMax));
            vComp = _simd_castsi_ps(vCompi);
        }
    }

    return vComp;
}

//////////////////////////////////////////////////////////////////////////
/// @brief Normalize the given component based on the requirements on the
///        Format template arg
/// @param vComp - SIMD vector of floats
/// @param Component - component
template<SWR_FORMAT Format>
INLINE simdscalar Normalize(simdscalar vComp, uint32_t Component)
{
    if (FormatTraits<Format>::isNormalized(Component))
    {
        vComp = _simd_mul_ps(vComp, _simd_set1_ps(FormatTraits<Format>::fromFloat(Component)));
        vComp = _simd_castsi_ps(_simd_cvtps_epi32(vComp));
    }
    return vComp;
}

//////////////////////////////////////////////////////////////////////////
/// @brief Convert and store simdvector of pixels in SOA
///        RGBA32_FLOAT to SOA format
/// @param src - source data in SOA form
/// @param dst - output data in SOA form
template<SWR_FORMAT DstFormat>
INLINE void StoreSOA(const simdvector &src, uint8_t *pDst)
{
    // fast path for float32
    if ((FormatTraits<DstFormat>::GetType(0) == SWR_TYPE_FLOAT) && (FormatTraits<DstFormat>::GetBPC(0) == 32))
    {
        for (uint32_t comp = 0; comp < FormatTraits<DstFormat>::numComps; ++comp)
        {
            simdscalar vComp = src.v[FormatTraits<DstFormat>::swizzle(comp)];

            // Gamma-correct
            if (FormatTraits<DstFormat>::isSRGB)
            {
                if (comp < 3)  // Input format is always RGBA32_FLOAT.
                {
                    vComp = FormatTraits<R32G32B32A32_FLOAT>::convertSrgb(comp, vComp);
                }
            }

            _simd_store_ps((float*)(pDst + comp*sizeof(simdscalar)), vComp);
        }
        return;
    }

    auto lambda = [&](int comp)
    {
        simdscalar vComp = src.v[FormatTraits<DstFormat>::swizzle(comp)];

        // Gamma-correct
        if (FormatTraits<DstFormat>::isSRGB)
        {
            if (comp < 3)  // Input format is always RGBA32_FLOAT.
            {
                vComp = FormatTraits<R32G32B32A32_FLOAT>::convertSrgb(comp, vComp);
            }
        }

        // clamp
        vComp = Clamp<DstFormat>(vComp, comp);

        // normalize
        vComp = Normalize<DstFormat>(vComp, comp);

        // pack
        vComp = FormatTraits<DstFormat>::pack(comp, vComp);

        // store
        FormatTraits<DstFormat>::storeSOA(comp, pDst, vComp);

        pDst += (FormatTraits<DstFormat>::GetBPC(comp) * KNOB_SIMD_WIDTH) / 8;
    };

    UnrollerL<0, FormatTraits<DstFormat>::numComps, 1>::step(lambda);
}

#if ENABLE_AVX512_SIMD16
//////////////////////////////////////////////////////////////////////////
/// @brief Load SIMD packed pixels in SOA format and converts to
///        SOA RGBA32_FLOAT format.
/// @param pSrc - source data in SOA form
/// @param dst - output data in SOA form
template<SWR_FORMAT SrcFormat>
INLINE void SIMDCALL LoadSOA(const uint8_t *pSrc, simd16vector &dst)
{
    // fast path for float32
    if ((FormatTraits<SrcFormat>::GetType(0) == SWR_TYPE_FLOAT) && (FormatTraits<SrcFormat>::GetBPC(0) == 32))
    {
        auto lambda = [&](int comp)
        {
            simd16scalar vComp = _simd16_load_ps(reinterpret_cast<const float *>(pSrc + comp * sizeof(simd16scalar)));

            dst.v[FormatTraits<SrcFormat>::swizzle(comp)] = vComp;
        };

        UnrollerL<0, FormatTraits<SrcFormat>::numComps, 1>::step(lambda);
        return;
    }

    auto lambda = [&](int comp)
    {
        // load SIMD components
        simd16scalar vComp = FormatTraits<SrcFormat>::loadSOA_16(comp, pSrc);

        // unpack
        vComp = FormatTraits<SrcFormat>::unpack(comp, vComp);

        // convert
        if (FormatTraits<SrcFormat>::isNormalized(comp))
        {
            vComp = _simd16_cvtepi32_ps(_simd16_castps_si(vComp));
            vComp = _simd16_mul_ps(vComp, _simd16_set1_ps(FormatTraits<SrcFormat>::toFloat(comp)));
        }

        dst.v[FormatTraits<SrcFormat>::swizzle(comp)] = vComp;

        pSrc += (FormatTraits<SrcFormat>::GetBPC(comp) * KNOB_SIMD16_WIDTH) / 8;
    };

    UnrollerL<0, FormatTraits<SrcFormat>::numComps, 1>::step(lambda);
}

//////////////////////////////////////////////////////////////////////////
/// @brief Clamps the given component based on the requirements on the 
///        Format template arg
/// @param vComp - SIMD vector of floats
/// @param Component - component
template<SWR_FORMAT Format>
INLINE simd16scalar SIMDCALL Clamp(simd16scalar vComp, uint32_t Component)
{
    if (FormatTraits<Format>::isNormalized(Component))
    {
        if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_UNORM)
        {
            vComp = _simd16_max_ps(vComp, _simd16_setzero_ps());
        }

        if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_SNORM)
        {
            vComp = _simd16_max_ps(vComp, _simd16_set1_ps(-1.0f));
        }
        vComp = _simd16_min_ps(vComp, _simd16_set1_ps(1.0f));
    }
    else if (FormatTraits<Format>::GetBPC(Component) < 32)
    {
        if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_UINT)
        {
            int iMax = (1 << FormatTraits<Format>::GetBPC(Component)) - 1;
            int iMin = 0;
            simd16scalari vCompi = _simd16_castps_si(vComp);
            vCompi = _simd16_max_epu32(vCompi, _simd16_set1_epi32(iMin));
            vCompi = _simd16_min_epu32(vCompi, _simd16_set1_epi32(iMax));
            vComp = _simd16_castsi_ps(vCompi);
        }
        else if (FormatTraits<Format>::GetType(Component) == SWR_TYPE_SINT)
        {
            int iMax = (1 << (FormatTraits<Format>::GetBPC(Component) - 1)) - 1;
            int iMin = -1 - iMax;
            simd16scalari vCompi = _simd16_castps_si(vComp);
            vCompi = _simd16_max_epi32(vCompi, _simd16_set1_epi32(iMin));
            vCompi = _simd16_min_epi32(vCompi, _simd16_set1_epi32(iMax));
            vComp = _simd16_castsi_ps(vCompi);
        }
    }

    return vComp;
}

//////////////////////////////////////////////////////////////////////////
/// @brief Normalize the given component based on the requirements on the
///        Format template arg
/// @param vComp - SIMD vector of floats
/// @param Component - component
template<SWR_FORMAT Format>
INLINE simd16scalar SIMDCALL Normalize(simd16scalar vComp, uint32_t Component)
{
    if (FormatTraits<Format>::isNormalized(Component))
    {
        vComp = _simd16_mul_ps(vComp, _simd16_set1_ps(FormatTraits<Format>::fromFloat(Component)));
        vComp = _simd16_castsi_ps(_simd16_cvtps_epi32(vComp));
    }
    return vComp;
}

//////////////////////////////////////////////////////////////////////////
/// @brief Convert and store simdvector of pixels in SOA
///        RGBA32_FLOAT to SOA format
/// @param src - source data in SOA form
/// @param dst - output data in SOA form
template<SWR_FORMAT DstFormat>
INLINE void SIMDCALL StoreSOA(const simd16vector &src, uint8_t *pDst)
{
    // fast path for float32
    if ((FormatTraits<DstFormat>::GetType(0) == SWR_TYPE_FLOAT) && (FormatTraits<DstFormat>::GetBPC(0) == 32))
    {
        for (uint32_t comp = 0; comp < FormatTraits<DstFormat>::numComps; ++comp)
        {
            simd16scalar vComp = src.v[FormatTraits<DstFormat>::swizzle(comp)];

            // Gamma-correct
            if (FormatTraits<DstFormat>::isSRGB)
            {
                if (comp < 3)  // Input format is always RGBA32_FLOAT.
                {
                    vComp = FormatTraits<R32G32B32A32_FLOAT>::convertSrgb(comp, vComp);
                }
            }

            _simd16_store_ps(reinterpret_cast<float *>(pDst + comp * sizeof(simd16scalar)), vComp);
        }
        return;
    }

    auto lambda = [&](int comp)
    {
        simd16scalar vComp = src.v[FormatTraits<DstFormat>::swizzle(comp)];

        // Gamma-correct
        if (FormatTraits<DstFormat>::isSRGB)
        {
            if (comp < 3)  // Input format is always RGBA32_FLOAT.
            {
                vComp = FormatTraits<R32G32B32A32_FLOAT>::convertSrgb(comp, vComp);
            }
        }

        // clamp
        vComp = Clamp<DstFormat>(vComp, comp);

        // normalize
        vComp = Normalize<DstFormat>(vComp, comp);

        // pack
        vComp = FormatTraits<DstFormat>::pack(comp, vComp);

        // store
        FormatTraits<DstFormat>::storeSOA(comp, pDst, vComp);

        pDst += (FormatTraits<DstFormat>::GetBPC(comp) * KNOB_SIMD16_WIDTH) / 8;
    };

    UnrollerL<0, FormatTraits<DstFormat>::numComps, 1>::step(lambda);
}

#endif
