// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qquick3dtexturedata.h"
#include "qquick3dtexturedata_p.h"
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype TextureData
    \inherits Object3D
    \inqmlmodule QtQuick3D
    \nativetype QQuick3DTextureData
    \brief Base type for custom texture data.

    Custom texture data allows using application-generated texture data, that
    can possibly change dynamically as well. To use custom texture data set the
    \l{Texture::textureData}{textureData} property of \l {Texture} to reference
    a TextureData object.

    Custom Texture data is implemented in C++ by creating a QQuick3DTextureData
    instance, often subclassing it. The QQuick3DTextureData type is registered to
    QML under the name of TextureData. Once the subclass is registered to QML,
    Texture objects can start referencing it.

    An example of when to use this API is when there is a need to procedurally
    generate a texture at runtime rather than loading a static image from a file.

    \code
    import MyCustomTexture 1.0

    Model {
        source: "#Cube"
        materials: [
            DefaultMaterial {
                diffuseMap: diffuseMapCustomTexture
            }
        ]
    }

    Texture {
        id: diffuseMapCustomTexture
        textureData: MyCustomTextureData {

        }
    }

    \endcode

    \sa Texture
*/

/*!
    \class QQuick3DTextureData
    \inmodule QtQuick3D
    \inherits QQuick3DObject
    \since 6.0
    \brief Base class for defining custom texture data.

    The QQuick3DTextureData class can be used to specify custom texture data for a
    \l {Texture} in a Qt Quick 3D scene.

    While not strictly required, the typical usage is to inherit from this class.
    The subclass is then exposed to QML by registering it to the type system. The
    \l{Texture::textureData}{textureData} property of a \l {QtQuick3D::Texture}{Texture}
    can then be set to reference an instance of the registered type.

    Example implementation:

    \code
    class CustomTextureData : public QQuick3DTextureData
    {
        Q_OBJECT
        ... properties ...

    public:
        CustomTextureData() { regenerateTextureData(); }

        void setProperty(...)
        {
            // Change relevant internal data.
            // ...

            // Update the texture data
            regenerateTextureData();

            // Finally, trigger an update. This is relevant in case nothing else
            // is changed in the scene; this way we make sure a new frame will
            // be rendered
            update();
        }
    private:
        void regenerateTextureData()
        {
            QByteArray textureData;
            textureData = generateTextureData();
            setTextureData(textureData);
            setSize(QSize(256, 256));
            setFormat(QQuick3DTextureData::Format::RGBA8)
            setHasTransparency(true);
        }
    };
    \endcode

    This class can then be registered as a QML type and used with \l {QtQuick3D::Texture}{Texture}.

    In Qt 5 type registration happened with qmlRegisterType:
    \code
    qmlRegisterType<MyCustomTextureData>("Example", 1, 0, "MyCustomTextureData");
    \endcode

    In Qt 6 the default approach is to use automatic registration with the help
    of the build system. Instead of calling qmlRegisterType, the \c{.pro} file
    can now contain:

    \code
    CONFIG += qmltypes
    QML_IMPORT_NAME = Example
    QML_IMPORT_MAJOR_VERSION = 1
    \endcode

    With CMake, automatic registration is the default behavior, so no special
    settings are needed beyond basic QML module setup:
    \code
    qt_add_qml_module(application
        URI Example
        VERSION 1.0
    )
    \endcode

    The class implementation should add QML_NAMED_ELEMENT:

    \code
    class CustomTextureData : public QQuick3DTextureData
    {
        Q_OBJECT
        QML_NAMED_ELEMENT(MyCustomTextureData)
        ...
    };
    \endcode

    The QML code can then use the custom type:

    \code
    import Example 1.0

    Model {
        source: "#Cube"
        materials: [
            DefaultMaterial {
                diffuseMap: diffuseMapCustomTexture
            }
        ]
        Texture {
            id: diffuseMapCustomTexture
            textureData: MyCustomTextureData { }
        }
    }
    \endcode
*/

