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

#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>

QT_BEGIN_NAMESPACE

static const QVector3D g_fullScreenRectFaces[] = {
    QVector3D(-1, -1, 0),
    QVector3D(-1, 1, 0),
    QVector3D(1, 1, 0),
    QVector3D(1, -1, 0),

    QVector3D(-1, -1, 1),
    QVector3D(-1, 1, 1),
    QVector3D(1, 1, 1),
    QVector3D(1, -1, 1),

    QVector3D(-1, -1, -1),
    QVector3D(-1, 1, -1),
    QVector3D(1, 1, -1),
    QVector3D(1, -1, -1),
};

static const QVector2D g_fullScreenRectUVs[] = {
    QVector2D(0, 0),
    QVector2D(0, 1),
    QVector2D(1, 1),
    QVector2D(1, 0)
};

static const quint16 g_rectIndex[] = {
    0, 1, 2, 0, 2, 3, // front face - 0, 1, 2, 3
    0, 4, 5, 0, 5, 1, // left face - 0, 4, 5, 1
    1, 5, 6, 1, 6, 2, // top face - 1, 5, 6, 2
    3, 2, 6, 3, 6, 7, // right face - 3, 2, 6, 7
    0, 3, 7, 0, 7, 4, // bottom face - 0, 3, 7, 4
    7, 6, 5, 7, 5, 4  // back face - 7, 6, 5, 4
};

void QSSGRhiQuadRenderer::ensureBuffers(QSSGRhiContext *rhiCtx, QRhiResourceUpdateBatch *rub)
{
    if (!m_vbuf) {
        constexpr int vertexCount = 8;
        m_vbuf = std::make_shared<QSSGRhiBuffer>(*rhiCtx,
                                                 QRhiBuffer::Immutable,
                                                 QRhiBuffer::VertexBuffer,
                                                 quint32(5 * sizeof(float)),
                                                 5 * vertexCount * sizeof(float));
        m_vbuf->buffer()->setName(QByteArrayLiteral("quad vertex buffer"));
        float buf[5 * vertexCount];
        float *p = buf;
        for (int i = 0; i < vertexCount; ++i) {
            *p++ = g_fullScreenRectFaces[i].x();
            *p++ = g_fullScreenRectFaces[i].y();
            *p++ = g_fullScreenRectFaces[i].z();
            *p++ = g_fullScreenRectUVs[i % 4].x();
            *p++ = g_fullScreenRectUVs[i % 4].y();
        }
        rub->uploadStaticBuffer(m_vbuf->buffer(), buf);
    }
    if (!m_ibuf) {
        m_ibuf = std::make_shared<QSSGRhiBuffer>(*rhiCtx,
                                                 QRhiBuffer::Immutable,
                                                 QRhiBuffer::IndexBuffer,
                                                 0,
                                                 6 * sizeof(quint16),
                                                 QRhiCommandBuffer::IndexUInt16);
        m_ibuf->buffer()->setName(QByteArrayLiteral("quad index buffer"));
        const quint16 buf[] = { 0, 1, 2, 0, 2, 3 };
        rub->uploadStaticBuffer(m_ibuf->buffer(), buf);
    }
}

void QSSGRhiQuadRenderer::prepareQuad(QSSGRhiContext *rhiCtx, QRhiResourceUpdateBatch *maybeRub)
{
    QRhiResourceUpdateBatch *rub = maybeRub ? maybeRub : rhiCtx->rhi()->nextResourceUpdateBatch();
    ensureBuffers(rhiCtx, rub);
    rhiCtx->commandBuffer()->resourceUpdate(rub);
}

void QSSGRhiQuadRenderer::recordRenderQuad(QSSGRhiContext *rhiCtx,
                                           QSSGRhiGraphicsPipelineState *ps, QRhiShaderResourceBindings *srb,
                                           QRhiRenderPassDescriptor *rpDesc, Flags flags)
{
    // ps must have viewport and shaderPipeline set already
    auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
    if (flags.testFlag(UvCoords)) {
        ia.inputLayout.setAttributes({
                                            { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
                                            { 0, 1, QRhiVertexInputAttribute::Float2, 3 * sizeof(float) }
                                        });
        ia.inputs << QSSGRhiInputAssemblerState::PositionSemantic << QSSGRhiInputAssemblerState::TexCoord0Semantic;
    } else {
        ia.inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } });
        ia.inputs << QSSGRhiInputAssemblerState::PositionSemantic;
    }
    ia.inputLayout.setBindings({ 5 * sizeof(float) });
    ia.topology = QRhiGraphicsPipeline::Triangles;

    ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, flags.testFlag(DepthTest));
    ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, flags.testFlag(DepthWrite));
    ps->cullMode = QRhiGraphicsPipeline::None;
    if (flags.testFlag(PremulBlend)) {
        ps->flags |= QSSGRhiGraphicsPipelineState::Flag::BlendEnabled;
        ps->targetBlend[0].srcColor = QRhiGraphicsPipeline::One;
        ps->targetBlend[0].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
        ps->targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
        ps->targetBlend[0].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
    } else { // set to default, since we may not have had a renderable previously
        ps->targetBlend[0].srcColor = QRhiGraphicsPipeline::SrcAlpha;
        ps->targetBlend[0].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
        ps->targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
        ps->targetBlend[0].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
    }

    QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
    QRhiGraphicsPipeline *pipeline = rhiCtxD->pipeline(*ps, rpDesc, srb);
    // Make sure that we were able to create the pipeline before trying to use it
    // When GraphicsPipeline creation fails it should return nullptr and print a warning
    if (!pipeline)
        return;

    QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
    cb->setGraphicsPipeline(pipeline);
    cb->setShaderResources(srb);
    cb->setViewport(ps->viewport);

    quint32 vertexOffset = flags.testAnyFlags(RenderBehind) ? 5 * 4 * sizeof(float) : 0;

    QRhiCommandBuffer::VertexInput vb(m_vbuf->buffer(), vertexOffset);
    Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
    cb->setVertexInput(0, 1, &vb, m_ibuf->buffer(), m_ibuf->indexFormat());
    cb->drawIndexed(6);
    QSSGRHICTX_STAT(rhiCtx, drawIndexed(6, 1));
    Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderCall, 36llu | (1llu << 32), QByteArrayLiteral("render_quad"));
}

