// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include "qsgsoftwarethreadedrenderloop_p.h"
#include "qsgsoftwarecontext_p.h"
#include "qsgsoftwarerenderer_p.h"

#include <private/qsgrenderer_p.h>
#include <private/qquickwindow_p.h>
#include <private/qquickitem_p.h>
#include <private/qquickprofiler_p.h>
#include <private/qquickanimatorcontroller_p.h>
#include <private/qquickprofiler_p.h>
#include <private/qqmldebugserviceinterfaces_p.h>
#include <private/qqmldebugconnector_p.h>

#include <qpa/qplatformbackingstore.h>

#include <QtCore/QQueue>
#include <QtCore/QElapsedTimer>
#include <QtCore/QThread>
#include <QtCore/QMutex>
#include <QtCore/QWaitCondition>
#include <QtGui/QGuiApplication>
#include <QtGui/QBackingStore>
#include <QtQuick/QQuickWindow>

#include <qtquick_tracepoints_p.h>

QT_BEGIN_NAMESPACE

// Used to debug the renderloop logic. Primarily useful for platform integrators
// and when investigating the render loop logic.
Q_STATIC_LOGGING_CATEGORY(QSG_RASTER_LOG_RENDERLOOP,          "qt.scenegraph.renderloop")

class QSGSoftwareWindowEvent : public QEvent
{
public:
    QSGSoftwareWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
    QQuickWindow *window;
};

class QSGSoftwareTryReleaseEvent : public QSGSoftwareWindowEvent
{
public:
    QSGSoftwareTryReleaseEvent(QQuickWindow *win, bool destroy)
        : QSGSoftwareWindowEvent(win, QEvent::Type(WM_TryRelease)), destroying(destroy) { }
    bool destroying;
};

class QSGSoftwareSyncEvent : public QSGSoftwareWindowEvent
{
public:
    QSGSoftwareSyncEvent(QQuickWindow *c, bool inExpose, bool force)
        : QSGSoftwareWindowEvent(c, QEvent::Type(WM_RequestSync))
        , size(c->size())
        , dpr(c->effectiveDevicePixelRatio())
        , syncInExpose(inExpose)
        , forceRenderPass(force) { }
    QSize size;
    float dpr;
    bool syncInExpose;
    bool forceRenderPass;
};

class QSGSoftwareGrabEvent : public QSGSoftwareWindowEvent
{
public:
    QSGSoftwareGrabEvent(QQuickWindow *c, QImage *result)
        : QSGSoftwareWindowEvent(c, QEvent::Type(WM_Grab)), image(result) { }
    QImage *image;
};

class QSGSoftwareJobEvent : public QSGSoftwareWindowEvent
{
public:
    QSGSoftwareJobEvent(QQuickWindow *c, QRunnable *postedJob)
        : QSGSoftwareWindowEvent(c, QEvent::Type(WM_PostJob)), job(postedJob) { }
    ~QSGSoftwareJobEvent() { delete job; }
    QRunnable *job;
};

class QSGSoftwareEventQueue : public QQueue<QEvent *>
{
public:
    void addEvent(QEvent *e) {
        mutex.lock();
        enqueue(e);
        if (waiting)
            condition.wakeOne();
        mutex.unlock();
    }

    QEvent *takeEvent(bool wait) {
        mutex.lock();
        if (isEmpty() && wait) {
            waiting = true;
            condition.wait(&mutex);
            waiting = false;
        }
        QEvent *e = dequeue();
        mutex.unlock();
        return e;
    }

    bool hasMoreEvents() {
        mutex.lock();
        bool has = !isEmpty();
        mutex.unlock();
        return has;
    }

private:
    QMutex mutex;
    QWaitCondition condition;
    bool waiting = false;
};

static inline int qsgrl_animation_interval()
{
    const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0;
    return refreshRate < 1 ? 16 : int(1000 / refreshRate);
}