/*!
    \enum QQuick3DTextureData::Format
    Returns the color format of the texture data assigned in \l textureData property.

    \value None The color format is not defined
    \value RGBA8 The color format is considered as 8-bit integer in R, G, B and alpha channels.
    \value RGBA16F The color format is considered as 16-bit float in R,G,B and alpha channels.
    \value RGBA32F The color format is considered as 32-bit float in R, G, B and alpha channels.
    \value RGBE8 The color format is considered as 8-bit mantissa in the R, G, and B channels and 8-bit shared exponent.
    \value R8 The color format is considered as 8-bit integer in R channel.
    \value R16 The color format is considered as 16-bit integer in R channel.
    \value R16F The color format is considered as 16-bit float in R channel.
    \value R32F The color format is considered as 32-bit float R channel.
    \value BC1 The color format is considred as BC1 compressed format with R, G, B, and alpha channels.
    \value BC2 The color format is considred as BC2 compressed format with R, G, B, and alpha channels.
    \value BC3 The color format is considred as BC3 compressed format with R, G, B, and alpha channels.
    \value BC4 The color format is considred as BC4 compressed format with one color channel.
    \value BC5 The color format is considred as BC5 compressed format with two color channels.
    \value BC6H The color format is considred as BC6H compressed format with three high dynamic range color channels.
    \value BC7 The color format is considred as BC7 compressed format with R, G, B, and alpha channels.
    \value DXT1_RGBA The color format is considered as DXT1 compressed format with R, G, B and alpha channels.
    \value DXT1_RGB The color format is considered as DXT1 compressed format with R, G and B channels.
    \value DXT3_RGBA The color format is considered as DXT3 compressed format with R, G, B and alpha channels.
    \value DXT5_RGBA The color format is considered as DXT5 compressed format with R, G, B and alpha channels.
    \value ETC2_RGB8 The color format is considered as ETC2 compressed format for RGB888 data
    \value ETC2_RGB8A1 The color format is considered as ETC2 compressed format for RGBA data where alpha is 1-bit.
    \value ETC2_RGBA8 The color format is considered as ETC2 compressed format with RGBA8888 data.
    \value ASTC_4x4 The color format is considered as ASTC compressed format with 4x4 block footprint.
    \value ASTC_5x4 The color format is considered as ASTC compressed format with 5x4 block footprint.
    \value ASTC_5x5 The color format is considered as ASTC compressed format with 5x5 block footprint.
    \value ASTC_6x5 The color format is considered as ASTC compressed format with 6x5 block footprint.
    \value ASTC_6x6 The color format is considered as ASTC compressed format with 6x6 block footprint.
    \value ASTC_8x5 The color format is considered as ASTC compressed format with 8x5 block footprint.
    \value ASTC_8x6 The color format is considered as ASTC compressed format with 8x6 block footprint.
    \value ASTC_8x8 The color format is considered as ASTC compressed format with 8x8 block footprint.
    \value ASTC_10x5 The color format is considered as ASTC compressed format with 10x5 block footprint.
    \value ASTC_10x6 The color format is considered as ASTC compressed format with 10x6 block footprint.
    \value ASTC_10x8 The color format is considered as ASTC compressed format with 10x8 block footprint.
    \value ASTC_10x10 The color format is considered as ASTC compressed format with 10x10 block footprint.
    \value ASTC_12x10 The color format is considered as ASTC compressed format with 12x10 block footprint.
    \value ASTC_12x12 The color format is considered as ASTC compressed format with 12x12 block footprint.

    \note With the exception of \c RGBA8, not every format is supported at runtime as this
    depends on which backend is being used as well which hardware is being used.

    \note \c RGBE is internally represented as an \c RGBA8 but is intepreted as described when used
    as a lightProbe or skybox texture.

    \note Using the value \c None will assume the default value of \c RGBA8
*/


QQuick3DTextureDataPrivate::QQuick3DTextureDataPrivate()
    : QQuick3DObjectPrivate(QQuick3DTextureDataPrivate::Type::TextureData)
{

}

QQuick3DTextureData::QQuick3DTextureData(QQuick3DObject *parent)
    : QQuick3DObject(*new QQuick3DTextureDataPrivate, parent)
{

}

QQuick3DTextureData::~QQuick3DTextureData()
{

}

/*!
    Returns the current texture data defined by this item.
*/
const QByteArray QQuick3DTextureData::textureData() const
{
    const Q_D(QQuick3DTextureData);
    return d->textureData;

}

