// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

// TODO Remove in Qt6
#include <QtCore/qcompilerdetection.h>
QT_WARNING_DISABLE_DEPRECATED

#include <QtTest/QTest>
#include <qbackendnodetester.h>
#include <qtextureimagedatagenerator.h>
#include <Qt3DRender/qtexture.h>
#include <Qt3DRender/qtextureimage.h>
#include <Qt3DRender/qtexturedata.h>

#include <renderer_p.h>
#include <glresourcemanagers_p.h>
#include <gltexture_p.h>
#include <Qt3DRender/private/texture_p.h>
#include <Qt3DRender/private/textureimage_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/managers_p.h>
#include <Qt3DRender/private/qtexture_p.h>

#include <testrenderer.h>

/**
 * @brief Dummy QTextureImageDataGenerator
 */
class TestImageDataGenerator : public Qt3DRender::QTextureImageDataGenerator
{
    int m_id;
public:
    TestImageDataGenerator(int id) : m_id(id) {}

    Qt3DRender::QTextureImageDataPtr operator ()() override {
        return Qt3DRender::QTextureImageDataPtr::create();
    }

    bool operator ==(const Qt3DRender::QTextureImageDataGenerator &other) const override {
        const TestImageDataGenerator *otherFunctor = Qt3DCore::functor_cast<TestImageDataGenerator>(&other);
        return (otherFunctor != nullptr && otherFunctor->m_id == m_id);
    }

    QT3D_FUNCTOR(TestImageDataGenerator)
};

/**
 * @brief Dummy QTextureGenerator
 */
class TestTextureGenerator : public Qt3DRender::QTextureGenerator
{
    int m_id;
public:
    TestTextureGenerator(int id) : m_id(id) {}

    Qt3DRender::QTextureDataPtr operator ()() override {
        return Qt3DRender::QTextureDataPtr::create();
    }

    bool operator ==(const Qt3DRender::QTextureGenerator &other) const override {
        const TestTextureGenerator *otherFunctor = Qt3DCore::functor_cast<TestTextureGenerator>(&other);
        return (otherFunctor != nullptr && otherFunctor->m_id == m_id);
    }

    QT3D_FUNCTOR(TestTextureGenerator)
};

typedef QSharedPointer<TestTextureGenerator> TestTextureGeneratorPtr;

class TestTexturePrivate : public Qt3DRender::QAbstractTexturePrivate
{
public:
    int genId;
};

/**
 * @brief Test QTexture. Assign texture data functor if genId > 0.
 */
class TestTexture : public Qt3DRender::QAbstractTexture
{
public:
    TestTexture(int genId, Qt3DCore::QNode *p = nullptr)
        : QAbstractTexture(*new TestTexturePrivate(), p)
    {
        d_func()->genId = genId;
        if (genId > 0)
            d_func()->setDataFunctor(TestTextureGeneratorPtr::create(genId));
    }
private:
    Q_DECLARE_PRIVATE(TestTexture)
};

class TestSharedGLTexturePrivate : public Qt3DRender::QAbstractTexturePrivate
{
};

class TestSharedGLTexture : public Qt3DRender::QAbstractTexture
{
public:
    TestSharedGLTexture(int textureId, Qt3DCore::QNode *p = nullptr)
        : QAbstractTexture(*new TestSharedGLTexturePrivate(), p)
    {
        d_func()->m_sharedTextureId = textureId;
    }

private:
    Q_DECLARE_PRIVATE(TestSharedGLTexture)
};


/**
 * @brief Test QTextureImage
 */
class TestTextureImage : public Qt3DRender::QAbstractTextureImage
{
public:
    TestTextureImage(int genId, Qt3DCore::QNode *p = nullptr)
        : QAbstractTextureImage(p)
        , m_genId(genId)
    {
    }

    void updateGenerator()
    {
        Qt3DRender::QAbstractTextureImage::notifyDataGeneratorChanged();
    }

    Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator() const override
    {
        return Qt3DRender::QTextureImageDataGeneratorPtr(new TestImageDataGenerator(m_genId));
    }
protected:
    int m_genId;
};

class EmptyTextureImage : public Qt3DRender::QAbstractTextureImage
{
public:
    EmptyTextureImage(Qt3DCore::QNode *p = nullptr)
        : QAbstractTextureImage(p)
    {
    }

    Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator() const override
    {
        return {};
    }
};