class QSGSoftwareRenderThread : public QThread
{
    Q_OBJECT
public:
    QSGSoftwareRenderThread(QSGSoftwareThreadedRenderLoop *rl, QSGRenderContext *renderContext)
        : renderLoop(rl)
    {
        rc = static_cast<QSGSoftwareRenderContext *>(renderContext);
        vsyncDelta = qsgrl_animation_interval();
    }

    ~QSGSoftwareRenderThread()
    {
        delete rc;
    }

    bool event(QEvent *e) override;
    void run() override;

    void syncAndRender();
    void sync(bool inExpose);

    void requestRepaint()
    {
        if (sleeping)
            stopEventProcessing = true;
        if (exposedWindow)
            pendingUpdate |= RepaintRequest;
    }

    void processEventsAndWaitForMore();
    void processEvents();
    void postEvent(QEvent *e);

    enum UpdateRequest {
        SyncRequest         = 0x01,
        RepaintRequest      = 0x02,
        ExposeRequest       = 0x04 | RepaintRequest | SyncRequest
    };

    QSGSoftwareThreadedRenderLoop *renderLoop;
    QSGSoftwareRenderContext *rc;
    QAnimationDriver *rtAnim = nullptr;
    volatile bool active = false;
    uint pendingUpdate = 0;
    bool sleeping = false;
    bool syncResultedInChanges = false;
    float vsyncDelta;
    QMutex mutex;
    QWaitCondition waitCondition;
    QQuickWindow *exposedWindow = nullptr;
    QBackingStore *backingStore = nullptr;
    bool stopEventProcessing = false;
    QSGSoftwareEventQueue eventQueue;
    QElapsedTimer renderThrottleTimer;
    qint64 syncTime;
    qint64 renderTime;
    qint64 sinceLastTime;

public slots:
    void onSceneGraphChanged() {
        syncResultedInChanges = true;
    }
};

bool QSGSoftwareRenderThread::event(QEvent *e)
{
    switch ((int)e->type()) {

    case WM_Obscure:
        Q_ASSERT(!exposedWindow || exposedWindow == static_cast<QSGSoftwareWindowEvent *>(e)->window);
        qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "RT - WM_Obscure" << exposedWindow;
        mutex.lock();
        if (exposedWindow) {
            QQuickWindowPrivate::get(exposedWindow)->fireAboutToStop();
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Obscure - window removed");
            exposedWindow = nullptr;
            delete backingStore;
            backingStore = nullptr;
        }
        waitCondition.wakeOne();
        mutex.unlock();
        return true;

    case WM_RequestSync: {
        QSGSoftwareSyncEvent *wme = static_cast<QSGSoftwareSyncEvent *>(e);
        if (sleeping)
            stopEventProcessing = true;
        exposedWindow = wme->window;
        if (backingStore == nullptr)
            backingStore = new QBackingStore(exposedWindow);
        if (backingStore->size() != exposedWindow->size())
            backingStore->resize(exposedWindow->size());
        qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "RT - WM_RequestSync" << exposedWindow;
        pendingUpdate |= SyncRequest;
        if (wme->syncInExpose) {
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_RequestSync - triggered from expose");
            pendingUpdate |= ExposeRequest;
        }
        if (wme->forceRenderPass) {
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_RequestSync - repaint regardless");
            pendingUpdate |= RepaintRequest;
        }
        return true;
    }

    case WM_TryRelease: {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease");
        mutex.lock();
        renderLoop->lockedForSync = true;
        QSGSoftwareTryReleaseEvent *wme = static_cast<QSGSoftwareTryReleaseEvent *>(e);
        // Only when no windows are exposed anymore or we are shutting down.
        if (!exposedWindow || wme->destroying) {
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease - invalidating rc");
            if (wme->window) {
                QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
                if (wme->destroying) {
                    // Bye bye nodes...
                    wd->cleanupNodesOnShutdown();
                }
                rc->invalidate();
                QCoreApplication::processEvents();
                QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
                if (wme->destroying)
                    wd->animationController.reset();
            }
            if (wme->destroying)
                active = false;
            if (sleeping)
                stopEventProcessing = true;
        } else {
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease - not releasing because window is still active");
        }
        waitCondition.wakeOne();
        renderLoop->lockedForSync = false;
        mutex.unlock();
        return true;
    }

    case WM_Grab: {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Grab");
        QSGSoftwareGrabEvent *wme = static_cast<QSGSoftwareGrabEvent *>(e);
        Q_ASSERT(wme->window);
        Q_ASSERT(wme->window == exposedWindow || !exposedWindow);
        mutex.lock();
        if (wme->window) {
            // Grabbing is generally done by rendering a frame and reading the
            // color buffer contents back, without presenting, and then
            // creating a QImage from the returned data. It is terribly
            // inefficient since it involves a full blocking wait for the GPU.
            // However, our hands are tied by the existing, synchronous APIs of
            // QQuickWindow and such.
            QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
            auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer);
            if (softwareRenderer)
                softwareRenderer->setBackingStore(backingStore);
            rc->initialize(nullptr);
            wd->syncSceneGraph();
            rc->endSync();
            wd->renderSceneGraph();
            *wme->image = backingStore->handle()->toImage();
        }
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Grab - waking gui to handle result");
        waitCondition.wakeOne();
        mutex.unlock();
        return true;
    }

    case WM_PostJob: {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_PostJob");
        QSGSoftwareJobEvent *wme = static_cast<QSGSoftwareJobEvent *>(e);
        Q_ASSERT(wme->window == exposedWindow);
        if (exposedWindow) {
            wme->job->run();
            delete wme->job;
            wme->job = nullptr;
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_PostJob - job done");
        }
        return true;
    }

    default:
        break;
    }

    return QThread::event(e);
}