/*!
    Sets the texture data. The contents of \a data must respect the \l size and
    \l format properties as the backend will try and upload and use the data as if
    it were a texture of size and format, and if there is any deviation the result
    will be somewhere between incorrect rendering of the texture, or potentially a crash.
*/

void QQuick3DTextureData::setTextureData(const QByteArray &data)
{
    Q_D(QQuick3DTextureData);
    d->textureData = data;
    d->textureDataDirty = true;
    update();
}

/*!
    Returns the size of the texture data in pixels.
*/
QSize QQuick3DTextureData::size() const
{
    const Q_D(QQuick3DTextureData);
    return d->size;
}

/*!
    Sets the \a size of the texture data in pixels.
*/
void QQuick3DTextureData::setSize(const QSize &size)
{
    Q_D(QQuick3DTextureData);
    d->size = size;
    update();
}

/*!
    Returns the depth of the texture data in pixels.
*/
int QQuick3DTextureData::depth() const
{
    const Q_D(QQuick3DTextureData);
    return d->depth;
}

/*!
    Sets the \a depth of the texture data in pixels. Setting the depth above
    0 means that the texture is handled as a 3D texture.
*/
void QQuick3DTextureData::setDepth(int depth)
{
    Q_D(QQuick3DTextureData);
    d->depth = depth;
    update();
}

/*!
    Returns the format of the texture data.
*/
QQuick3DTextureData::Format QQuick3DTextureData::format() const
{
    const Q_D(QQuick3DTextureData);
    return d->format;
}

/*!
    Sets the \a format of the texture data.

    The default format is /c RGBA8
*/
void QQuick3DTextureData::setFormat(QQuick3DTextureData::Format format)
{
    Q_D(QQuick3DTextureData);
    d->format = format;
    update();
}

/*!
    Returns \c true if the texture data has transparency.

    The default value is \c false.
*/
bool QQuick3DTextureData::hasTransparency() const
{
    const Q_D(QQuick3DTextureData);
    return d->hasTransparency;
}

/*!
    Set \a hasTransparency to true if the texture data has an active alpha
    channel with non-opaque values.

    This is used as an optimization by the engine so that for formats that
    do support an alpha channel do not need to have each value checked for
    non-opaque values.
*/
void QQuick3DTextureData::setHasTransparency(bool hasTransparency)
{
    Q_D(QQuick3DTextureData);
    d->hasTransparency = hasTransparency;
    update();
}