class tst_RenderTextures : public Qt3DCore::QBackendNodeTester
{
    Q_OBJECT

    Qt3DRender::QAbstractTexture *createQTexture(int genId,
                                                 const QList<int> &imgGenIds,
                                                 bool genMipMaps)
    {
        TestTexture *tex = new TestTexture(genId);

        for (int imgGen : imgGenIds)
            tex->addTextureImage(new TestTextureImage(imgGen));
        tex->setGenerateMipMaps(genMipMaps);

        return tex;
    }

    Qt3DRender::QAbstractTexture *createQTextureWithTextureId(int textureId)
    {
        return new TestSharedGLTexture(textureId);
    }

    Qt3DRender::Render::Texture *createBackendTexture(Qt3DRender::QAbstractTexture *frontend,
                                                      Qt3DRender::Render::TextureManager *texMgr,
                                                      Qt3DRender::Render::TextureImageManager *texImgMgr,
                                                      Qt3DRender::Render::AbstractRenderer *renderer)
    {
        Qt3DRender::Render::Texture *backend = texMgr->getOrCreateResource(frontend->id());
        backend->setRenderer(renderer);
        simulateInitializationSync(frontend, backend);

        // create texture images
        for (const auto texImgFrontend : frontend->textureImages()) {
            // make sure TextureImageManager has backend node for this QTextureImage
            if (!texImgMgr->contains(texImgFrontend->id())) {
                Qt3DRender::Render::TextureImage *texImgBackend = texImgMgr->getOrCreateResource(texImgFrontend->id());
                texImgBackend->setRenderer(renderer);
                simulateInitializationSync(texImgFrontend, texImgBackend);
            }
        }

        return backend;
    }

private Q_SLOTS:

    void shouldCreateSameGLTextures()
    {
        QSKIP("Texture Sharing is now disabled");
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        renderer.setNodeManagers(mgrs.data());

        // GIVEN
        Qt3DRender::QAbstractTexture *tex1a = createQTexture(-1, {1,2}, true);
        Qt3DRender::QAbstractTexture *tex1b = createQTexture(-1, {1,2}, true);

        // WHEN
        Qt3DRender::Render::Texture *bt1a = createBackendTexture(tex1a,
                                                                 mgrs->textureManager(),
                                                                 mgrs->textureImageManager(),
                                                                 &renderer);
        Qt3DRender::Render::Texture *bt1b = createBackendTexture(tex1b,
                                                                 mgrs->textureManager(),
                                                                 mgrs->textureImageManager(),
                                                                 &renderer);
        renderer.updateTexture(bt1a);
        renderer.updateTexture(bt1b);

        // THEN
        QCOMPARE(renderer.glResourceManagers()->glTextureManager()->lookupResource(bt1a->peerId()),
                 renderer.glResourceManagers()->glTextureManager()->lookupResource(bt1b->peerId()));
        renderer.shutdown();
    }