void QSGSoftwareRenderThread::postEvent(QEvent *e)
{
    eventQueue.addEvent(e);
}

void QSGSoftwareRenderThread::processEvents()
{
    while (eventQueue.hasMoreEvents()) {
        QEvent *e = eventQueue.takeEvent(false);
        event(e);
        delete e;
    }
}

void QSGSoftwareRenderThread::processEventsAndWaitForMore()
{
    stopEventProcessing = false;
    while (!stopEventProcessing) {
        QEvent *e = eventQueue.takeEvent(true);
        event(e);
        delete e;
    }
}

void QSGSoftwareRenderThread::run()
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - run()");

    rtAnim = rc->sceneGraphContext()->createAnimationDriver(nullptr);
    rtAnim->install();

    if (QQmlDebugConnector::service<QQmlProfilerService>())
        QQuickProfiler::registerAnimationCallback();

    renderThrottleTimer.start();

    while (active) {
        if (exposedWindow)
            syncAndRender();

        processEvents();
        QCoreApplication::processEvents();

        if (pendingUpdate == 0 || !exposedWindow) {
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - done drawing, sleep");
            sleeping = true;
            processEventsAndWaitForMore();
            sleeping = false;
        }
    }

    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - run() exiting");

    delete rtAnim;
    rtAnim = nullptr;

    rc->moveToThread(renderLoop->thread());
    moveToThread(renderLoop->thread());
}

void QSGSoftwareRenderThread::sync(bool inExpose)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - sync");

    mutex.lock();
    Q_ASSERT_X(renderLoop->lockedForSync, "QSGSoftwareRenderThread::sync()", "sync triggered with gui not locked");

    if (exposedWindow) {
        QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
        bool hadRenderer = wd->renderer != nullptr;
        // If the scene graph was touched since the last sync() make sure it sends the
        // changed signal.
        if (wd->renderer)
            wd->renderer->clearChangedFlag();

        rc->initialize(nullptr);
        wd->syncSceneGraph();
        rc->endSync();

        if (!hadRenderer && wd->renderer) {
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - created renderer");
            syncResultedInChanges = true;
            connect(wd->renderer, &QSGRenderer::sceneGraphChanged, this,
                    &QSGSoftwareRenderThread::onSceneGraphChanged, Qt::DirectConnection);
        }

        // Process deferred deletes now, directly after the sync as deleteLater
        // on the GUI must now also have resulted in SG changes and the delete
        // is a safe operation.
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    }

    if (!inExpose) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - sync complete, waking gui");
        waitCondition.wakeOne();
        mutex.unlock();
    }
}