void QSSGRhiQuadRenderer::recordRenderQuadPass(QSSGRhiContext *rhiCtx,
                                               QSSGRhiGraphicsPipelineState *ps, QRhiShaderResourceBindings *srb,
                                               QRhiTextureRenderTarget *rt, Flags flags)
{
    QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
    cb->beginPass(rt, Qt::black, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
    QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
    recordRenderQuad(rhiCtx, ps, srb, rt->renderPassDescriptor(), flags);
    cb->endPass();
    QSSGRHICTX_STAT(rhiCtx, endRenderPass());
}

void QSSGRhiCubeRenderer::prepareCube(QSSGRhiContext *rhiCtx, QRhiResourceUpdateBatch *maybeRub)
{
    QRhiResourceUpdateBatch *rub = maybeRub ? maybeRub : rhiCtx->rhi()->nextResourceUpdateBatch();
    ensureBuffers(rhiCtx, rub);
    rhiCtx->commandBuffer()->resourceUpdate(rub);
}

//### The flags UvCoords and RenderBehind are ignored
void QSSGRhiCubeRenderer::recordRenderCube(QSSGRhiContext *rhiCtx, QSSGRhiGraphicsPipelineState *ps, QRhiShaderResourceBindings *srb, QRhiRenderPassDescriptor *rpDesc, QSSGRhiQuadRenderer::Flags flags)
{
    auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
    // ps must have viewport and shaderPipeline set already
    ia.inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } });
    ia.inputs << QSSGRhiInputAssemblerState::PositionSemantic;
    ia.inputLayout.setBindings({ 3 * sizeof(float) });
    ia.topology = QRhiGraphicsPipeline::Triangles;

    ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, flags.testFlag(QSSGRhiQuadRenderer::DepthTest));
    ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, flags.testFlag(QSSGRhiQuadRenderer::DepthWrite));
    ps->cullMode = QRhiGraphicsPipeline::None;
    if (flags.testFlag(QSSGRhiQuadRenderer::PremulBlend)) {
        ps->flags |= QSSGRhiGraphicsPipelineState::Flag::BlendEnabled;
        ps->targetBlend[0].srcColor = QRhiGraphicsPipeline::One;
        ps->targetBlend[0].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
        ps->targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
        ps->targetBlend[0].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
    } else { // set to default, since we may not have had a renderable previously
        ps->targetBlend[0].srcColor = QRhiGraphicsPipeline::SrcAlpha;
        ps->targetBlend[0].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
        ps->targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
        ps->targetBlend[0].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
    }

    QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
    QRhiGraphicsPipeline *pipeline = rhiCtxD->pipeline(*ps, rpDesc, srb);
    // Make sure that we were able to create the pipeline before trying to use it
    // When GraphicsPipeline creation fails it should return nullptr and print a warning
    if (!pipeline)
        return;

    QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
    cb->setGraphicsPipeline(pipeline);
    cb->setShaderResources(srb);
    cb->setViewport(ps->viewport);

    QRhiCommandBuffer::VertexInput vb(m_vbuf->buffer(), 0);
    Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
    cb->setVertexInput(0, 1, &vb, m_ibuf->buffer(), m_ibuf->indexFormat());
    cb->drawIndexed(36);
    QSSGRHICTX_STAT(rhiCtx, drawIndexed(36, 1));
    Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderCall, 36, QByteArrayLiteral("render_cube"));
}

void QSSGRhiCubeRenderer::ensureBuffers(QSSGRhiContext *rhiCtx, QRhiResourceUpdateBatch *rub)
{
    if (!m_vbuf) {
        constexpr int vertexCount = 8;
        m_vbuf = std::make_shared<QSSGRhiBuffer>(*rhiCtx,
                                                 QRhiBuffer::Immutable,
                                                 QRhiBuffer::VertexBuffer,
                                                 quint32(3 * sizeof(float)),
                                                 3 * vertexCount * sizeof(float));
        m_vbuf->buffer()->setName(QByteArrayLiteral("cube vertex buffer"));

        float buf[3 * vertexCount];
        float *p = buf;
        for (int i = 0; i < vertexCount; ++i) {
            *p++ = g_fullScreenRectFaces[4 + i].x();
            *p++ = g_fullScreenRectFaces[4 + i].y();
            *p++ = g_fullScreenRectFaces[4 + i].z();
        }
        rub->uploadStaticBuffer(m_vbuf->buffer(), buf);
    }
    if (!m_ibuf) {
        m_ibuf = std::make_shared<QSSGRhiBuffer>(*rhiCtx,
                                                 QRhiBuffer::Immutable,
                                                 QRhiBuffer::IndexBuffer,
                                                 0,
                                                 sizeof(g_rectIndex),
                                                 QRhiCommandBuffer::IndexUInt16);
        m_ibuf->buffer()->setName(QByteArrayLiteral("cube index buffer"));
        rub->uploadStaticBuffer(m_ibuf->buffer(), g_rectIndex);
    }
}

QT_END_NAMESPACE