    void shouldCreateDifferentGLTexturess()
    {
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        renderer.setNodeManagers(mgrs.data());

        // GIVEN
        const QList<Qt3DRender::QAbstractTexture*> textures
                = { createQTexture(-1, {1,2}, true),
                    createQTexture(-1, {1,2}, false),
                    createQTexture(1, {1,2}, true),
                    createQTexture(1, {1,2}, false),
                    createQTexture(1, {1,2,3}, true),
                    createQTexture(1, {1,2,3}, false) };

        // WHEN
        QList<Qt3DRender::Render::Texture *> backend;
        for (auto *t : textures) {
            Qt3DRender::Render::Texture *backendTexture = createBackendTexture(t,
                                                                               mgrs->textureManager(),
                                                                               mgrs->textureImageManager(),
                                                                               &renderer);
            backend.push_back(backendTexture);
            renderer.updateTexture(backendTexture);
        }

        // THEN

        // no 2 textures must be the same
        for (int i = 0; i < backend.size(); i++)
            for (int k = i+1; k < backend.size(); k++)
                QVERIFY(renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[i]->peerId()) !=
                        renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[k]->peerId()));

        QList<Qt3DRender::Render::OpenGL::GLTexture *> glTextures;
        for (Qt3DRender::Render::Texture *t : backend)
            glTextures.push_back(renderer.glResourceManagers()->glTextureManager()->lookupResource(t->peerId()));

        // some texture generators must be the same
        QVERIFY(glTextures[0]->textureGenerator().data() == nullptr);
        QVERIFY(glTextures[1]->textureGenerator().data() == nullptr);
        QCOMPARE(*(glTextures[2]->textureGenerator()), *(glTextures[3]->textureGenerator()));

        // some images must be the same
        QCOMPARE(glTextures[0]->images(), glTextures[1]->images());
        QCOMPARE(glTextures[0]->images(), glTextures[2]->images());
        QCOMPARE(glTextures[0]->images(), glTextures[3]->images());
        QCOMPARE(glTextures[4]->images(), glTextures[5]->images());

        QCOMPARE(glTextures[0]->properties(), glTextures[2]->properties());
        QCOMPARE(glTextures[1]->properties(), glTextures[3]->properties());
        QVERIFY(glTextures[0]->properties() != glTextures[1]->properties());

        renderer.shutdown();
    }

    void shouldCreateDifferentGLTexturesWhenUsingSharedTextureIds()
    {
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        renderer.setNodeManagers(mgrs.data());

        Qt3DRender::Render::OpenGL::GLResourceManagers *glMgrs = renderer.glResourceManagers();

        // both texture having the same sharedTextureId
        {
            // GIVEN
            Qt3DRender::QAbstractTexture *tex1a = createQTextureWithTextureId(1);
            Qt3DRender::QAbstractTexture *tex1b = createQTextureWithTextureId(1);

            // WHEN
            Qt3DRender::Render::Texture *bt1 = createBackendTexture(tex1a,
                                                                    mgrs->textureManager(),
                                                                    mgrs->textureImageManager(),
                                                                    &renderer);
            Qt3DRender::Render::Texture *bt2 = createBackendTexture(tex1b,
                                                                    mgrs->textureManager(),
                                                                    mgrs->textureImageManager(),
                                                                    &renderer);
            // THEN
            QCOMPARE(bt1->sharedTextureId(), 1);
            QCOMPARE(bt2->sharedTextureId(), 1);

            // WHEN
            renderer.updateTexture(bt1);
            renderer.updateTexture(bt2);

            // THEN
            Qt3DRender::Render::OpenGL::GLTexture *glt1 = glMgrs->glTextureManager()->lookupResource(bt1->peerId());
            Qt3DRender::Render::OpenGL::GLTexture *glt2 = glMgrs->glTextureManager()->lookupResource(bt2->peerId());
            QVERIFY(glt1 != glt2);
            QCOMPARE(glt1->sharedTextureId(), bt1->sharedTextureId());
            QCOMPARE(glt2->sharedTextureId(), bt2->sharedTextureId());
        }

        // textures having a different sharedTextureId
        {
            // GIVEN
            Qt3DRender::QAbstractTexture *tex1a = createQTextureWithTextureId(1);
            Qt3DRender::QAbstractTexture *tex1b = createQTextureWithTextureId(2);

            // WHEN
            Qt3DRender::Render::Texture *bt1 = createBackendTexture(tex1a,
                                                                    mgrs->textureManager(),
                                                                    mgrs->textureImageManager(),
                                                                    &renderer);
            Qt3DRender::Render::Texture *bt2 = createBackendTexture(tex1b,
                                                                    mgrs->textureManager(),
                                                                    mgrs->textureImageManager(),
                                                                    &renderer);
            // THEN
            QCOMPARE(bt1->sharedTextureId(), 1);
            QCOMPARE(bt2->sharedTextureId(), 2);

            // WHEN
            renderer.updateTexture(bt1);
            renderer.updateTexture(bt2);

            // THEN
            Qt3DRender::Render::OpenGL::GLTexture *glt1 = glMgrs->glTextureManager()->lookupResource(bt1->peerId());
            Qt3DRender::Render::OpenGL::GLTexture *glt2 = glMgrs->glTextureManager()->lookupResource(bt2->peerId());
            QVERIFY(glt1 != glt2);
            QCOMPARE(glt1->sharedTextureId(), bt1->sharedTextureId());
            QCOMPARE(glt2->sharedTextureId(), bt2->sharedTextureId());
        }

        renderer.shutdown();
    }

    void generatorsShouldCreateSameData()
    {
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        renderer.setNodeManagers(mgrs.data());

        // GIVEN
        const QList<Qt3DRender::QAbstractTexture*> textures
                = { createQTexture(1, {1}, true),
                    createQTexture(2, {1,2}, true),
                    createQTexture(1, {1,2}, true) };

        // WHEN
        QList<Qt3DRender::Render::Texture*> backend;
        for (auto *t : textures) {
            Qt3DRender::Render::Texture *backendTexture = createBackendTexture(t,
                                                                               mgrs->textureManager(),
                                                                               mgrs->textureImageManager(),
                                                                               &renderer);
            backend.push_back(backendTexture);
            renderer.updateTexture(backendTexture);
        }

        Qt3DRender::QTextureImageDataGeneratorPtr idg1a = renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[0]->peerId())->images()[0].generator;
        Qt3DRender::QTextureImageDataGeneratorPtr idg1b = renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[1]->peerId())->images()[0].generator;
        Qt3DRender::QTextureImageDataGeneratorPtr idg2 = renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[1]->peerId())->images()[1].generator;
        Qt3DRender::QTextureGeneratorPtr tg1a = renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[0]->peerId())->textureGenerator();
        Qt3DRender::QTextureGeneratorPtr tg1b = renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[2]->peerId())->textureGenerator();
        Qt3DRender::QTextureGeneratorPtr tg2 = renderer.glResourceManagers()->glTextureManager()->lookupResource(backend[1]->peerId())->textureGenerator();

        // THEN
        QVERIFY(idg1a);
        QVERIFY(idg1b);
        QVERIFY(idg2);
        QVERIFY(tg1a);
        QVERIFY(tg1b);
        QVERIFY(tg2);

        QCOMPARE(*idg1a, *idg1b);
        QVERIFY(!(*idg1a == *idg2));
        QCOMPARE(*tg1a, *tg1b);
        QVERIFY(!(*tg1a == *tg2));

        renderer.shutdown();
    }

    void checkTextureImageInitialState()
    {
        // GIVEN
        Qt3DRender::Render::TextureImage img;

        // THEN
        QCOMPARE(img.layer(), 0);
        QCOMPARE(img.mipLevel(), 0);
        QCOMPARE(img.isDirty(), false);
        QCOMPARE(img.face(), Qt3DRender::QAbstractTexture::CubeMapPositiveX);
        QVERIFY(img.dataGenerator().isNull());
    }

    void checkTextureImageCleanupState()
    {
        // GIVEN
        Qt3DRender::Render::OpenGL::Renderer renderer;
        TestTextureImage img(1);
        img.setLayer(2);
        img.setMipLevel(3);

        // WHEN
        Qt3DRender::Render::TextureImage texImgBackend;
        texImgBackend.setRenderer(&renderer);
        simulateInitializationSync(&img, &texImgBackend);
        texImgBackend.cleanup();

        // THEN
        QCOMPARE(texImgBackend.isDirty(), false);
        QCOMPARE(texImgBackend.layer(), 0);
        QCOMPARE(texImgBackend.mipLevel(), 0);
        QCOMPARE(texImgBackend.face(), Qt3DRender::QAbstractTexture::CubeMapPositiveX);
        QVERIFY(texImgBackend.dataGenerator().isNull());

        renderer.shutdown();
    }

    void checkTextureImageInitializeFromPeer()
    {
        // GIVEN
        Qt3DRender::Render::OpenGL::Renderer renderer;
        TestTextureImage img(1);

        {
            // WHEN
            img.setLayer(2);
            img.setMipLevel(3);

            Qt3DRender::Render::TextureImage texImgBackend;
            texImgBackend.setRenderer(&renderer);
            simulateInitializationSync(&img, &texImgBackend);

            // THEN
            QCOMPARE(texImgBackend.isEnabled(), true);
            QCOMPARE(texImgBackend.isDirty(), true);
            QCOMPARE(texImgBackend.peerId(), img.id());
            QCOMPARE(texImgBackend.layer(), 2);
            QCOMPARE(texImgBackend.mipLevel(), 3);
            QCOMPARE(texImgBackend.face(), Qt3DRender::QAbstractTexture::CubeMapPositiveX);
            QVERIFY(!texImgBackend.dataGenerator().isNull());
        }

        {
            // WHEN
            img.setEnabled(false);

            Qt3DRender::Render::TextureImage texImgBackend;
            texImgBackend.setRenderer(&renderer);
            simulateInitializationSync(&img, &texImgBackend);

            // THEN
            QCOMPARE(texImgBackend.isEnabled(), false);
            QCOMPARE(texImgBackend.peerId(), img.id());
        }

        renderer.shutdown();
    }

    void checkTextureImageSceneChangeEvents()
    {
        // GIVEN
        Qt3DRender::Render::TextureImage backendImage;
        TestTextureImage textureImage(1);
        TestRenderer renderer;
        backendImage.setRenderer(&renderer);
        simulateInitializationSync(&textureImage, &backendImage);

        {
            // WHEN
            const bool newValue = false;
            textureImage.setEnabled(newValue);
            backendImage.syncFromFrontEnd(&textureImage, false);

            // THEN
            QCOMPARE(backendImage.isEnabled(), newValue);
            QVERIFY(backendImage.isDirty());
            QVERIFY(renderer.dirtyBits() & Qt3DRender::Render::AbstractRenderer::AllDirty);
            renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
            backendImage.unsetDirty();
        }

        {
            // WHEN
            const int newValue = 7;
            textureImage.setLayer(newValue);
            backendImage.syncFromFrontEnd(&textureImage, false);

            // THEN
            QCOMPARE(backendImage.layer(), newValue);
            QVERIFY(backendImage.isDirty());
            QVERIFY(renderer.dirtyBits() & Qt3DRender::Render::AbstractRenderer::AllDirty);
            renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
            backendImage.unsetDirty();
        }

        {
            // WHEN
            const int newValue = 3;
            textureImage.setMipLevel(newValue);
            backendImage.syncFromFrontEnd(&textureImage, false);


            // THEN
            QCOMPARE(backendImage.mipLevel(), newValue);
            QVERIFY(backendImage.isDirty());
            QVERIFY(renderer.dirtyBits() & Qt3DRender::Render::AbstractRenderer::AllDirty);
            renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
            backendImage.unsetDirty();
        }

        {
            // WHEN
            const Qt3DRender::QAbstractTexture::CubeMapFace newValue = Qt3DRender::QAbstractTexture::CubeMapNegativeX;
            textureImage.setFace(newValue);
            backendImage.syncFromFrontEnd(&textureImage, false);

            // THEN
            QCOMPARE(backendImage.face(), newValue);
            QVERIFY(backendImage.isDirty());
            QVERIFY(renderer.dirtyBits() & Qt3DRender::Render::AbstractRenderer::AllDirty);
            renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
            backendImage.unsetDirty();
        }

        {
            // WHEN
            textureImage.updateGenerator();
            backendImage.syncFromFrontEnd(&textureImage, false);

            // THEN
            QVERIFY(backendImage.isDirty());
            QVERIFY(renderer.dirtyBits() & Qt3DRender::Render::AbstractRenderer::AllDirty);
            renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
            backendImage.unsetDirty();

            // WHEN
            textureImage.updateGenerator();
            backendImage.syncFromFrontEnd(&textureImage, false);

            // THEN
            QVERIFY(backendImage.isDirty());
            QVERIFY(renderer.dirtyBits() & Qt3DRender::Render::AbstractRenderer::AllDirty);
            renderer.clearDirtyBits(Qt3DRender::Render::AbstractRenderer::AllDirty);
            backendImage.unsetDirty();
        }

        renderer.shutdown();
    }

    void checkTextureImageProperlyReleaseGenerator()
    {
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        Qt3DRender::Render::TextureManager *texMgr = mgrs->textureManager();
        Qt3DRender::Render::TextureImageManager *texImgMgr = mgrs->textureImageManager();
        renderer.setNodeManagers(mgrs.data());

        // GIVEN
        Qt3DRender::QAbstractTexture* frontendTexture = createQTexture(1, {1}, true);

        Qt3DRender::Render::Texture *backendTexture = texMgr->getOrCreateResource(frontendTexture->id());
        backendTexture->setRenderer(&renderer);
        simulateInitializationSync(frontendTexture, backendTexture);

        // THEN
        QCOMPARE(backendTexture->textureImageIds().size(), 1);
        QCOMPARE(frontendTexture->textureImages().size(), 1);

        // WHEN
        TestTextureImage *texImgFrontend = static_cast<TestTextureImage *>(frontendTexture->textureImages().first());
        const Qt3DRender::QTextureImageDataGeneratorPtr frontendGenerator = texImgFrontend->dataGenerator();

        // THEN
        QVERIFY(!frontendGenerator.isNull());

        // WHEN
        Qt3DRender::Render::TextureImage *texImgBackend = texImgMgr->getOrCreateResource(texImgFrontend->id());
        texImgBackend->setRenderer(&renderer);
        simulateInitializationSync(texImgFrontend, texImgBackend);

        // THEN
        qDebug() << frontendGenerator << texImgBackend->dataGenerator();
        const Qt3DRender::QTextureImageDataGeneratorPtr backendGenerator = texImgFrontend->dataGenerator();
        QVERIFY(frontendGenerator != backendGenerator);
        QVERIFY(*frontendGenerator == *backendGenerator);

        renderer.shutdown();
    }

    void checkTextureIsMarkedForDeletion()
    {
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        Qt3DRender::Render::TextureManager *texMgr = mgrs->textureManager();
        renderer.setNodeManagers(mgrs.data());

        Qt3DRender::Render::TextureFunctor textureBackendNodeMapper(&renderer,
                                                                    texMgr);

        // GIVEN
        Qt3DRender::QAbstractTexture* frontendTexture = createQTexture(1, {1}, true);

        Qt3DRender::Render::Texture *backendTexture = static_cast<Qt3DRender::Render::Texture *>(textureBackendNodeMapper.create(frontendTexture->id()));
        backendTexture->setRenderer(&renderer);
        simulateInitializationSync(frontendTexture, backendTexture);

        // THEN
        QVERIFY(backendTexture != nullptr);
        QCOMPARE(texMgr->textureIdsToCleanup().size(), 0);

        QCOMPARE(texMgr->lookupResource(frontendTexture->id()), backendTexture);

        // WHEN
        textureBackendNodeMapper.destroy(frontendTexture->id());

        // THEN
        QCOMPARE(texMgr->textureIdsToCleanup().size(), 1);
        QCOMPARE(texMgr->textureIdsToCleanup().first(), frontendTexture->id());
        QVERIFY(texMgr->lookupResource(frontendTexture->id()) == nullptr);

        renderer.shutdown();
    }

    void checkTextureDestructionReconstructionWithinSameLoop()
    {
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        Qt3DRender::Render::TextureManager *texMgr = mgrs->textureManager();
        renderer.setNodeManagers(mgrs.data());

        Qt3DRender::Render::TextureFunctor textureBackendNodeMapper(&renderer,
                                                                    texMgr);

        // GIVEN
        Qt3DRender::QAbstractTexture* frontendTexture = createQTexture(1, {1}, true);

        Qt3DRender::Render::Texture *backendTexture = static_cast<Qt3DRender::Render::Texture *>(textureBackendNodeMapper.create(frontendTexture->id()));
        backendTexture->setRenderer(&renderer);
        simulateInitializationSync(frontendTexture, backendTexture);

        // WHEN
        textureBackendNodeMapper.destroy(frontendTexture->id());

        // THEN
        QCOMPARE(texMgr->textureIdsToCleanup().size(), 1);
        QCOMPARE(texMgr->textureIdsToCleanup().first(), frontendTexture->id());
        QVERIFY(texMgr->lookupResource(frontendTexture->id()) == nullptr);

        // WHEN
        backendTexture = static_cast<Qt3DRender::Render::Texture *>(textureBackendNodeMapper.create(frontendTexture->id()));
        backendTexture->setRenderer(&renderer);
        simulateInitializationSync(frontendTexture, backendTexture);

        // THEN
        QVERIFY(backendTexture != nullptr);
        QCOMPARE(texMgr->textureIdsToCleanup().size(), 0);
        QCOMPARE(texMgr->lookupResource(frontendTexture->id()), backendTexture);

        renderer.shutdown();
    }

    void checkTextureImageDirtinessPropagatesToTextures()
    {
        // GIVEN
        QScopedPointer<Qt3DRender::Render::NodeManagers> mgrs(new Qt3DRender::Render::NodeManagers());
        Qt3DRender::Render::OpenGL::Renderer renderer;
        Qt3DRender::Render::TextureManager *texMgr = mgrs->textureManager();
        Qt3DRender::Render::TextureImageManager *texImgMgr = mgrs->textureImageManager();
        renderer.setNodeManagers(mgrs.data());

        Qt3DRender::QTexture2D *texture1 = new Qt3DRender::QTexture2D();
        TestTextureImage *image1 = new TestTextureImage(1);

        Qt3DRender::QTexture2D *texture2 = new Qt3DRender::QTexture2D();
        TestTextureImage *image2 = new TestTextureImage(2);

        Qt3DRender::QTexture2D *texture3 = new Qt3DRender::QTexture2D();

        texture1->addTextureImage(image1);
        texture2->addTextureImage(image2);
        texture3->addTextureImage(image1);
        texture3->addTextureImage(image2);

        Qt3DRender::Render::Texture *backendTexture1 = texMgr->getOrCreateResource(texture1->id());
        Qt3DRender::Render::Texture *backendTexture2 = texMgr->getOrCreateResource(texture2->id());
        Qt3DRender::Render::Texture *backendTexture3 = texMgr->getOrCreateResource(texture3->id());
        Qt3DRender::Render::TextureImage *backendImage1 = texImgMgr->getOrCreateResource(image1->id());
        Qt3DRender::Render::TextureImage *backendImage2 = texImgMgr->getOrCreateResource(image2->id());

        backendTexture1->setRenderer(&renderer);
        backendTexture2->setRenderer(&renderer);
        backendTexture3->setRenderer(&renderer);
        backendImage1->setRenderer(&renderer);
        backendImage2->setRenderer(&renderer);

        simulateInitializationSync(texture1, backendTexture1);
        simulateInitializationSync(texture2, backendTexture2);
        simulateInitializationSync(texture3, backendTexture3);
        simulateInitializationSync(image1, backendImage1);
        simulateInitializationSync(image2, backendImage2);

        // THEN
        QCOMPARE(backendTexture1->textureImageIds().size(), 1);
        QCOMPARE(backendTexture1->textureImageIds().first(), image1->id());
        QCOMPARE(backendTexture2->textureImageIds().size(), 1);
        QCOMPARE(backendTexture2->textureImageIds().first(), image2->id());
        QCOMPARE(backendTexture3->textureImageIds().size(), 2);
        QCOMPARE(backendTexture3->textureImageIds().first(), image1->id());
        QCOMPARE(backendTexture3->textureImageIds().last(), image2->id());

        // WHEN
        backendTexture1->unsetDirty();
        backendTexture2->unsetDirty();
        backendTexture3->unsetDirty();
        backendImage1->unsetDirty();
        backendImage2->unsetDirty();

        // THEN
        QVERIFY(backendTexture1->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture2->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture3->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);

        // WHEN
        renderer.textureGathererJob()->run();

        // THEN
        QVERIFY(backendTexture1->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture2->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture3->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);

        // WHEN
        // Make Image1 dirty
        image1->updateGenerator();
        backendImage1->syncFromFrontEnd(image1, false);

        // THEN
        QVERIFY(backendImage1->isDirty());
        QVERIFY(backendTexture1->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture2->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture3->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);

        // WHEN
        renderer.textureGathererJob()->run();

        // THEN
        QVERIFY(backendTexture1->dirtyFlags() & Qt3DRender::Render::Texture::DirtyImageGenerators);
        QVERIFY(backendTexture2->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture3->dirtyFlags() & Qt3DRender::Render::Texture::DirtyImageGenerators);

        backendImage1->unsetDirty();
        backendTexture1->unsetDirty();
        backendTexture3->unsetDirty();

        // WHEN
        image2->updateGenerator();
        backendImage2->syncFromFrontEnd(image2, false);

        // THEN
        QVERIFY(backendImage2->isDirty());
        QVERIFY(backendTexture1->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture2->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture3->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);

        // WHEN
        renderer.textureGathererJob()->run();

        QVERIFY(backendTexture1->dirtyFlags() == Qt3DRender::Render::Texture::NotDirty);
        QVERIFY(backendTexture2->dirtyFlags() & Qt3DRender::Render::Texture::DirtyImageGenerators);
        QVERIFY(backendTexture3->dirtyFlags() & Qt3DRender::Render::Texture::DirtyImageGenerators);

        renderer.shutdown();
    }
};

QTEST_MAIN(tst_RenderTextures)

#include "tst_textures.moc"
