/****************************************************************************
**
** 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 <QtGui/QMatrix4x4>
#include <QtQuick3DRender/private/qssgrendercontext_p.h>
#include <QtQuick3DRender/private/qssgrendershaderprogram_p.h>
#include <QtQuick3DRender/private/qssgrenderprogrampipeline_p.h>

#include <QtQuick3DUtils/private/qssgutils_p.h>

QT_BEGIN_NAMESPACE

QSSGRenderContext::QSSGRenderContext(const QSSGRef<QSSGRenderBackend> &inBackend)
    : m_backend(inBackend)
    , m_defaultOffscreenRenderTarget(nullptr)
    , m_depthBits(16)
    , m_stencilBits(8)
    , m_nextTextureUnit(1)
    , m_nextConstantBufferUnit(1)
{
    m_maxTextureUnits = m_backend->getMaxCombinedTextureUnits();
    m_maxConstantBufferUnits = 16; // need backend query

    // get default blending functions
    m_backend->getBlendFunc(&m_hardwarePropertyContext.m_blendFunction);
    // set default blend euqation
    m_hardwarePropertyContext.m_blendEquation.m_rgbEquation = QSSGRenderBlendEquation::Add;
    m_hardwarePropertyContext.m_blendEquation.m_alphaEquation = QSSGRenderBlendEquation::Add;
    // default state
    m_hardwarePropertyContext.m_cullingEnabled = m_backend->getRenderState(QSSGRenderState::CullFace);
    m_hardwarePropertyContext.m_depthFunction = m_backend->getDepthFunc();
    m_hardwarePropertyContext.m_blendingEnabled = m_backend->getRenderState(QSSGRenderState::Blend);
    m_hardwarePropertyContext.m_depthWriteEnabled = m_backend->getDepthWrite();
    m_hardwarePropertyContext.m_depthTestEnabled = m_backend->getRenderState(QSSGRenderState::DepthTest);
    m_hardwarePropertyContext.m_scissorTestEnabled = m_backend->getRenderState(QSSGRenderState::ScissorTest);
    m_backend->getScissorRect(&m_hardwarePropertyContext.m_scissorRect);
    m_backend->getViewportRect(&m_hardwarePropertyContext.m_viewport);

    m_backend->setClearColor(&m_hardwarePropertyContext.m_clearColor);
}

QSSGRenderContext::~QSSGRenderContext()
{
    Q_ASSERT(m_constantToImpMap.size() == 0);
    m_constantToImpMap.clear();
    Q_ASSERT(m_storageToImpMap.size() == 0);
    m_storageToImpMap.clear();
}

void QSSGRenderContext::maxTextureSize(qint32 &oWidth, qint32 &oHeight)
{
    qint32 theMaxTextureSize = 0;
    m_backend->getRenderBackendValue(QSSGRenderBackend::QSSGRenderBackendQuery::MaxTextureSize, &theMaxTextureSize);

    oWidth = theMaxTextureSize;
    oHeight = theMaxTextureSize;
}

void QSSGRenderContext::setDepthStencilState(const QSSGRef<QSSGRenderDepthStencilState> &inDepthStencilState)
{
    if (inDepthStencilState) {
        m_backend->setDepthStencilState(inDepthStencilState->handle());
        // currently we have a mixture therefore we need to update the context state
        setDepthFunction(inDepthStencilState->depthFunction());
        setDepthWriteEnabled(inDepthStencilState->depthMask());
        setDepthTestEnabled(inDepthStencilState->depthEnabled());
        setStencilTestEnabled(inDepthStencilState->stencilEnabled());
    }
}

void QSSGRenderContext::setRasterizerState(const QSSGRef<QSSGRenderRasterizerState> &inRasterizerState)
{
    if (inRasterizerState)
        m_backend->setRasterizerState(inRasterizerState->handle());
}

void QSSGRenderContext::registerConstantBuffer(QSSGRenderConstantBuffer *buffer)
{
    Q_ASSERT(buffer);
    m_constantToImpMap.insert(buffer->name(), buffer);
}

QSSGRef<QSSGRenderConstantBuffer> QSSGRenderContext::getConstantBuffer(const QByteArray &bufferName) const
{
    const auto entry = m_constantToImpMap.constFind(bufferName);
    if (entry != m_constantToImpMap.cend())
        return entry.value();
    return nullptr;
}

void QSSGRenderContext::bufferDestroyed(QSSGRenderConstantBuffer *buffer)
{
    const auto it = m_constantToImpMap.constFind(buffer->name());
    if (it != m_constantToImpMap.cend()) {
        Q_ASSERT(it.value()->ref == 1);
        m_constantToImpMap.erase(it);
    }
}

qint32 QSSGRenderContext::nextConstantBufferUnit()
{
    qint32 retval = m_nextConstantBufferUnit;
    ++m_nextConstantBufferUnit;
    // Too many texture units for a single draw call.
    if (retval >= m_maxConstantBufferUnits) {
        Q_ASSERT(false);
        retval = retval % m_maxConstantBufferUnits;
    }
    return retval;
}

void QSSGRenderContext::registerStorageBuffer(QSSGRenderStorageBuffer *buffer)
{
    m_storageToImpMap.insert(buffer->name(), buffer);
}

QSSGRef<QSSGRenderStorageBuffer> QSSGRenderContext::getStorageBuffer(const QByteArray &bufferName)
{
    const auto entry = m_storageToImpMap.constFind(bufferName);
    if (entry != m_storageToImpMap.cend())
        return entry.value();
    return nullptr;
}

void QSSGRenderContext::bufferDestroyed(QSSGRenderStorageBuffer *buffer)
{
    const auto it = m_storageToImpMap.constFind(buffer->name());
    if (it != m_storageToImpMap.cend()) {
        Q_ASSERT(it.value()->ref == 1);
        m_storageToImpMap.erase(it);
    }
}

void QSSGRenderContext::setMemoryBarrier(QSSGRenderBufferBarrierFlags barriers)
{
    m_backend->setMemoryBarrier(barriers);
}

// IF this texture isn't on a texture unit, put it on one.
// If it is on a texture unit, mark it as the most recently used texture.
qint32 QSSGRenderContext::nextTextureUnit()
{
    qint32 retval = m_nextTextureUnit;
    ++m_nextTextureUnit;
    // Too many texture units for a single draw call.
    if (retval >= m_maxTextureUnits) {
        Q_ASSERT(false);
        retval = retval % m_maxTextureUnits;
    }
    return retval;
}

QSSGRef<QSSGRenderAttribLayout> QSSGRenderContext::createAttributeLayout(QSSGDataView<QSSGRenderVertexBufferEntry> attribs)
{
    return QSSGRef<QSSGRenderAttribLayout>(new QSSGRenderAttribLayout(this, attribs));
}

QSSGRef<QSSGRenderInputAssembler> QSSGRenderContext::createInputAssembler(
        const QSSGRef<QSSGRenderAttribLayout> &attribLayout,
        QSSGDataView<QSSGRef<QSSGRenderVertexBuffer>> buffers,
        const QSSGRef<QSSGRenderIndexBuffer> &indexBuffer,
        QSSGDataView<quint32> strides,
        QSSGDataView<quint32> offsets,
        QSSGRenderDrawMode primType,
        quint32 patchVertexCount)
{
    return QSSGRef<QSSGRenderInputAssembler>(
            new QSSGRenderInputAssembler(this, attribLayout, buffers, indexBuffer, strides, offsets, primType, patchVertexCount));
}

void QSSGRenderContext::setInputAssembler(const QSSGRef<QSSGRenderInputAssembler> &inputAssembler, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_inputAssembler == inputAssembler)
        return;

    m_hardwarePropertyContext.m_inputAssembler = inputAssembler;
    m_dirtyFlags |= QSSGRenderContextDirtyValues::InputAssembler;
}

QSSGRenderVertFragCompilationResult QSSGRenderContext::compileSource(const char *shaderName,
                                                                             QSSGByteView vertShader,
                                                                             QSSGByteView fragShader,
                                                                             QSSGByteView tessControlShaderSource,
                                                                             QSSGByteView tessEvaluationShaderSource,
                                                                             QSSGByteView geometryShaderSource,
                                                                             bool separateProgram,
                                                                             QSSGRenderShaderProgramBinaryType type,
                                                                             bool binaryProgram)
{
    QSSGRenderVertFragCompilationResult result = QSSGRenderShaderProgram::create(this,
                                                                                     shaderName,
                                                                                     vertShader,
                                                                                     fragShader,
                                                                                     tessControlShaderSource,
                                                                                     tessEvaluationShaderSource,
                                                                                     geometryShaderSource,
                                                                                     separateProgram,
                                                                                     type,
                                                                                     binaryProgram);

    return result;
}

QSSGRenderVertFragCompilationResult QSSGRenderContext::compileBinary(const char *shaderName,
                                                                             QSSGRenderShaderProgramBinaryType type,
                                                                             QSSGByteView vertShader,
                                                                             QSSGByteView fragShader,
                                                                             QSSGByteView tessControlShaderSource,
                                                                             QSSGByteView tessEvaluationShaderSource,
                                                                             QSSGByteView geometryShaderSource)
{
#ifndef _MACOSX
    QSSGRenderVertFragCompilationResult result = QSSGRenderShaderProgram::create(this,
                                                                                     shaderName,
                                                                                     vertShader,
                                                                                     fragShader,
                                                                                     tessControlShaderSource,
                                                                                     tessEvaluationShaderSource,
                                                                                     geometryShaderSource,
                                                                                     false,
                                                                                     type,
                                                                                     true);

    return result;
#else
    Q_ASSERT(false);
    return QSSGRenderVertFragCompilationResult();
#endif
}

QSSGRenderVertFragCompilationResult QSSGRenderContext::compileComputeSource(const QByteArray &shaderName,
                                                                                QSSGByteView computeShaderSource)
{
    QSSGRenderVertFragCompilationResult result = QSSGRenderShaderProgram::createCompute(this, shaderName, computeShaderSource);

    return result;
}


void QSSGRenderContext::shaderDestroyed(QSSGRenderShaderProgram *shader)
{
    if (m_hardwarePropertyContext.m_activeShader.data() == shader)
        setActiveShader(nullptr);
}

QSSGRef<QSSGRenderProgramPipeline> QSSGRenderContext::createProgramPipeline()
{
    return QSSGRef<QSSGRenderProgramPipeline>(new QSSGRenderProgramPipeline(this));
}

void QSSGRenderContext::setClearColor(QVector4D inClearColor, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_clearColor == inClearColor)
        return;

    m_hardwarePropertyContext.m_clearColor = inClearColor;
    m_backend->setClearColor(&inClearColor);
}

void QSSGRenderContext::setBlendFunction(QSSGRenderBlendFunctionArgument inFunctions, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_blendFunction == inFunctions)
        return;

    m_hardwarePropertyContext.m_blendFunction = inFunctions;
    m_backend->setBlendFunc(inFunctions);
}

void QSSGRenderContext::setBlendEquation(QSSGRenderBlendEquationArgument inEquations, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_blendEquation == inEquations)
        return;

    m_hardwarePropertyContext.m_blendEquation = inEquations;
    m_backend->setBlendEquation(inEquations);
}

void QSSGRenderContext::setCullingEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_cullingEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_cullingEnabled = inEnabled;
    m_backend->setRenderState(inEnabled, QSSGRenderState::CullFace);
}

void QSSGRenderContext::setCullFaceMode(QSSGCullFaceMode inMode, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_cullFaceMode == inMode)
        return;

    m_hardwarePropertyContext.m_cullFaceMode = inMode;
    m_backend->setCullFaceMode(inMode);
}

void QSSGRenderContext::setDepthFunction(QSSGRenderBoolOp inFunction, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_depthFunction == inFunction)
        return;

    m_hardwarePropertyContext.m_depthFunction = inFunction;
    m_backend->setDepthFunc(inFunction);
}

void QSSGRenderContext::setBlendingEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_blendingEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_blendingEnabled = inEnabled;
    m_backend->setRenderState(inEnabled, QSSGRenderState::Blend);
}

void QSSGRenderContext::setColorWritesEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_colorWritesEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_colorWritesEnabled = inEnabled;
    m_backend->setColorWrites(inEnabled, inEnabled, inEnabled, inEnabled);
}

void QSSGRenderContext::setDepthWriteEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_depthWriteEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_depthWriteEnabled = inEnabled;
    m_backend->setDepthWrite(inEnabled);
}

void QSSGRenderContext::setDepthTestEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_depthTestEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_depthTestEnabled = inEnabled;
    m_backend->setRenderState(inEnabled, QSSGRenderState::DepthTest);
}

void QSSGRenderContext::setMultisampleEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_multisampleEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_multisampleEnabled = inEnabled;
    m_backend->setMultisample(inEnabled);
}

void QSSGRenderContext::setStencilTestEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_stencilTestEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_stencilTestEnabled = inEnabled;
    m_backend->setRenderState(inEnabled, QSSGRenderState::StencilTest);
}

void QSSGRenderContext::setScissorTestEnabled(bool inEnabled, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_scissorTestEnabled == inEnabled)
        return;

    m_hardwarePropertyContext.m_scissorTestEnabled = inEnabled;
    m_backend->setRenderState(inEnabled, QSSGRenderState::ScissorTest);
}

void QSSGRenderContext::setScissorRect(QRect inRect, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_scissorRect == inRect)
        return;

    m_hardwarePropertyContext.m_scissorRect = inRect;
    m_backend->setScissorRect(inRect);
}

void QSSGRenderContext::setViewport(QRect inViewport, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_viewport == inViewport)
        return;

    m_hardwarePropertyContext.m_viewport = inViewport;
    m_backend->setViewportRect(inViewport);
}

void QSSGRenderContext::setActiveShader(const QSSGRef<QSSGRenderShaderProgram> &inShader, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_activeShader == inShader)
        return;

    if (!m_backend) {
        m_hardwarePropertyContext.m_activeShader = nullptr;
        return;
    }

    m_hardwarePropertyContext.m_activeShader = inShader;

    if (inShader)
        m_backend->setActiveProgram(inShader->handle());
    else
        m_backend->setActiveProgram(nullptr);
}

QSSGRef<QSSGRenderShaderProgram> QSSGRenderContext::activeShader() const
{
    return m_hardwarePropertyContext.m_activeShader;
}

void QSSGRenderContext::setActiveProgramPipeline(const QSSGRef<QSSGRenderProgramPipeline> &inProgramPipeline, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_activeProgramPipeline == inProgramPipeline)
        return;

    if (inProgramPipeline) {
        // invalid any bound shader
        setActiveShader(nullptr, true);
        inProgramPipeline->bind();
    } else {
        m_backend->setActiveProgramPipeline(nullptr);
    }

    m_hardwarePropertyContext.m_activeProgramPipeline = inProgramPipeline;
}

QSSGRef<QSSGRenderProgramPipeline> QSSGRenderContext::activeProgramPipeline() const
{
    return m_hardwarePropertyContext.m_activeProgramPipeline;
}

void QSSGRenderContext::dispatchCompute(const QSSGRef<QSSGRenderShaderProgram> &inShader, quint32 numGroupsX, quint32 numGroupsY, quint32 numGroupsZ)
{
    Q_ASSERT(inShader);
    setActiveShader(inShader);
    m_backend->dispatchCompute(inShader->handle(), numGroupsX, numGroupsY, numGroupsZ);
    onPostDraw();
}

void QSSGRenderContext::setDrawBuffers(QSSGDataView<qint32> inDrawBufferSet)
{
    m_backend->setDrawBuffers((m_hardwarePropertyContext.m_frameBuffer)
                                      ? m_hardwarePropertyContext.m_frameBuffer->handle()
                                      : nullptr,
                              inDrawBufferSet);
}

void QSSGRenderContext::setReadBuffer(QSSGReadFace inReadFace)
{
    // currently nullptr which means the read target must be set with setReadTarget
    m_backend->setReadBuffer(nullptr, inReadFace);
}

void QSSGRenderContext::readPixels(QRect inRect, QSSGRenderReadPixelFormat inFormat, QSSGByteRef inWriteBuffer)
{
    Q_ASSERT(sizeofPixelFormat(inFormat)*inRect.width()*inRect.height() <= inWriteBuffer.size());
    // nullptr means read from current render target
    m_backend->readPixel(nullptr,
                         inRect.x(),
                         inRect.y(),
                         inRect.width(),
                         inRect.height(),
                         inFormat,
                         inWriteBuffer);
}

void QSSGRenderContext::setRenderTarget(const QSSGRef<QSSGRenderFrameBuffer> &inBuffer, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_frameBuffer == inBuffer)
        return;

    if (inBuffer)
        m_backend->setRenderTarget(inBuffer->handle());
    else
        m_backend->setRenderTarget(m_defaultOffscreenRenderTarget);

    m_hardwarePropertyContext.m_frameBuffer = inBuffer;
}

void QSSGRenderContext::setReadTarget(const QSSGRef<QSSGRenderFrameBuffer> &inBuffer, bool forceSet)
{
    if (!forceSet && m_hardwarePropertyContext.m_frameBuffer == inBuffer)
        return;

    if (inBuffer)
        m_backend->setReadTarget(inBuffer->handle());
    else
        m_backend->setReadTarget(QSSGRenderBackend::QSSGRenderBackendRenderTargetObject(nullptr));
}

void QSSGRenderContext::solveCullingOptions(const QSSGCullFaceMode modes)
{
    switch (modes) {
    case QSSGCullFaceMode::Back:
    case QSSGCullFaceMode::Front:
    case QSSGCullFaceMode::FrontAndBack:
        setCullingEnabled(true);
        setCullFaceMode(modes);
        break;
    case QSSGCullFaceMode::Disabled:
        setCullingEnabled(false);
        break;
    default:
        Q_ASSERT(false);
    }
}

void QSSGRenderContext::resetBlendState()
{
    qint32_4 values;

    m_backend->setRenderState(m_hardwarePropertyContext.m_blendingEnabled, QSSGRenderState::Blend);
    const QSSGRenderBlendFunctionArgument &theBlendArg(m_hardwarePropertyContext.m_blendFunction);
    m_backend->setBlendFunc(theBlendArg);
}

void QSSGRenderContext::pushPropertySet()
{
    m_propertyStack.push_back(m_hardwarePropertyContext);
}

// Pop the entire set of properties, potentially forcing the values
// to opengl.
void QSSGRenderContext::popPropertySet(bool inForceSetProperties)
{
    if (!m_propertyStack.empty()) {
        QSSGGLHardPropertyContext &theTopContext(m_propertyStack.back());
        setRenderTarget(theTopContext.m_frameBuffer, inForceSetProperties);
        setActiveShader(theTopContext.m_activeShader, inForceSetProperties);
        setActiveProgramPipeline(theTopContext.m_activeProgramPipeline, inForceSetProperties);
        setInputAssembler(theTopContext.m_inputAssembler, inForceSetProperties);
        setBlendFunction(theTopContext.m_blendFunction, inForceSetProperties);
        setCullingEnabled(theTopContext.m_cullingEnabled, inForceSetProperties);
        setCullFaceMode(theTopContext.m_cullFaceMode, inForceSetProperties);
        setDepthFunction(theTopContext.m_depthFunction, inForceSetProperties);
        setBlendingEnabled(theTopContext.m_blendingEnabled, inForceSetProperties);
        setDepthWriteEnabled(theTopContext.m_depthWriteEnabled, inForceSetProperties);
        setDepthTestEnabled(theTopContext.m_depthTestEnabled, inForceSetProperties);
        setStencilTestEnabled(theTopContext.m_stencilTestEnabled, inForceSetProperties);
        setScissorTestEnabled(theTopContext.m_scissorTestEnabled, inForceSetProperties);
        setScissorRect(theTopContext.m_scissorRect, inForceSetProperties);
        setViewport(theTopContext.m_viewport, inForceSetProperties);
        setClearColor(theTopContext.m_clearColor, inForceSetProperties);
        m_propertyStack.pop_back();
    }
}

void QSSGRenderContext::clear(QSSGRenderClearFlags flags)
{
    if ((flags & QSSGRenderClearValues::Depth) && m_hardwarePropertyContext.m_depthWriteEnabled == false) {
        Q_ASSERT(false);
        setDepthWriteEnabled(true);
    }
    m_backend->clear(flags);
}

void QSSGRenderContext::clear(const QSSGRef<QSSGRenderFrameBuffer> &fb, QSSGRenderClearFlags flags)
{
    QSSGRef<QSSGRenderFrameBuffer> previous = m_hardwarePropertyContext.m_frameBuffer;
    if (previous != fb)
        setRenderTarget(fb);

    clear(flags);

    if (previous != fb)
        setRenderTarget(previous);
}

void QSSGRenderContext::blitFramebuffer(qint32 srcX0,
                                              qint32 srcY0,
                                              qint32 srcX1,
                                              qint32 srcY1,
                                              qint32 dstX0,
                                              qint32 dstY0,
                                              qint32 dstX1,
                                              qint32 dstY1,
                                              QSSGRenderClearFlags flags,
                                              QSSGRenderTextureMagnifyingOp filter)
{
    m_backend->blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, flags, filter);
}

void QSSGRenderContext::copyFramebufferTexture(qint32 srcX0,
                                               qint32 srcY0,
                                               qint32 width,
                                               qint32 height,
                                               qint32 dstX0,
                                               qint32 dstY0,
                                               const QSSGRenderTextureOrRenderBuffer &buffer)
{
    m_backend->copyFramebufferTexture(srcX0, srcY0, width, height, dstX0, dstY0,
                                      buffer.texture2D()->handle(),
                                      QSSGRenderTextureTargetType::Texture2D);
}

bool QSSGRenderContext::bindShaderToInputAssembler(const QSSGRef<QSSGRenderInputAssembler> &inputAssembler,
                                                     const QSSGRef<QSSGRenderShaderProgram> &shader)
{
    // setup the input assembler object
    return m_backend->setInputAssembler(inputAssembler->handle(), shader->handle());
}

bool QSSGRenderContext::applyPreDrawProperties()
{
    // Get the currently bound vertex and shader
    const QSSGRef<QSSGRenderInputAssembler> &inputAssembler = m_hardwarePropertyContext.m_inputAssembler;
    QSSGRef<QSSGRenderShaderProgram> &shader(m_hardwarePropertyContext.m_activeShader);

    // we could render through a program pipline
    if (shader == nullptr && m_hardwarePropertyContext.m_activeProgramPipeline)
        shader = m_hardwarePropertyContext.m_activeProgramPipeline->vertexStage();

    if (inputAssembler == nullptr || shader == nullptr) {
        qCCritical(INVALID_OPERATION, "Attempting to render no valid shader or input assembler setup");
        Q_ASSERT(false);
        return false;
    }

    return bindShaderToInputAssembler(inputAssembler, shader);
}

void QSSGRenderContext::onPostDraw()
{
    // reset input assembler binding
    m_backend->setInputAssembler(nullptr, nullptr);
    // Texture unit 0 is used for setting up and loading textures.
    // Bugs happen if we load a texture then setup the sampler.
    // Then we load another texture.  Because when loading we use texture unit 0,
    // the render bindings for the first texture are blown away.
    // Again, for this reason, texture unit 0 is reserved for loading textures.
    m_nextTextureUnit = 1;
    m_nextConstantBufferUnit = 1;
}

void QSSGRenderContext::draw(QSSGRenderDrawMode drawMode, quint32 count, quint32 offset)
{
    if (applyPreDrawProperties()) {
        const QSSGRef<QSSGRenderIndexBuffer> &theIndexBuffer
                            = m_hardwarePropertyContext.m_inputAssembler->indexBuffer();
        if (theIndexBuffer == nullptr)
            m_backend->draw(drawMode, offset, count);
        else
            theIndexBuffer->draw(drawMode, count, offset);
    }

    onPostDraw();
}

QMatrix4x4 QSSGRenderContext::applyVirtualViewportToProjectionMatrix(const QMatrix4x4 &inProjection,
                                                                       const QRectF &inViewport,
                                                                       const QRectF &inVirtualViewport)
{
    if (inVirtualViewport == inViewport)
        return inProjection;
    // Run conversion to floating point once.
    QRectF theVirtualViewport(inVirtualViewport);
    QRectF theViewport(inViewport);
    if (Q_UNLIKELY(qFuzzyIsNull(theVirtualViewport.width()) || qFuzzyIsNull(theVirtualViewport.height()) || qFuzzyIsNull(theViewport.width())
                   || qFuzzyIsNull(theViewport.height()))) {
        Q_ASSERT(false);
        return inProjection;
    }
    QMatrix4x4 theScaleTransMat;
    const qreal theHeightDiff = theViewport.height() - theVirtualViewport.height();
    const qreal theViewportOffY = theVirtualViewport.y() - theViewport.y();
    QVector2D theCameraOffsets = QVector2D(float(theVirtualViewport.width() - theViewport.width() + (theVirtualViewport.x() - theViewport.x())) * 2.0f,
                                           float(theHeightDiff + (theViewportOffY - theHeightDiff)) * 2.0f);
    QVector2D theCameraScale = QVector2D(float(theVirtualViewport.width() / theViewport.width()),
                                         float(theVirtualViewport.height() / theViewport.height()));

    QVector3D theTranslation(theCameraOffsets.x() / float(theViewport.width()), theCameraOffsets.y() / float(theViewport.height()), 0.0f);
    QVector4D column3 = theScaleTransMat.column(3);
    column3.setX(theTranslation.x());
    column3.setY(theTranslation.y());
    theScaleTransMat.setColumn(3, column3);
    QVector4D column0 = theScaleTransMat.column(0);
    column0.setX(theCameraScale.x());
    theScaleTransMat.setColumn(0, column0);
    QVector4D column1 = theScaleTransMat.column(1);
    column1.setY(theCameraScale.y());
    theScaleTransMat.setColumn(1, column1);

    return theScaleTransMat * inProjection;
}

QSSGRef<QSSGRenderContext> QSSGRenderContext::createNull()
{
    return QSSGRef<QSSGRenderContext>(new QSSGRenderContext(QSSGRenderBackendNULL::createBackend()));;
}
QT_END_NAMESPACE
