| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtQuick module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #include "qsgsoftwarethreadedrenderloop_p.h" | 
| 41 | #include "qsgsoftwarecontext_p.h" | 
| 42 | #include "qsgsoftwarerenderer_p.h" | 
| 43 |  | 
| 44 | #include <private/qsgrenderer_p.h> | 
| 45 | #include <private/qquickwindow_p.h> | 
| 46 | #include <private/qquickprofiler_p.h> | 
| 47 | #include <private/qquickanimatorcontroller_p.h> | 
| 48 | #include <private/qquickprofiler_p.h> | 
| 49 | #include <private/qqmldebugserviceinterfaces_p.h> | 
| 50 | #include <private/qqmldebugconnector_p.h> | 
| 51 |  | 
| 52 | #include <qpa/qplatformbackingstore.h> | 
| 53 |  | 
| 54 | #include <QtCore/QQueue> | 
| 55 | #include <QtCore/QElapsedTimer> | 
| 56 | #include <QtCore/QThread> | 
| 57 | #include <QtCore/QMutex> | 
| 58 | #include <QtCore/QWaitCondition> | 
| 59 | #include <QtGui/QGuiApplication> | 
| 60 | #include <QtGui/QBackingStore> | 
| 61 | #include <QtQuick/QQuickWindow> | 
| 62 |  | 
| 63 | #include <qtquick_tracepoints_p.h> | 
| 64 |  | 
| 65 | QT_BEGIN_NAMESPACE | 
| 66 |  | 
| 67 | // Passed from the RL to the RT when a window is removed obscured and should be | 
| 68 | // removed from the render loop. | 
| 69 | const QEvent::Type WM_Obscure           = QEvent::Type(QEvent::User + 1); | 
| 70 |  | 
| 71 | // Passed from the RL to RT when GUI has been locked, waiting for sync. | 
| 72 | const QEvent::Type WM_RequestSync       = QEvent::Type(QEvent::User + 2); | 
| 73 |  | 
| 74 | // Passed by the RL to the RT to maybe release resource if no windows are | 
| 75 | // rendering. | 
| 76 | const QEvent::Type WM_TryRelease        = QEvent::Type(QEvent::User + 4); | 
| 77 |  | 
| 78 | // Passed by the RL to the RT when a QQuickWindow::grabWindow() is called. | 
| 79 | const QEvent::Type WM_Grab              = QEvent::Type(QEvent::User + 5); | 
| 80 |  | 
| 81 | // Passed by the window when there is a render job to run. | 
| 82 | const QEvent::Type WM_PostJob           = QEvent::Type(QEvent::User + 6); | 
| 83 |  | 
| 84 | class QSGSoftwareWindowEvent : public QEvent | 
| 85 | { | 
| 86 | public: | 
| 87 |     QSGSoftwareWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { } | 
| 88 |     QQuickWindow *window; | 
| 89 | }; | 
| 90 |  | 
| 91 | class QSGSoftwareTryReleaseEvent : public QSGSoftwareWindowEvent | 
| 92 | { | 
| 93 | public: | 
| 94 |     QSGSoftwareTryReleaseEvent(QQuickWindow *win, bool destroy) | 
| 95 |         : QSGSoftwareWindowEvent(win, WM_TryRelease), destroying(destroy) { } | 
| 96 |     bool destroying; | 
| 97 | }; | 
| 98 |  | 
| 99 | class QSGSoftwareSyncEvent : public QSGSoftwareWindowEvent | 
| 100 | { | 
| 101 | public: | 
| 102 |     QSGSoftwareSyncEvent(QQuickWindow *c, bool inExpose, bool force) | 
| 103 |         : QSGSoftwareWindowEvent(c, WM_RequestSync) | 
| 104 |         , size(c->size()) | 
| 105 |         , dpr(c->effectiveDevicePixelRatio()) | 
| 106 |         , syncInExpose(inExpose) | 
| 107 |         , forceRenderPass(force) { } | 
| 108 |     QSize size; | 
| 109 |     float dpr; | 
| 110 |     bool syncInExpose; | 
| 111 |     bool forceRenderPass; | 
| 112 | }; | 
| 113 |  | 
| 114 | class QSGSoftwareGrabEvent : public QSGSoftwareWindowEvent | 
| 115 | { | 
| 116 | public: | 
| 117 |     QSGSoftwareGrabEvent(QQuickWindow *c, QImage *result) | 
| 118 |         : QSGSoftwareWindowEvent(c, WM_Grab), image(result) { } | 
| 119 |     QImage *image; | 
| 120 | }; | 
| 121 |  | 
| 122 | class QSGSoftwareJobEvent : public QSGSoftwareWindowEvent | 
| 123 | { | 
| 124 | public: | 
| 125 |     QSGSoftwareJobEvent(QQuickWindow *c, QRunnable *postedJob) | 
| 126 |         : QSGSoftwareWindowEvent(c, WM_PostJob), job(postedJob) { } | 
| 127 |     ~QSGSoftwareJobEvent() { delete job; } | 
| 128 |     QRunnable *job; | 
| 129 | }; | 
| 130 |  | 
| 131 | class QSGSoftwareEventQueue : public QQueue<QEvent *> | 
| 132 | { | 
| 133 | public: | 
| 134 |     void addEvent(QEvent *e) { | 
| 135 |         mutex.lock(); | 
| 136 |         enqueue(t: e); | 
| 137 |         if (waiting) | 
| 138 |             condition.wakeOne(); | 
| 139 |         mutex.unlock(); | 
| 140 |     } | 
| 141 |  | 
| 142 |     QEvent *takeEvent(bool wait) { | 
| 143 |         mutex.lock(); | 
| 144 |         if (isEmpty() && wait) { | 
| 145 |             waiting = true; | 
| 146 |             condition.wait(lockedMutex: &mutex); | 
| 147 |             waiting = false; | 
| 148 |         } | 
| 149 |         QEvent *e = dequeue(); | 
| 150 |         mutex.unlock(); | 
| 151 |         return e; | 
| 152 |     } | 
| 153 |  | 
| 154 |     bool hasMoreEvents() { | 
| 155 |         mutex.lock(); | 
| 156 |         bool has = !isEmpty(); | 
| 157 |         mutex.unlock(); | 
| 158 |         return has; | 
| 159 |     } | 
| 160 |  | 
| 161 | private: | 
| 162 |     QMutex mutex; | 
| 163 |     QWaitCondition condition; | 
| 164 |     bool waiting = false; | 
| 165 | }; | 
| 166 |  | 
| 167 | static inline int qsgrl_animation_interval() | 
| 168 | { | 
| 169 |     const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0; | 
| 170 |     return refreshRate < 1 ? 16 : int(1000 / refreshRate); | 
| 171 | } | 
| 172 |  | 
| 173 | class QSGSoftwareRenderThread : public QThread | 
| 174 | { | 
| 175 |     Q_OBJECT | 
| 176 | public: | 
| 177 |     QSGSoftwareRenderThread(QSGSoftwareThreadedRenderLoop *rl, QSGRenderContext *renderContext) | 
| 178 |         : renderLoop(rl) | 
| 179 |     { | 
| 180 |         rc = static_cast<QSGSoftwareRenderContext *>(renderContext); | 
| 181 |         vsyncDelta = qsgrl_animation_interval(); | 
| 182 |     } | 
| 183 |  | 
| 184 |     ~QSGSoftwareRenderThread() | 
| 185 |     { | 
| 186 |         delete rc; | 
| 187 |     } | 
| 188 |  | 
| 189 |     bool event(QEvent *e) override; | 
| 190 |     void run() override; | 
| 191 |  | 
| 192 |     void syncAndRender(); | 
| 193 |     void sync(bool inExpose); | 
| 194 |  | 
| 195 |     void requestRepaint() | 
| 196 |     { | 
| 197 |         if (sleeping) | 
| 198 |             stopEventProcessing = true; | 
| 199 |         if (exposedWindow) | 
| 200 |             pendingUpdate |= RepaintRequest; | 
| 201 |     } | 
| 202 |  | 
| 203 |     void processEventsAndWaitForMore(); | 
| 204 |     void processEvents(); | 
| 205 |     void postEvent(QEvent *e); | 
| 206 |  | 
| 207 |     enum UpdateRequest { | 
| 208 |         SyncRequest         = 0x01, | 
| 209 |         RepaintRequest      = 0x02, | 
| 210 |         ExposeRequest       = 0x04 | RepaintRequest | SyncRequest | 
| 211 |     }; | 
| 212 |  | 
| 213 |     QSGSoftwareThreadedRenderLoop *renderLoop; | 
| 214 |     QSGSoftwareRenderContext *rc; | 
| 215 |     QAnimationDriver *rtAnim = nullptr; | 
| 216 |     volatile bool active = false; | 
| 217 |     uint pendingUpdate = 0; | 
| 218 |     bool sleeping = false; | 
| 219 |     bool syncResultedInChanges = false; | 
| 220 |     float vsyncDelta; | 
| 221 |     QMutex mutex; | 
| 222 |     QWaitCondition waitCondition; | 
| 223 |     QQuickWindow *exposedWindow = nullptr; | 
| 224 |     QBackingStore *backingStore = nullptr; | 
| 225 |     bool stopEventProcessing = false; | 
| 226 |     QSGSoftwareEventQueue eventQueue; | 
| 227 |     QElapsedTimer renderThrottleTimer; | 
| 228 |     qint64 syncTime; | 
| 229 |     qint64 renderTime; | 
| 230 |     qint64 sinceLastTime; | 
| 231 |  | 
| 232 | public slots: | 
| 233 |     void onSceneGraphChanged() { | 
| 234 |         syncResultedInChanges = true; | 
| 235 |     } | 
| 236 | }; | 
| 237 |  | 
| 238 | bool QSGSoftwareRenderThread::event(QEvent *e) | 
| 239 | { | 
| 240 |     switch ((int)e->type()) { | 
| 241 |  | 
| 242 |     case WM_Obscure: | 
| 243 |         Q_ASSERT(!exposedWindow || exposedWindow == static_cast<QSGSoftwareWindowEvent *>(e)->window); | 
| 244 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "RT - WM_Obscure"  << exposedWindow; | 
| 245 |         mutex.lock(); | 
| 246 |         if (exposedWindow) { | 
| 247 |             QQuickWindowPrivate::get(c: exposedWindow)->fireAboutToStop(); | 
| 248 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Obscure - window removed" ); | 
| 249 |             exposedWindow = nullptr; | 
| 250 |             delete backingStore; | 
| 251 |             backingStore = nullptr; | 
| 252 |         } | 
| 253 |         waitCondition.wakeOne(); | 
| 254 |         mutex.unlock(); | 
| 255 |         return true; | 
| 256 |  | 
| 257 |     case WM_RequestSync: { | 
| 258 |         QSGSoftwareSyncEvent *wme = static_cast<QSGSoftwareSyncEvent *>(e); | 
| 259 |         if (sleeping) | 
| 260 |             stopEventProcessing = true; | 
| 261 |         exposedWindow = wme->window; | 
| 262 |         if (backingStore == nullptr) | 
| 263 |             backingStore = new QBackingStore(exposedWindow); | 
| 264 |         if (backingStore->size() != exposedWindow->size()) | 
| 265 |             backingStore->resize(size: exposedWindow->size()); | 
| 266 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "RT - WM_RequestSync"  << exposedWindow; | 
| 267 |         pendingUpdate |= SyncRequest; | 
| 268 |         if (wme->syncInExpose) { | 
| 269 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_RequestSync - triggered from expose" ); | 
| 270 |             pendingUpdate |= ExposeRequest; | 
| 271 |         } | 
| 272 |         if (wme->forceRenderPass) { | 
| 273 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_RequestSync - repaint regardless" ); | 
| 274 |             pendingUpdate |= RepaintRequest; | 
| 275 |         } | 
| 276 |         return true; | 
| 277 |     } | 
| 278 |  | 
| 279 |     case WM_TryRelease: { | 
| 280 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease" ); | 
| 281 |         mutex.lock(); | 
| 282 |         renderLoop->lockedForSync = true; | 
| 283 |         QSGSoftwareTryReleaseEvent *wme = static_cast<QSGSoftwareTryReleaseEvent *>(e); | 
| 284 |         // Only when no windows are exposed anymore or we are shutting down. | 
| 285 |         if (!exposedWindow || wme->destroying) { | 
| 286 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease - invalidating rc" ); | 
| 287 |             if (wme->window) { | 
| 288 |                 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: wme->window); | 
| 289 |                 if (wme->destroying) { | 
| 290 |                     // Bye bye nodes... | 
| 291 |                     wd->cleanupNodesOnShutdown(); | 
| 292 |                 } | 
| 293 |                 rc->invalidate(); | 
| 294 |                 QCoreApplication::processEvents(); | 
| 295 |                 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); | 
| 296 |                 if (wme->destroying) | 
| 297 |                     wd->animationController.reset(); | 
| 298 |             } | 
| 299 |             if (wme->destroying) | 
| 300 |                 active = false; | 
| 301 |             if (sleeping) | 
| 302 |                 stopEventProcessing = true; | 
| 303 |         } else { | 
| 304 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease - not releasing because window is still active" ); | 
| 305 |         } | 
| 306 |         waitCondition.wakeOne(); | 
| 307 |         renderLoop->lockedForSync = false; | 
| 308 |         mutex.unlock(); | 
| 309 |         return true; | 
| 310 |     } | 
| 311 |  | 
| 312 |     case WM_Grab: { | 
| 313 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Grab" ); | 
| 314 |         QSGSoftwareGrabEvent *wme = static_cast<QSGSoftwareGrabEvent *>(e); | 
| 315 |         Q_ASSERT(wme->window); | 
| 316 |         Q_ASSERT(wme->window == exposedWindow || !exposedWindow); | 
| 317 |         mutex.lock(); | 
| 318 |         if (wme->window) { | 
| 319 |             // Grabbing is generally done by rendering a frame and reading the | 
| 320 |             // color buffer contents back, without presenting, and then | 
| 321 |             // creating a QImage from the returned data. It is terribly | 
| 322 |             // inefficient since it involves a full blocking wait for the GPU. | 
| 323 |             // However, our hands are tied by the existing, synchronous APIs of | 
| 324 |             // QQuickWindow and such. | 
| 325 |             QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: wme->window); | 
| 326 |             auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer); | 
| 327 |             if (softwareRenderer) | 
| 328 |                 softwareRenderer->setBackingStore(backingStore); | 
| 329 |             rc->initialize(params: nullptr); | 
| 330 |             wd->syncSceneGraph(); | 
| 331 |             rc->endSync(); | 
| 332 |             wd->renderSceneGraph(size: wme->window->size()); | 
| 333 |             *wme->image = backingStore->handle()->toImage(); | 
| 334 |         } | 
| 335 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Grab - waking gui to handle result" ); | 
| 336 |         waitCondition.wakeOne(); | 
| 337 |         mutex.unlock(); | 
| 338 |         return true; | 
| 339 |     } | 
| 340 |  | 
| 341 |     case WM_PostJob: { | 
| 342 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_PostJob" ); | 
| 343 |         QSGSoftwareJobEvent *wme = static_cast<QSGSoftwareJobEvent *>(e); | 
| 344 |         Q_ASSERT(wme->window == exposedWindow); | 
| 345 |         if (exposedWindow) { | 
| 346 |             wme->job->run(); | 
| 347 |             delete wme->job; | 
| 348 |             wme->job = nullptr; | 
| 349 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_PostJob - job done" ); | 
| 350 |         } | 
| 351 |         return true; | 
| 352 |     } | 
| 353 |  | 
| 354 |     default: | 
| 355 |         break; | 
| 356 |     } | 
| 357 |  | 
| 358 |     return QThread::event(event: e); | 
| 359 | } | 
| 360 |  | 
| 361 | void QSGSoftwareRenderThread::postEvent(QEvent *e) | 
| 362 | { | 
| 363 |     eventQueue.addEvent(e); | 
| 364 | } | 
| 365 |  | 
| 366 | void QSGSoftwareRenderThread::processEvents() | 
| 367 | { | 
| 368 |     while (eventQueue.hasMoreEvents()) { | 
| 369 |         QEvent *e = eventQueue.takeEvent(wait: false); | 
| 370 |         event(e); | 
| 371 |         delete e; | 
| 372 |     } | 
| 373 | } | 
| 374 |  | 
| 375 | void QSGSoftwareRenderThread::processEventsAndWaitForMore() | 
| 376 | { | 
| 377 |     stopEventProcessing = false; | 
| 378 |     while (!stopEventProcessing) { | 
| 379 |         QEvent *e = eventQueue.takeEvent(wait: true); | 
| 380 |         event(e); | 
| 381 |         delete e; | 
| 382 |     } | 
| 383 | } | 
| 384 |  | 
| 385 | void QSGSoftwareRenderThread::run() | 
| 386 | { | 
| 387 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - run()" ); | 
| 388 |  | 
| 389 |     rtAnim = rc->sceneGraphContext()->createAnimationDriver(parent: nullptr); | 
| 390 |     rtAnim->install(); | 
| 391 |  | 
| 392 |     if (QQmlDebugConnector::service<QQmlProfilerService>()) | 
| 393 |         QQuickProfiler::registerAnimationCallback(); | 
| 394 |  | 
| 395 |     renderThrottleTimer.start(); | 
| 396 |  | 
| 397 |     while (active) { | 
| 398 |         if (exposedWindow) | 
| 399 |             syncAndRender(); | 
| 400 |  | 
| 401 |         processEvents(); | 
| 402 |         QCoreApplication::processEvents(); | 
| 403 |  | 
| 404 |         if (pendingUpdate == 0 || !exposedWindow) { | 
| 405 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - done drawing, sleep" ); | 
| 406 |             sleeping = true; | 
| 407 |             processEventsAndWaitForMore(); | 
| 408 |             sleeping = false; | 
| 409 |         } | 
| 410 |     } | 
| 411 |  | 
| 412 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - run() exiting" ); | 
| 413 |  | 
| 414 |     delete rtAnim; | 
| 415 |     rtAnim = nullptr; | 
| 416 |  | 
| 417 |     rc->moveToThread(thread: renderLoop->thread()); | 
| 418 |     moveToThread(thread: renderLoop->thread()); | 
| 419 | } | 
| 420 |  | 
| 421 | void QSGSoftwareRenderThread::sync(bool inExpose) | 
| 422 | { | 
| 423 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - sync" ); | 
| 424 |  | 
| 425 |     mutex.lock(); | 
| 426 |     Q_ASSERT_X(renderLoop->lockedForSync, "QSGD3D12RenderThread::sync()" , "sync triggered with gui not locked" ); | 
| 427 |  | 
| 428 |     if (exposedWindow) { | 
| 429 |         QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: exposedWindow); | 
| 430 |         bool hadRenderer = wd->renderer != nullptr; | 
| 431 |         // If the scene graph was touched since the last sync() make sure it sends the | 
| 432 |         // changed signal. | 
| 433 |         if (wd->renderer) | 
| 434 |             wd->renderer->clearChangedFlag(); | 
| 435 |  | 
| 436 |         rc->initialize(params: nullptr); | 
| 437 |         wd->syncSceneGraph(); | 
| 438 |         rc->endSync(); | 
| 439 |  | 
| 440 |         if (!hadRenderer && wd->renderer) { | 
| 441 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - created renderer" ); | 
| 442 |             syncResultedInChanges = true; | 
| 443 |             connect(sender: wd->renderer, signal: &QSGRenderer::sceneGraphChanged, receiver: this, | 
| 444 |                     slot: &QSGSoftwareRenderThread::onSceneGraphChanged, type: Qt::DirectConnection); | 
| 445 |         } | 
| 446 |  | 
| 447 |         // Process deferred deletes now, directly after the sync as deleteLater | 
| 448 |         // on the GUI must now also have resulted in SG changes and the delete | 
| 449 |         // is a safe operation. | 
| 450 |         QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); | 
| 451 |     } | 
| 452 |  | 
| 453 |     if (!inExpose) { | 
| 454 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - sync complete, waking gui" ); | 
| 455 |         waitCondition.wakeOne(); | 
| 456 |         mutex.unlock(); | 
| 457 |     } | 
| 458 | } | 
| 459 |  | 
| 460 | void QSGSoftwareRenderThread::syncAndRender() | 
| 461 | { | 
| 462 |     Q_TRACE_SCOPE(QSG_syncAndRender); | 
| 463 |     Q_TRACE(QSG_sync_entry); | 
| 464 |     Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame); | 
| 465 |  | 
| 466 |     QElapsedTimer waitTimer; | 
| 467 |     waitTimer.start(); | 
| 468 |  | 
| 469 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - syncAndRender()" ); | 
| 470 |  | 
| 471 |     syncResultedInChanges = false; | 
| 472 |     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: exposedWindow); | 
| 473 |  | 
| 474 |     const bool repaintRequested = (pendingUpdate & RepaintRequest) || wd->customRenderStage; | 
| 475 |     const bool syncRequested = pendingUpdate & SyncRequest; | 
| 476 |     const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest; | 
| 477 |     pendingUpdate = 0; | 
| 478 |  | 
| 479 |     if (syncRequested) | 
| 480 |         sync(inExpose: exposeRequested); | 
| 481 |  | 
| 482 |     Q_TRACE(QSG_sync_exit); | 
| 483 |     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame, | 
| 484 |                               QQuickProfiler::SceneGraphRenderLoopSync); | 
| 485 |  | 
| 486 |     if (!syncResultedInChanges && !repaintRequested) { | 
| 487 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - no changes, render aborted" ); | 
| 488 |         int waitTime = vsyncDelta - (int) waitTimer.elapsed(); | 
| 489 |         if (waitTime > 0) | 
| 490 |             msleep(waitTime); | 
| 491 |         return; | 
| 492 |     } | 
| 493 |  | 
| 494 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - rendering started" ); | 
| 495 |     Q_TRACE(QSG_render_entry); | 
| 496 |  | 
| 497 |     if (rtAnim->isRunning()) { | 
| 498 |         wd->animationController->lock(); | 
| 499 |         rtAnim->advance(); | 
| 500 |         wd->animationController->unlock(); | 
| 501 |     } | 
| 502 |  | 
| 503 |     bool canRender = wd->renderer != nullptr; | 
| 504 |  | 
| 505 |     if (canRender) { | 
| 506 |         auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer); | 
| 507 |         if (softwareRenderer) | 
| 508 |             softwareRenderer->setBackingStore(backingStore); | 
| 509 |         wd->renderSceneGraph(size: exposedWindow->size()); | 
| 510 |  | 
| 511 |         Q_TRACE(QSG_render_exit); | 
| 512 |         Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame, | 
| 513 |                                   QQuickProfiler::SceneGraphRenderLoopRender); | 
| 514 |         Q_TRACE(QSG_swap_entry); | 
| 515 |  | 
| 516 |         if (softwareRenderer && (!wd->customRenderStage || !wd->customRenderStage->swap())) | 
| 517 |             backingStore->flush(region: softwareRenderer->flushRegion()); | 
| 518 |  | 
| 519 |         // Since there is no V-Sync with QBackingStore, throttle rendering the refresh | 
| 520 |         // rate of the current screen the window is on. | 
| 521 |         int blockTime = vsyncDelta - (int) renderThrottleTimer.elapsed(); | 
| 522 |         if (blockTime > 0) { | 
| 523 |             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - blocking for %d ms" , blockTime); | 
| 524 |             msleep(blockTime); | 
| 525 |         } | 
| 526 |         renderThrottleTimer.restart(); | 
| 527 |  | 
| 528 |         wd->fireFrameSwapped(); | 
| 529 |     } else { | 
| 530 |         Q_TRACE(QSG_render_exit); | 
| 531 |         Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame, | 
| 532 |                                 QQuickProfiler::SceneGraphRenderLoopSync, 1); | 
| 533 |         Q_TRACE(QSG_swap_entry); | 
| 534 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - window not ready, skipping render" ); | 
| 535 |     } | 
| 536 |  | 
| 537 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - rendering done" ); | 
| 538 |  | 
| 539 |     if (exposeRequested) { | 
| 540 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - wake gui after initial expose" ); | 
| 541 |         waitCondition.wakeOne(); | 
| 542 |         mutex.unlock(); | 
| 543 |     } | 
| 544 |  | 
| 545 |     Q_TRACE(QSG_swap_exit); | 
| 546 |     Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame, | 
| 547 |                            QQuickProfiler::SceneGraphRenderLoopSwap); | 
| 548 | } | 
| 549 |  | 
| 550 | template<class T> T *windowFor(const QVector<T> &list, QQuickWindow *window) | 
| 551 | { | 
| 552 |     for (const T &t : list) { | 
| 553 |         if (t.window == window) | 
| 554 |             return const_cast<T *>(&t); | 
| 555 |     } | 
| 556 |     return nullptr; | 
| 557 | } | 
| 558 |  | 
| 559 |  | 
| 560 | QSGSoftwareThreadedRenderLoop::QSGSoftwareThreadedRenderLoop() | 
| 561 | { | 
| 562 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop constructor" ); | 
| 563 |     m_sg = new QSGSoftwareContext; | 
| 564 |     m_anim = m_sg->createAnimationDriver(parent: this); | 
| 565 |     connect(sender: m_anim, signal: &QAnimationDriver::started, receiver: this, slot: &QSGSoftwareThreadedRenderLoop::onAnimationStarted); | 
| 566 |     connect(sender: m_anim, signal: &QAnimationDriver::stopped, receiver: this, slot: &QSGSoftwareThreadedRenderLoop::onAnimationStopped); | 
| 567 |     m_anim->install(); | 
| 568 | } | 
| 569 |  | 
| 570 | QSGSoftwareThreadedRenderLoop::~QSGSoftwareThreadedRenderLoop() | 
| 571 | { | 
| 572 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop destructor" ); | 
| 573 |     delete m_sg; | 
| 574 | } | 
| 575 |  | 
| 576 | void QSGSoftwareThreadedRenderLoop::show(QQuickWindow *window) | 
| 577 | { | 
| 578 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "show"  << window; | 
| 579 | } | 
| 580 |  | 
| 581 | void QSGSoftwareThreadedRenderLoop::hide(QQuickWindow *window) | 
| 582 | { | 
| 583 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "hide"  << window; | 
| 584 |  | 
| 585 |     if (window->isExposed()) | 
| 586 |         handleObscurity(w: windowFor(list: m_windows, window)); | 
| 587 |  | 
| 588 |     releaseResources(window); | 
| 589 | } | 
| 590 |  | 
| 591 | void QSGSoftwareThreadedRenderLoop::resize(QQuickWindow *window) | 
| 592 | { | 
| 593 |     if (!window->isExposed() || window->size().isEmpty()) | 
| 594 |         return; | 
| 595 |  | 
| 596 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "resize"  << window << window->size(); | 
| 597 | } | 
| 598 |  | 
| 599 | void QSGSoftwareThreadedRenderLoop::windowDestroyed(QQuickWindow *window) | 
| 600 | { | 
| 601 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "window destroyed"  << window; | 
| 602 |  | 
| 603 |     WindowData *w = windowFor(list: m_windows, window); | 
| 604 |     if (!w) | 
| 605 |         return; | 
| 606 |  | 
| 607 |     handleObscurity(w); | 
| 608 |     handleResourceRelease(w, destroying: true); | 
| 609 |  | 
| 610 |     QSGSoftwareRenderThread *thread = w->thread; | 
| 611 |     while (thread->isRunning()) | 
| 612 |         QThread::yieldCurrentThread(); | 
| 613 |  | 
| 614 |     Q_ASSERT(thread->thread() == QThread::currentThread()); | 
| 615 |     delete thread; | 
| 616 |  | 
| 617 |     for (int i = 0; i < m_windows.size(); ++i) { | 
| 618 |         if (m_windows.at(i).window == window) { | 
| 619 |             m_windows.removeAt(i); | 
| 620 |             break; | 
| 621 |         } | 
| 622 |     } | 
| 623 |  | 
| 624 |     // Now that we altered the window list, we may need to stop the animation | 
| 625 |     // timer even if we didn't via handleObscurity. This covers the case where | 
| 626 |     // we destroy a visible & exposed QQuickWindow. | 
| 627 |     startOrStopAnimationTimer(); | 
| 628 | } | 
| 629 |  | 
| 630 | void QSGSoftwareThreadedRenderLoop::exposureChanged(QQuickWindow *window) | 
| 631 | { | 
| 632 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "exposure changed"  << window; | 
| 633 |  | 
| 634 |     if (window->isExposed()) { | 
| 635 |         handleExposure(window); | 
| 636 |     } else { | 
| 637 |         WindowData *w = windowFor(list: m_windows, window); | 
| 638 |         if (w) | 
| 639 |             handleObscurity(w); | 
| 640 |     } | 
| 641 | } | 
| 642 |  | 
| 643 | QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window) | 
| 644 | { | 
| 645 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "grab"  << window; | 
| 646 |  | 
| 647 |     WindowData *w = windowFor(list: m_windows, window); | 
| 648 |     // Have to support invisible (but created()'ed) windows as well. | 
| 649 |     // Unlike with GL, leaving that case for QQuickWindow to handle is not feasible. | 
| 650 |     const bool tempExpose = !w; | 
| 651 |     if (tempExpose) { | 
| 652 |         handleExposure(window); | 
| 653 |         w = windowFor(list: m_windows, window); | 
| 654 |         Q_ASSERT(w); | 
| 655 |     } | 
| 656 |  | 
| 657 |     if (!w->thread->isRunning()) | 
| 658 |         return QImage(); | 
| 659 |  | 
| 660 |     if (!window->handle()) | 
| 661 |         window->create(); | 
| 662 |  | 
| 663 |     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window); | 
| 664 |     wd->polishItems(); | 
| 665 |  | 
| 666 |     QImage result; | 
| 667 |     w->thread->mutex.lock(); | 
| 668 |     lockedForSync = true; | 
| 669 |     w->thread->postEvent(e: new QSGSoftwareGrabEvent(window, &result)); | 
| 670 |     w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex); | 
| 671 |     lockedForSync = false; | 
| 672 |     w->thread->mutex.unlock(); | 
| 673 |  | 
| 674 |     result.setDevicePixelRatio(window->effectiveDevicePixelRatio()); | 
| 675 |  | 
| 676 |     if (tempExpose) | 
| 677 |         handleObscurity(w); | 
| 678 |  | 
| 679 |     return result; | 
| 680 | } | 
| 681 |  | 
| 682 | void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window) | 
| 683 | { | 
| 684 |     WindowData *w = windowFor(list: m_windows, window); | 
| 685 |     if (!w) | 
| 686 |         return; | 
| 687 |  | 
| 688 |     if (w->thread == QThread::currentThread()) { | 
| 689 |         w->thread->requestRepaint(); | 
| 690 |         return; | 
| 691 |     } | 
| 692 |  | 
| 693 |     // We set forceRenderPass because we want to make sure the QQuickWindow | 
| 694 |     // actually does a full render pass after the next sync. | 
| 695 |     w->forceRenderPass = true; | 
| 696 |     scheduleUpdate(w); | 
| 697 | } | 
| 698 |  | 
| 699 | void QSGSoftwareThreadedRenderLoop::maybeUpdate(QQuickWindow *window) | 
| 700 | { | 
| 701 |     WindowData *w = windowFor(list: m_windows, window); | 
| 702 |     if (w) | 
| 703 |         scheduleUpdate(w); | 
| 704 | } | 
| 705 |  | 
| 706 | void QSGSoftwareThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window) | 
| 707 | { | 
| 708 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleUpdateRequest"  << window; | 
| 709 |  | 
| 710 |     WindowData *w = windowFor(list: m_windows, window); | 
| 711 |     if (w) | 
| 712 |         polishAndSync(w, inExpose: false); | 
| 713 | } | 
| 714 |  | 
| 715 | QAnimationDriver *QSGSoftwareThreadedRenderLoop::animationDriver() const | 
| 716 | { | 
| 717 |     return m_anim; | 
| 718 | } | 
| 719 |  | 
| 720 | QSGContext *QSGSoftwareThreadedRenderLoop::sceneGraphContext() const | 
| 721 | { | 
| 722 |     return m_sg; | 
| 723 | } | 
| 724 |  | 
| 725 | QSGRenderContext *QSGSoftwareThreadedRenderLoop::createRenderContext(QSGContext *) const | 
| 726 | { | 
| 727 |     return m_sg->createRenderContext(); | 
| 728 | } | 
| 729 |  | 
| 730 | void QSGSoftwareThreadedRenderLoop::releaseResources(QQuickWindow *window) | 
| 731 | { | 
| 732 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "releaseResources"  << window; | 
| 733 |  | 
| 734 |     WindowData *w = windowFor(list: m_windows, window); | 
| 735 |     if (w) | 
| 736 |         handleResourceRelease(w, destroying: false); | 
| 737 | } | 
| 738 |  | 
| 739 | void QSGSoftwareThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job) | 
| 740 | { | 
| 741 |     WindowData *w = windowFor(list: m_windows, window); | 
| 742 |     if (w && w->thread && w->thread->exposedWindow) | 
| 743 |         w->thread->postEvent(e: new QSGSoftwareJobEvent(window, job)); | 
| 744 |     else | 
| 745 |         delete job; | 
| 746 | } | 
| 747 |  | 
| 748 | QSurface::SurfaceType QSGSoftwareThreadedRenderLoop::windowSurfaceType() const | 
| 749 | { | 
| 750 |     return QSurface::RasterSurface; | 
| 751 | } | 
| 752 |  | 
| 753 | bool QSGSoftwareThreadedRenderLoop::interleaveIncubation() const | 
| 754 | { | 
| 755 |     bool somethingVisible = false; | 
| 756 |     for (const WindowData &w : m_windows) { | 
| 757 |         if (w.window->isVisible() && w.window->isExposed()) { | 
| 758 |             somethingVisible = true; | 
| 759 |             break; | 
| 760 |         } | 
| 761 |     } | 
| 762 |     return somethingVisible && m_anim->isRunning(); | 
| 763 | } | 
| 764 |  | 
| 765 | int QSGSoftwareThreadedRenderLoop::flags() const | 
| 766 | { | 
| 767 |     return SupportsGrabWithoutExpose; | 
| 768 | } | 
| 769 |  | 
| 770 | bool QSGSoftwareThreadedRenderLoop::event(QEvent *e) | 
| 771 | { | 
| 772 |     if (e->type() == QEvent::Timer) { | 
| 773 |         QTimerEvent *te = static_cast<QTimerEvent *>(e); | 
| 774 |         if (te->timerId() == animationTimer) { | 
| 775 |             m_anim->advance(); | 
| 776 |             emit timeToIncubate(); | 
| 777 |             return true; | 
| 778 |         } | 
| 779 |     } | 
| 780 |  | 
| 781 |     return QObject::event(event: e); | 
| 782 | } | 
| 783 |  | 
| 784 | void QSGSoftwareThreadedRenderLoop::onAnimationStarted() | 
| 785 | { | 
| 786 |     startOrStopAnimationTimer(); | 
| 787 |  | 
| 788 |     for (const WindowData &w : qAsConst(t&: m_windows)) | 
| 789 |         w.window->requestUpdate(); | 
| 790 | } | 
| 791 |  | 
| 792 | void QSGSoftwareThreadedRenderLoop::onAnimationStopped() | 
| 793 | { | 
| 794 |     startOrStopAnimationTimer(); | 
| 795 | } | 
| 796 |  | 
| 797 | void QSGSoftwareThreadedRenderLoop::startOrStopAnimationTimer() | 
| 798 | { | 
| 799 |     int exposedWindowCount = 0; | 
| 800 |     const WindowData *exposed = nullptr; | 
| 801 |  | 
| 802 |     for (int i = 0; i < m_windows.size(); ++i) { | 
| 803 |         const WindowData &w(m_windows[i]); | 
| 804 |         if (w.window->isVisible() && w.window->isExposed()) { | 
| 805 |             ++exposedWindowCount; | 
| 806 |             exposed = &w; | 
| 807 |         } | 
| 808 |     } | 
| 809 |  | 
| 810 |     if (animationTimer && (exposedWindowCount == 1 || !m_anim->isRunning())) { | 
| 811 |         killTimer(id: animationTimer); | 
| 812 |         animationTimer = 0; | 
| 813 |         // If animations are running, make sure we keep on animating | 
| 814 |         if (m_anim->isRunning()) | 
| 815 |             exposed->window->requestUpdate(); | 
| 816 |     } else if (!animationTimer && exposedWindowCount != 1 && m_anim->isRunning()) { | 
| 817 |         animationTimer = startTimer(interval: qsgrl_animation_interval()); | 
| 818 |     } | 
| 819 | } | 
| 820 |  | 
| 821 | void QSGSoftwareThreadedRenderLoop::handleExposure(QQuickWindow *window) | 
| 822 | { | 
| 823 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleExposure"  << window; | 
| 824 |  | 
| 825 |     WindowData *w = windowFor(list: m_windows, window); | 
| 826 |     if (!w) { | 
| 827 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "adding window to list" ); | 
| 828 |         WindowData win; | 
| 829 |         win.window = window; | 
| 830 |         QSGRenderContext *rc = QQuickWindowPrivate::get(c: window)->context; // will transfer ownership | 
| 831 |         win.thread = new QSGSoftwareRenderThread(this, rc); | 
| 832 |         win.updateDuringSync = false; | 
| 833 |         win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt | 
| 834 |         m_windows.append(t: win); | 
| 835 |         w = &m_windows.last(); | 
| 836 |     } | 
| 837 |  | 
| 838 |     // set this early as we'll be rendering shortly anyway and this avoids | 
| 839 |     // special casing exposure in polishAndSync. | 
| 840 |     w->thread->exposedWindow = window; | 
| 841 |  | 
| 842 |     if (w->window->size().isEmpty() | 
| 843 |         || (w->window->isTopLevel() && !w->window->geometry().intersects(r: w->window->screen()->availableGeometry()))) { | 
| 844 | #ifndef QT_NO_DEBUG | 
| 845 |         qWarning().noquote().nospace() << "QSGSotwareThreadedRenderLoop: expose event received for window "  | 
| 846 |             << w->window << " with invalid geometry: "  << w->window->geometry() | 
| 847 |             << " on "  << w->window->screen(); | 
| 848 | #endif | 
| 849 |     } | 
| 850 |  | 
| 851 |     if (!w->window->handle()) | 
| 852 |         w->window->create(); | 
| 853 |  | 
| 854 |     // Start render thread if it is not running | 
| 855 |     if (!w->thread->isRunning()) { | 
| 856 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "starting render thread" ); | 
| 857 |         // Push a few things to the render thread. | 
| 858 |         QQuickAnimatorController *controller | 
| 859 |                 = QQuickWindowPrivate::get(c: w->window)->animationController.get(); | 
| 860 |         if (controller->thread() != w->thread) | 
| 861 |             controller->moveToThread(thread: w->thread); | 
| 862 |         if (w->thread->thread() == QThread::currentThread()) { | 
| 863 |             w->thread->rc->moveToThread(thread: w->thread); | 
| 864 |             w->thread->moveToThread(thread: w->thread); | 
| 865 |         } | 
| 866 |  | 
| 867 |         w->thread->active = true; | 
| 868 |         w->thread->start(); | 
| 869 |  | 
| 870 |         if (!w->thread->isRunning()) | 
| 871 |             qFatal(msg: "Render thread failed to start, aborting application." ); | 
| 872 |     } | 
| 873 |  | 
| 874 |     polishAndSync(w, inExpose: true); | 
| 875 |  | 
| 876 |     startOrStopAnimationTimer(); | 
| 877 | } | 
| 878 |  | 
| 879 | void QSGSoftwareThreadedRenderLoop::handleObscurity(QSGSoftwareThreadedRenderLoop::WindowData *w) | 
| 880 | { | 
| 881 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleObscurity"  << w->window; | 
| 882 |  | 
| 883 |     if (w->thread->isRunning()) { | 
| 884 |         w->thread->mutex.lock(); | 
| 885 |         w->thread->postEvent(e: new QSGSoftwareWindowEvent(w->window, WM_Obscure)); | 
| 886 |         w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex); | 
| 887 |         w->thread->mutex.unlock(); | 
| 888 |     } | 
| 889 |  | 
| 890 |     startOrStopAnimationTimer(); | 
| 891 | } | 
| 892 |  | 
| 893 | void QSGSoftwareThreadedRenderLoop::scheduleUpdate(QSGSoftwareThreadedRenderLoop::WindowData *w) | 
| 894 | { | 
| 895 |     if (!QCoreApplication::instance()) | 
| 896 |         return; | 
| 897 |  | 
| 898 |     if (!w || !w->thread->isRunning()) | 
| 899 |         return; | 
| 900 |  | 
| 901 |     QThread *current = QThread::currentThread(); | 
| 902 |     if (current != QCoreApplication::instance()->thread() && (current != w->thread || !lockedForSync)) { | 
| 903 |         qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()" ; | 
| 904 |         return; | 
| 905 |     } | 
| 906 |  | 
| 907 |     if (current == w->thread) { | 
| 908 |         w->updateDuringSync = true; | 
| 909 |         return; | 
| 910 |     } | 
| 911 |  | 
| 912 |     w->window->requestUpdate(); | 
| 913 | } | 
| 914 |  | 
| 915 | void QSGSoftwareThreadedRenderLoop::handleResourceRelease(QSGSoftwareThreadedRenderLoop::WindowData *w, bool destroying) | 
| 916 | { | 
| 917 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleResourceRelease"  << (destroying ? "destroying"  : "hide/releaseResources" ) << w->window; | 
| 918 |  | 
| 919 |     w->thread->mutex.lock(); | 
| 920 |     if (w->thread->isRunning() && w->thread->active) { | 
| 921 |         QQuickWindow *window = w->window; | 
| 922 |  | 
| 923 |         // Note that window->handle() is typically null by this time because | 
| 924 |         // the platform window is already destroyed. This should not be a | 
| 925 |         // problem for the D3D cleanup. | 
| 926 |  | 
| 927 |         w->thread->postEvent(e: new QSGSoftwareTryReleaseEvent(window, destroying)); | 
| 928 |         w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex); | 
| 929 |  | 
| 930 |         // Avoid a shutdown race condition. | 
| 931 |         // If SG is invalidated and 'active' becomes false, the thread's run() | 
| 932 |         // method will exit. handleExposure() relies on QThread::isRunning() (because it | 
| 933 |         // potentially needs to start the thread again) and our mutex cannot be used to | 
| 934 |         // track the thread stopping, so we wait a few nanoseconds extra so the thread | 
| 935 |         // can exit properly. | 
| 936 |         if (!w->thread->active) | 
| 937 |             w->thread->wait(); | 
| 938 |     } | 
| 939 |     w->thread->mutex.unlock(); | 
| 940 | } | 
| 941 |  | 
| 942 | void QSGSoftwareThreadedRenderLoop::polishAndSync(QSGSoftwareThreadedRenderLoop::WindowData *w, bool inExpose) | 
| 943 | { | 
| 944 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "polishAndSync"  << (inExpose ? "(in expose)"  : "(normal)" ) << w->window; | 
| 945 |  | 
| 946 |     QQuickWindow *window = w->window; | 
| 947 |     if (!w->thread || !w->thread->exposedWindow) { | 
| 948 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - not exposed, abort" ); | 
| 949 |         return; | 
| 950 |     } | 
| 951 |  | 
| 952 |     // Flush pending touch events. | 
| 953 |     QQuickWindowPrivate::get(c: window)->flushFrameSynchronousEvents(); | 
| 954 |     // The delivery of the event might have caused the window to stop rendering | 
| 955 |     w = windowFor(list: m_windows, window); | 
| 956 |     if (!w || !w->thread || !w->thread->exposedWindow) { | 
| 957 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - removed after touch event flushing, abort" ); | 
| 958 |         return; | 
| 959 |     } | 
| 960 |  | 
| 961 |     Q_TRACE_SCOPE(QSG_polishAndSync); | 
| 962 |  | 
| 963 |     Q_TRACE(QSG_polishItems_entry); | 
| 964 |     Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync); | 
| 965 |  | 
| 966 |     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window); | 
| 967 |     wd->polishItems(); | 
| 968 |  | 
| 969 |     Q_TRACE(QSG_polishItems_exit); | 
| 970 |     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync, | 
| 971 |                               QQuickProfiler::SceneGraphPolishAndSyncPolish); | 
| 972 |     Q_TRACE(QSG_sync_entry); | 
| 973 |  | 
| 974 |     w->updateDuringSync = false; | 
| 975 |  | 
| 976 |     emit window->afterAnimating(); | 
| 977 |  | 
| 978 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - lock for sync" ); | 
| 979 |     w->thread->mutex.lock(); | 
| 980 |     lockedForSync = true; | 
| 981 |     w->thread->postEvent(e: new QSGSoftwareSyncEvent(window, inExpose, w->forceRenderPass)); | 
| 982 |     w->forceRenderPass = false; | 
| 983 |  | 
| 984 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - wait for sync" ); | 
| 985 |  | 
| 986 |     Q_TRACE(QSG_sync_exit); | 
| 987 |     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync, | 
| 988 |                               QQuickProfiler::SceneGraphPolishAndSyncWait); | 
| 989 |     Q_TRACE(QSG_wait_entry); | 
| 990 |     w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex); | 
| 991 |     lockedForSync = false; | 
| 992 |     w->thread->mutex.unlock(); | 
| 993 |     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - unlock after sync" ); | 
| 994 |  | 
| 995 |     Q_TRACE(QSG_wait_exit); | 
| 996 |     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync, | 
| 997 |                               QQuickProfiler::SceneGraphPolishAndSyncSync); | 
| 998 |     Q_TRACE(QSG_animations_entry); | 
| 999 |  | 
| 1000 |     if (!animationTimer && m_anim->isRunning()) { | 
| 1001 |         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - advancing animations" ); | 
| 1002 |         m_anim->advance(); | 
| 1003 |         // We need to trigger another sync to keep animations running... | 
| 1004 |         w->window->requestUpdate(); | 
| 1005 |         emit timeToIncubate(); | 
| 1006 |     } else if (w->updateDuringSync) { | 
| 1007 |         w->window->requestUpdate(); | 
| 1008 |     } | 
| 1009 |  | 
| 1010 |     Q_TRACE(QSG_animations_exit); | 
| 1011 |     Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync, | 
| 1012 |                            QQuickProfiler::SceneGraphPolishAndSyncAnimations); | 
| 1013 | } | 
| 1014 |  | 
| 1015 | #include "qsgsoftwarethreadedrenderloop.moc" | 
| 1016 | #include "moc_qsgsoftwarethreadedrenderloop_p.cpp" | 
| 1017 |  | 
| 1018 | QT_END_NAMESPACE | 
| 1019 |  |