static QSSGRenderTextureFormat::Format convertToBackendFormat(QQuick3DTextureData::Format format)
{
    switch (format) {
    case QQuick3DTextureData::None:
    case QQuick3DTextureData::RGBA8:
        return QSSGRenderTextureFormat::RGBA8;
    case QQuick3DTextureData::RGBA16F:
        return QSSGRenderTextureFormat::RGBA16F;
    case QQuick3DTextureData::RGBA32F:
        return QSSGRenderTextureFormat::RGBA32F;
    case QQuick3DTextureData::RGBE8:
        return QSSGRenderTextureFormat::RGBE8;
    case QQuick3DTextureData::R8:
        return QSSGRenderTextureFormat::R8;
    case QQuick3DTextureData::R16:
        return QSSGRenderTextureFormat::R16;
    case QQuick3DTextureData::R16F:
        return QSSGRenderTextureFormat::R16F;
    case QQuick3DTextureData::R32F:
        return QSSGRenderTextureFormat::R32F;
    case QQuick3DTextureData::BC1:
        return QSSGRenderTextureFormat::BC1;
    case QQuick3DTextureData::BC2:
        return QSSGRenderTextureFormat::BC2;
    case QQuick3DTextureData::BC3:
        return QSSGRenderTextureFormat::BC3;
    case QQuick3DTextureData::BC4:
        return QSSGRenderTextureFormat::BC4;
    case QQuick3DTextureData::BC5:
        return QSSGRenderTextureFormat::BC5;
    case QQuick3DTextureData::BC6H:
        return QSSGRenderTextureFormat::BC6H;
    case QQuick3DTextureData::BC7:
        return QSSGRenderTextureFormat::BC7;
    case QQuick3DTextureData::DXT1_RGBA:
        return QSSGRenderTextureFormat::RGBA_DXT1;
    case QQuick3DTextureData::DXT1_RGB:
        return QSSGRenderTextureFormat::RGB_DXT1;
    case QQuick3DTextureData::DXT3_RGBA:
        return QSSGRenderTextureFormat::RGBA_DXT3;
    case QQuick3DTextureData::DXT5_RGBA:
        return QSSGRenderTextureFormat::RGBA_DXT5;
    case QQuick3DTextureData::ETC2_RGB8:
        return QSSGRenderTextureFormat::RGB8_ETC2;
    case QQuick3DTextureData::ETC2_RGB8A1:
        return QSSGRenderTextureFormat::RGB8_PunchThrough_Alpha1_ETC2;
    case QQuick3DTextureData::ETC2_RGBA8:
        return QSSGRenderTextureFormat::RGBA8_ETC2_EAC;
    case QQuick3DTextureData::ASTC_4x4:
        return QSSGRenderTextureFormat::RGBA_ASTC_4x4;
    case QQuick3DTextureData::ASTC_5x4:
        return QSSGRenderTextureFormat::RGBA_ASTC_5x4;
    case QQuick3DTextureData::ASTC_5x5:
        return QSSGRenderTextureFormat::RGBA_ASTC_5x5;
    case QQuick3DTextureData::ASTC_6x5:
        return QSSGRenderTextureFormat::RGBA_ASTC_6x5;
    case QQuick3DTextureData::ASTC_6x6:
        return QSSGRenderTextureFormat::RGBA_ASTC_6x6;
    case QQuick3DTextureData::ASTC_8x5:
        return QSSGRenderTextureFormat::RGBA_ASTC_8x5;
    case QQuick3DTextureData::ASTC_8x6:
        return QSSGRenderTextureFormat::RGBA_ASTC_8x6;
    case QQuick3DTextureData::ASTC_8x8:
        return QSSGRenderTextureFormat::RGBA_ASTC_8x8;
    case QQuick3DTextureData::ASTC_10x5:
        return QSSGRenderTextureFormat::RGBA_ASTC_10x5;
    case QQuick3DTextureData::ASTC_10x6:
        return QSSGRenderTextureFormat::RGBA_ASTC_10x6;
    case QQuick3DTextureData::ASTC_10x8:
        return QSSGRenderTextureFormat::RGBA_ASTC_10x8;
    case QQuick3DTextureData::ASTC_10x10:
        return QSSGRenderTextureFormat::RGBA_ASTC_10x10;
    case QQuick3DTextureData::ASTC_12x10:
        return QSSGRenderTextureFormat::RGBA_ASTC_12x10;
    case QQuick3DTextureData::ASTC_12x12:
        return QSSGRenderTextureFormat::RGBA_ASTC_12x12;
    default:
        return QSSGRenderTextureFormat::RGBA8;
    }
}

/*!
    \internal
*/
QSSGRenderGraphObject *QQuick3DTextureData::updateSpatialNode(QSSGRenderGraphObject *node)
{
    Q_D(QQuick3DTextureData);
    if (!node) {
        markAllDirty();
        node = new QSSGRenderTextureData();
    }
    QQuick3DObject::updateSpatialNode(node);
    auto *textureData = static_cast<QSSGRenderTextureData*>(node);

    bool changed = false;

    // Use a dirty flag so we don't compare large buffer values
    if (d->textureDataDirty) {
        d->textureDataDirty = false;
        textureData->setTextureData(d->textureData);
        changed = true;
    }

    // Can't use qUpdateIfNeeded unfortunately
    if (d->size != textureData->size()) {
        textureData->setSize(d->size);
        changed = true;
    }

    if (d->depth != textureData->depth()) {
        textureData->setDepth(d->depth);
        changed = true;
    }

    QSSGRenderTextureFormat format = convertToBackendFormat(d->format);
    if (format != textureData->format()) {
        textureData->setFormat(format);
        changed = true;
    }

    if (d->hasTransparency != textureData->hasTransparency()) {
        textureData->setHasTransparency(d->hasTransparency);
        changed = true;
    }

    if (changed)
        emit textureDataNodeDirty();

    DebugViewHelpers::ensureDebugObjectName(textureData, this);

    return node;
}

void QQuick3DTextureData::markAllDirty()
{
    QQuick3DObject::markAllDirty();
}


QT_END_NAMESPACE