void QSGSoftwareRenderThread::syncAndRender()
{
    Q_TRACE_SCOPE(QSG_syncAndRender);
    Q_TRACE(QSG_sync_entry);
    Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);

    QElapsedTimer waitTimer;
    waitTimer.start();

    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - syncAndRender()");

    syncResultedInChanges = false;
    QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);

    const bool repaintRequested = pendingUpdate & RepaintRequest;
    const bool syncRequested = pendingUpdate & SyncRequest;
    const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
    pendingUpdate = 0;

    emit exposedWindow->beforeFrameBegin();

    if (syncRequested)
        sync(exposeRequested);

    Q_TRACE(QSG_sync_exit);
    Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
                              QQuickProfiler::SceneGraphRenderLoopSync);

    if (!syncResultedInChanges && !repaintRequested) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - no changes, render aborted");
        int waitTime = vsyncDelta - (int) waitTimer.elapsed();
        if (waitTime > 0)
            msleep(waitTime);
        return;
    }

    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - rendering started");
    Q_TRACE(QSG_render_entry);

    if (rtAnim->isRunning()) {
        wd->animationController->lock();
        rtAnim->advance();
        wd->animationController->unlock();
    }

    bool canRender = wd->renderer != nullptr;

    if (canRender) {
        auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer);
        if (softwareRenderer)
            softwareRenderer->setBackingStore(backingStore);
        wd->renderSceneGraph();

        Q_TRACE(QSG_render_exit);
        Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
                                  QQuickProfiler::SceneGraphRenderLoopRender);
        Q_TRACE(QSG_swap_entry);

        if (softwareRenderer)
            backingStore->flush(softwareRenderer->flushRegion());

        // Since there is no V-Sync with QBackingStore, throttle rendering the refresh
        // rate of the current screen the window is on.
        int blockTime = vsyncDelta - (int) renderThrottleTimer.elapsed();
        if (blockTime > 0) {
            qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - blocking for %d ms", blockTime);
            msleep(blockTime);
        }
        renderThrottleTimer.start();

        wd->fireFrameSwapped();
    } else {
        Q_TRACE(QSG_render_exit);
        Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
                                QQuickProfiler::SceneGraphRenderLoopSync, 1);
        Q_TRACE(QSG_swap_entry);
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - window not ready, skipping render");
    }

    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - rendering done");

    emit exposedWindow->afterFrameEnd();

    if (exposeRequested) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - wake gui after initial expose");
        waitCondition.wakeOne();
        mutex.unlock();
    }

    Q_TRACE(QSG_swap_exit);
    Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
                           QQuickProfiler::SceneGraphRenderLoopSwap);
}

QSGSoftwareThreadedRenderLoop::WindowData *QSGSoftwareThreadedRenderLoop::windowFor(QQuickWindow *window)
{
    for (const auto &t : std::as_const(m_windows)) {
        if (t.window == window)
            return const_cast<WindowData *>(&t);
    }
    return nullptr;
}


QSGSoftwareThreadedRenderLoop::QSGSoftwareThreadedRenderLoop()
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop constructor");
    m_sg = new QSGSoftwareContext;
    m_anim = m_sg->createAnimationDriver(this);
    connect(m_anim, &QAnimationDriver::started, this, &QSGSoftwareThreadedRenderLoop::onAnimationStarted);
    connect(m_anim, &QAnimationDriver::stopped, this, &QSGSoftwareThreadedRenderLoop::onAnimationStopped);
    m_anim->install();
}

QSGSoftwareThreadedRenderLoop::~QSGSoftwareThreadedRenderLoop()
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop destructor");
    delete m_sg;
}

void QSGSoftwareThreadedRenderLoop::show(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "show" << window;
}

void QSGSoftwareThreadedRenderLoop::hide(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "hide" << window;

    if (window->isExposed())
        handleObscurity(windowFor(window));

    releaseResources(window);
}

