/****************************************************************************
**
** Copyright (C) 2008-2012 NVIDIA Corporation.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtQuick3DRender/private/qssgrendercontext_p.h>
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <QtQuick/QSGTexture>

QT_BEGIN_NAMESPACE

QSSGRenderTexture2D::QSSGRenderTexture2D(const QSSGRef<QSSGRenderContext> &context)
    : QSSGRenderTextureBase(context, QSSGRenderTextureTargetType::Texture2D), m_width(0), m_height(0)
{
}

QSSGRenderTexture2D::QSSGRenderTexture2D(const QSSGRef<QSSGRenderContext> &context, QSGTexture *qsgTexture)
    : QSSGRenderTextureBase(context, QSSGRenderTextureTargetType::Texture2D, false),
      m_width(qsgTexture->textureSize().width()),
      m_height(qsgTexture->textureSize().height())
{
    Q_ASSERT(!m_ownsTexture);
    Q_ASSERT(!m_handle);
    m_handle = reinterpret_cast<QSSGRenderBackend::QSSGRenderBackendTextureObject>(quintptr(qsgTexture->textureId()));
    m_texTarget = QSSGRenderTextureTargetType::Texture2D;

    m_format = qsgTexture->hasAlphaChannel() ? QSSGRenderTextureFormat::RGBA8
                                             : QSSGRenderTextureFormat::RGB8;
    m_sampleCount = 1; // TODO
}

QSSGRenderTexture2D::~QSSGRenderTexture2D()
{
}

QSSGTextureDetails QSSGRenderTexture2D::textureDetails() const
{
    return QSSGTextureDetails(m_width, m_height, 0, m_sampleCount, m_format);
}

void QSSGRenderTexture2D::setTextureData(QSSGByteView newBuffer,
                                           quint8 inMipLevel,
                                           qint32 width,
                                           qint32 height,
                                           QSSGRenderTextureFormat format,
                                           QSSGRenderTextureFormat formatDest)
{
    Q_ASSERT(m_handle);

    // check if we should compress this texture

    if (inMipLevel == 0) {
        m_width = width;
        m_height = height;
        m_format = format;

        // We re-use textures and this might have been a MSAA texture before
        // for resue we must completely destroy the texture object and create a new one
        // The same is true for immutable textures
        if (m_texTarget == QSSGRenderTextureTargetType::Texture2D_MS || m_immutable) {
            m_backend->releaseTexture(m_handle);
            m_texTarget = QSSGRenderTextureTargetType::Texture2D;
            m_sampleCount = 1;
            m_handle = m_backend->createTexture();
        }

        if (formatDest.isCompressedTextureFormat()) {
            bool compress = format.isUncompressedTextureFormat();
            bool appropriateSizes = !((width % 4) || (height % 4));

            // we only compress multiple of 4 textures
            if (compress && !appropriateSizes)
                compress = false;

            if (compress) {
                // This seems like a very dubious line here.  If we are compressing then the
                // image
                // is really 1/4 the width and height? - CN
                m_width = width / 4;
                m_height = height / 4;
                m_format = formatDest;
            }
        } else if (formatDest.isUncompressedTextureFormat()) {
            m_format = formatDest;
        }
    }

    if (m_maxMipLevel < inMipLevel) {
        m_maxMipLevel = inMipLevel;
    }

    // get max size and check value
    qint32 maxWidth, maxHeight;
    m_context->maxTextureSize(maxWidth, maxHeight);
    if (width > maxWidth || height > maxHeight) {
        qCCritical(RENDER_INVALID_OPERATION, "Width or height is greater than max texture size (%d, %d)", maxWidth, maxHeight);
    }
    if (format.isUncompressedTextureFormat() || format.isDepthTextureFormat()) {
        m_backend->setTextureData2D(m_handle,
                                    m_texTarget,
                                    inMipLevel,
                                    m_format,
                                    width,
                                    height,
                                    0,
                                    format,
                                    newBuffer);
    } else if (format.isCompressedTextureFormat()) {
        m_backend->setCompressedTextureData2D(m_handle,
                                              m_texTarget,
                                              inMipLevel,
                                              format,
                                              width,
                                              height,
                                              0,
                                              newBuffer);
    }
    // Set our texture parameters to a default that will look the best
    if (inMipLevel > 0)
        setMinFilter(QSSGRenderTextureMinifyingOp::LinearMipmapLinear);
}

void QSSGRenderTexture2D::setTextureStorage(qint32 inLevels,
                                              qint32 width,
                                              qint32 height,
                                              QSSGRenderTextureFormat formaInternal,
                                              QSSGRenderTextureFormat format,
                                              QSSGByteView dataBuffer)
{
    Q_ASSERT(m_handle);

    if (!m_context->supportsShaderImageLoadStore()) {
        qCCritical(RENDER_INVALID_OPERATION, "The extension Shader_Image_Load_Store is not supported");
        return;
    }

    m_width = width;
    m_height = height;
    m_format = formaInternal;
    if (format == QSSGRenderTextureFormat::Unknown)
        format = formaInternal;

    // get max size and check value
    qint32 maxWidth, maxHeight;
    m_context->maxTextureSize(maxWidth, maxHeight);
    if (width > maxWidth || height > maxHeight) {
        qCCritical(RENDER_INVALID_OPERATION, "Width or height is greater than max texture size (%d, %d)", maxWidth, maxHeight);
    }

    if (inLevels < 1) {
        qCCritical(RENDER_INVALID_PARAMETER, "inLevels is less than 1 (%d)", inLevels);
    }

    m_maxMipLevel = inLevels - 1; // we count from 0

    // only uncompressed formats are supported and no depth
    if (formaInternal.isUncompressedTextureFormat()) {
        m_backend->createTextureStorage2D(m_handle, m_texTarget, inLevels, formaInternal, width, height);

        m_immutable = true;
        m_texTarget = QSSGRenderTextureTargetType::Texture2D;

        if (dataBuffer.size() > 0)
            m_backend->setTextureSubData2D(m_handle, m_texTarget, 0, 0, 0, width, height, format, dataBuffer);

        if (inLevels > 1)
            setMinFilter(QSSGRenderTextureMinifyingOp::LinearMipmapLinear);
    }
}

void QSSGRenderTexture2D::setTextureDataMultisample(qint32 sampleCount,
                                                      qint32 width,
                                                      qint32 height,
                                                      QSSGRenderTextureFormat format)
{
    Q_ASSERT(m_handle);
    Q_ASSERT(m_maxMipLevel == 0);

    m_texTarget = QSSGRenderTextureTargetType::Texture2D_MS;

    qint32 maxWidth, maxHeight;
    m_context->maxTextureSize(maxWidth, maxHeight);
    if (width > maxWidth || height > maxHeight) {
        qCCritical(RENDER_INVALID_OPERATION, "Width or height is greater than max texture size (%d, %d)", maxWidth, maxHeight);
    }

    Q_ASSERT(format.isUncompressedTextureFormat()
             || format.isDepthTextureFormat());

    m_backend->setMultisampledTextureData2D(m_handle, m_texTarget, sampleCount, format, width, height, true);

    m_width = width;
    m_height = height;
    m_sampleCount = sampleCount;
    m_format = format;
}

void QSSGRenderTexture2D::setTextureSubData(QSSGByteView newBuffer,
                                              quint8 inMipLevel,
                                              qint32 inXOffset,
                                              qint32 inYOffset,
                                              qint32 width,
                                              qint32 height,
                                              QSSGRenderTextureFormat format)
{
    Q_ASSERT(m_handle);
    Q_ASSERT(inXOffset >= 0 && inYOffset >= 0 && width >= 0 && height >= 0);

    if (!format.isUncompressedTextureFormat()) {
        qCCritical(RENDER_INVALID_PARAMETER, "Cannot set sub data for depth or compressed formats");
        Q_ASSERT(false);
        return;
    }
    qint32 subRectStride = width * format.getSizeofFormat();
    if (qint32(newBuffer.size()) < subRectStride * height) {
        qCCritical(RENDER_INVALID_PARAMETER, "Invalid sub rect buffer size");
        Q_ASSERT(false);
        return;
    }
    // nop
    if (width == 0 || height == 0)
        return;

    if (inXOffset + width > m_width || inYOffset + height > m_height) {
        qCCritical(RENDER_INVALID_PARAMETER, "Sub rect outside existing image bounds");
        Q_ASSERT(false);
        return;
    }

    // not handled yet
    Q_ASSERT(!format.isDepthTextureFormat());

    m_backend->setTextureSubData2D(m_handle,
                                   m_texTarget,
                                   inMipLevel,
                                   inXOffset,
                                   inYOffset,
                                   width,
                                   height,
                                   format,
                                   newBuffer);
}

void QSSGRenderTexture2D::generateMipmaps(QSSGRenderHint genType)
{
    applyTexParams();
    m_backend->generateMipMaps(m_handle, m_texTarget, genType);
    qint32 maxDim = (m_width >= m_height) ? m_width : m_height;
    m_maxMipLevel = qint32(float(std::log(maxDim)) / std::log(2.0f));
    // we never create more level than m_maxLevel
    m_maxMipLevel = qMin(m_maxMipLevel, m_maxLevel);
}

void QSSGRenderTexture2D::bind()
{
    m_textureUnit = m_context->nextTextureUnit();

    m_backend->bindTexture(m_handle, m_texTarget, m_textureUnit);

    applyTexParams();
    applyTexSwizzle();
}

void QSSGRenderTexture2D::applyTextureParams()
{
    m_backend->bindTexture(m_handle, m_texTarget, 0);
    applyTexParams();
    applyTexSwizzle();
    m_backend->bindTexture(0, m_texTarget, 0);
}

QT_END_NAMESPACE
