Revision control

Copy as Markdown

// 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"