void QSGSoftwareThreadedRenderLoop::resize(QQuickWindow *window)
{
    if (!window->isExposed() || window->size().isEmpty())
        return;

    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "resize" << window << window->size();
}

void QSGSoftwareThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "window destroyed" << window;

    WindowData *w = windowFor(window);
    if (!w)
        return;

    handleObscurity(w);
    handleResourceRelease(w, true);

    QSGSoftwareRenderThread *thread = w->thread;
    while (thread->isRunning())
        QThread::yieldCurrentThread();

    Q_ASSERT(thread->thread() == QThread::currentThread());
    delete thread;

    for (int i = 0; i < m_windows.size(); ++i) {
        if (m_windows.at(i).window == window) {
            m_windows.removeAt(i);
            break;
        }
    }

    // Now that we altered the window list, we may need to stop the animation
    // timer even if we didn't via handleObscurity. This covers the case where
    // we destroy a visible & exposed QQuickWindow.
    startOrStopAnimationTimer();
}

void QSGSoftwareThreadedRenderLoop::exposureChanged(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "exposure changed" << window;

    if (window->isExposed()) {
        handleExposure(window);
    } else {
        WindowData *w = windowFor(window);
        if (w)
            handleObscurity(w);
    }
}

QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "grab" << window;

    WindowData *w = windowFor(window);
    // Have to support invisible (but created()'ed) windows as well.
    // Unlike with GL, leaving that case for QQuickWindow to handle is not feasible.
    const bool tempExpose = !w;
    if (tempExpose) {
        handleExposure(window);
        w = windowFor(window);
        Q_ASSERT(w);
    }

    if (!w->thread->isRunning())
        return QImage();

    if (!window->handle())
        window->create();

    QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
    wd->polishItems();

    QImage result;
    w->thread->mutex.lock();
    lockedForSync = true;
    w->thread->postEvent(new QSGSoftwareGrabEvent(window, &result));
    w->thread->waitCondition.wait(&w->thread->mutex);
    lockedForSync = false;
    w->thread->mutex.unlock();

    result.setDevicePixelRatio(window->effectiveDevicePixelRatio());

    if (tempExpose)
        handleObscurity(w);

    return result;
}

void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window)
{
    WindowData *w = windowFor(window);
    if (!w)
        return;

    if (w->thread == QThread::currentThread()) {
        w->thread->requestRepaint();
        return;
    }

    // We set forceRenderPass because we want to make sure the QQuickWindow
    // actually does a full render pass after the next sync.
    w->forceRenderPass = true;
    scheduleUpdate(w);
}

void QSGSoftwareThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
{
    WindowData *w = windowFor(window);
    if (w)
        scheduleUpdate(w);
}

void QSGSoftwareThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleUpdateRequest" << window;

    WindowData *w = windowFor(window);
    if (w)
        polishAndSync(w, false);
}

QAnimationDriver *QSGSoftwareThreadedRenderLoop::animationDriver() const
{
    return m_anim;
}

QSGContext *QSGSoftwareThreadedRenderLoop::sceneGraphContext() const
{
    return m_sg;
}

QSGRenderContext *QSGSoftwareThreadedRenderLoop::createRenderContext(QSGContext *) const
{
    return m_sg->createRenderContext();
}

void QSGSoftwareThreadedRenderLoop::releaseResources(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "releaseResources" << window;

    WindowData *w = windowFor(window);
    if (w)
        handleResourceRelease(w, false);
}

void QSGSoftwareThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
{
    WindowData *w = windowFor(window);
    if (w && w->thread && w->thread->exposedWindow)
        w->thread->postEvent(new QSGSoftwareJobEvent(window, job));
    else
        delete job;
}

QSurface::SurfaceType QSGSoftwareThreadedRenderLoop::windowSurfaceType() const
{
    return QSurface::RasterSurface;
}

bool QSGSoftwareThreadedRenderLoop::interleaveIncubation() const
{
    bool somethingVisible = false;
    for (const WindowData &w : m_windows) {
        if (w.window->isVisible() && w.window->isExposed()) {
            somethingVisible = true;
            break;
        }
    }
    return somethingVisible && m_anim->isRunning();
}

int QSGSoftwareThreadedRenderLoop::flags() const
{
    return SupportsGrabWithoutExpose;
}

bool QSGSoftwareThreadedRenderLoop::event(QEvent *e)
{
    if (e->type() == QEvent::Timer) {
        QTimerEvent *te = static_cast<QTimerEvent *>(e);
        if (te->timerId() == animationTimer) {
            m_anim->advance();
            emit timeToIncubate();
            return true;
        }
    }

    return QObject::event(e);
}

void QSGSoftwareThreadedRenderLoop::onAnimationStarted()
{
    startOrStopAnimationTimer();

    for (const WindowData &w : std::as_const(m_windows))
        w.window->requestUpdate();
}

void QSGSoftwareThreadedRenderLoop::onAnimationStopped()
{
    startOrStopAnimationTimer();
}

void QSGSoftwareThreadedRenderLoop::startOrStopAnimationTimer()
{
    int exposedWindowCount = 0;
    const WindowData *exposed = nullptr;

    for (int i = 0; i < m_windows.size(); ++i) {
        const WindowData &w(m_windows[i]);
        if (w.window->isVisible() && w.window->isExposed()) {
            ++exposedWindowCount;
            exposed = &w;
        }
    }

    if (animationTimer && (exposedWindowCount == 1 || !m_anim->isRunning())) {
        killTimer(animationTimer);
        animationTimer = 0;
        // If animations are running, make sure we keep on animating
        if (m_anim->isRunning())
            exposed->window->requestUpdate();
    } else if (!animationTimer && exposedWindowCount != 1 && m_anim->isRunning()) {
        animationTimer = startTimer(qsgrl_animation_interval());
    }
}

void QSGSoftwareThreadedRenderLoop::handleExposure(QQuickWindow *window)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleExposure" << window;

    WindowData *w = windowFor(window);
    if (!w) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "adding window to list");
        WindowData win;
        win.window = window;
        QSGRenderContext *rc = QQuickWindowPrivate::get(window)->context; // will transfer ownership
        win.thread = new QSGSoftwareRenderThread(this, rc);
        win.updateDuringSync = false;
        win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
        m_windows.append(win);
        w = &m_windows.last();
    }

    // set this early as we'll be rendering shortly anyway and this avoids
    // special casing exposure in polishAndSync.
    w->thread->exposedWindow = window;

    if (w->window->size().isEmpty()
        || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
#ifndef QT_NO_DEBUG
        qWarning().noquote().nospace() << "QSGSotwareThreadedRenderLoop: expose event received for window "
            << w->window << " with invalid geometry: " << w->window->geometry()
            << " on " << w->window->screen();
#endif
    }

    if (!w->window->handle())
        w->window->create();

    // Start render thread if it is not running
    if (!w->thread->isRunning()) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "starting render thread");
        // Push a few things to the render thread.
        QQuickAnimatorController *controller
                = QQuickWindowPrivate::get(w->window)->animationController.get();
        if (controller->thread() != w->thread)
            controller->moveToThread(w->thread);
        if (w->thread->thread() == QThread::currentThread()) {
            w->thread->rc->moveToThread(w->thread);
            w->thread->moveToThread(w->thread);
        }

        w->thread->active = true;
        w->thread->start();

        if (!w->thread->isRunning())
            qFatal("Render thread failed to start, aborting application.");
    }

    polishAndSync(w, true);

    startOrStopAnimationTimer();
}

void QSGSoftwareThreadedRenderLoop::handleObscurity(QSGSoftwareThreadedRenderLoop::WindowData *w)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleObscurity" << w->window;

    if (w->thread->isRunning()) {
        w->thread->mutex.lock();
        w->thread->postEvent(new QSGSoftwareWindowEvent(w->window, QEvent::Type(WM_Obscure)));
        w->thread->waitCondition.wait(&w->thread->mutex);
        w->thread->mutex.unlock();
    }

    startOrStopAnimationTimer();
}

void QSGSoftwareThreadedRenderLoop::scheduleUpdate(QSGSoftwareThreadedRenderLoop::WindowData *w)
{
    if (!QCoreApplication::instance())
        return;

    if (!w || !w->thread->isRunning())
        return;

    QThread *current = QThread::currentThread();
    if (current != QCoreApplication::instance()->thread() && (current != w->thread || !lockedForSync)) {
        qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
        return;
    }

    if (current == w->thread) {
        w->updateDuringSync = true;
        return;
    }

    w->window->requestUpdate();
}

void QSGSoftwareThreadedRenderLoop::handleResourceRelease(QSGSoftwareThreadedRenderLoop::WindowData *w, bool destroying)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleResourceRelease" << (destroying ? "destroying" : "hide/releaseResources") << w->window;

    w->thread->mutex.lock();
    if (w->thread->isRunning() && w->thread->active) {
        QQuickWindow *window = w->window;

        // Note that window->handle() is typically null by this time because
        // the platform window is already destroyed. This should not be a
        // problem for the D3D cleanup.

        w->thread->postEvent(new QSGSoftwareTryReleaseEvent(window, destroying));
        w->thread->waitCondition.wait(&w->thread->mutex);

        // Avoid a shutdown race condition.
        // If SG is invalidated and 'active' becomes false, the thread's run()
        // method will exit. handleExposure() relies on QThread::isRunning() (because it
        // potentially needs to start the thread again) and our mutex cannot be used to
        // track the thread stopping, so we wait a few nanoseconds extra so the thread
        // can exit properly.
        if (!w->thread->active)
            w->thread->wait();
    }
    w->thread->mutex.unlock();
}

void QSGSoftwareThreadedRenderLoop::polishAndSync(QSGSoftwareThreadedRenderLoop::WindowData *w, bool inExpose)
{
    qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;

    QQuickWindow *window = w->window;
    if (!w->thread || !w->thread->exposedWindow) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - not exposed, abort");
        return;
    }

    // Flush pending touch events.
    QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
    // The delivery of the event might have caused the window to stop rendering
    w = windowFor(window);
    if (!w || !w->thread || !w->thread->exposedWindow) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - removed after touch event flushing, abort");
        return;
    }

    Q_TRACE_SCOPE(QSG_polishAndSync);

    Q_TRACE(QSG_polishItems_entry);
    Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);

    QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
    wd->polishItems();

    Q_TRACE(QSG_polishItems_exit);
    Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
                              QQuickProfiler::SceneGraphPolishAndSyncPolish);
    Q_TRACE(QSG_sync_entry);

    w->updateDuringSync = false;

    emit window->afterAnimating();

    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - lock for sync");
    w->thread->mutex.lock();
    lockedForSync = true;
    w->thread->postEvent(new QSGSoftwareSyncEvent(window, inExpose, w->forceRenderPass));
    w->forceRenderPass = false;

    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - wait for sync");

    Q_TRACE(QSG_sync_exit);
    Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
                              QQuickProfiler::SceneGraphPolishAndSyncWait);
    Q_TRACE(QSG_wait_entry);
    w->thread->waitCondition.wait(&w->thread->mutex);
    lockedForSync = false;
    w->thread->mutex.unlock();
    qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - unlock after sync");

    Q_TRACE(QSG_wait_exit);
    Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
                              QQuickProfiler::SceneGraphPolishAndSyncSync);
    Q_TRACE(QSG_animations_entry);

    if (!animationTimer && m_anim->isRunning()) {
        qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - advancing animations");
        m_anim->advance();
        // We need to trigger another sync to keep animations running...
        w->window->requestUpdate();
        emit timeToIncubate();
    } else if (w->updateDuringSync) {
        w->window->requestUpdate();
    }

    Q_TRACE(QSG_animations_exit);
    Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
                           QQuickProfiler::SceneGraphPolishAndSyncAnimations);
}

QT_END_NAMESPACE

#include "qsgsoftwarethreadedrenderloop.moc"
#include "moc_qsgsoftwarethreadedrenderloop_p.cpp"
