diff --git a/src/quick/items/items.pri b/src/quick/items/items.pri index 308706f0b91a0ec65a4f7f261f6b7b3f6f704994..a224db31d6a3ae877d2d8e2625c89c6018af8cf0 100644 --- a/src/quick/items/items.pri +++ b/src/quick/items/items.pri @@ -67,7 +67,8 @@ HEADERS += \ $$PWD/qquickmultipointtoucharea_p.h \ $$PWD/qquickitemview_p.h \ $$PWD/qquickitemview_p_p.h \ - $$PWD/qquickwindowmodule_p.h + $$PWD/qquickwindowmodule_p.h \ + $$PWD/qquickwindowmanager_p.h SOURCES += \ $$PWD/qquickevents.cpp \ @@ -114,7 +115,8 @@ SOURCES += \ $$PWD/qquickdroparea.cpp \ $$PWD/qquickmultipointtoucharea.cpp \ $$PWD/qquickitemview.cpp \ - $$PWD/qquickwindowmodule.cpp + $$PWD/qquickwindowmodule.cpp \ + $$PWD/qquickwindowmanager.cpp SOURCES += \ $$PWD/qquickshadereffect.cpp \ @@ -129,3 +131,5 @@ HEADERS += \ $$PWD/qquickshadereffectsource_p.h \ include(context2d/context2d.pri) + + diff --git a/src/quick/items/qquickcanvas.cpp b/src/quick/items/qquickcanvas.cpp index 9c6ab08fc7b0840d04b52c514943e437d9aafb40..5bf27dea5b74ea3a28a1a139b73fff3be4fb37fd 100644 --- a/src/quick/items/qquickcanvas.cpp +++ b/src/quick/items/qquickcanvas.cpp @@ -45,10 +45,12 @@ #include "qquickitem.h" #include "qquickitem_p.h" -#include <QtQuick/private/qsgrenderer_p.h> -#include <QtQuick/private/qsgtexture_p.h> +#include <private/qsgrenderer_p.h> +#include <private/qsgtexture_p.h> #include <private/qsgflashnode_p.h> -#include <QtQuick/qsgengine.h> +#include <qsgengine.h> + +#include <private/qquickwindowmanager_p.h> #include <private/qguiapplication_p.h> #include <QtGui/QInputPanel> @@ -66,19 +68,6 @@ QT_BEGIN_NAMESPACE -#define QQUICK_CANVAS_TIMING -#ifdef QQUICK_CANVAS_TIMING -static bool qquick_canvas_timing = !qgetenv("QML_CANVAS_TIMING").isEmpty(); -static QTime threadTimer; -static int syncTime; -static int renderTime; -static int swapTime; -#endif - -DEFINE_BOOL_CONFIG_OPTION(qmlFixedAnimationStep, QML_FIXED_ANIMATION_STEP) -DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP) - -extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); void QQuickCanvasPrivate::updateFocusItemTransform() { @@ -100,7 +89,7 @@ protected: if (e->type() == QEvent::User) { Q_ASSERT(m_eventSent); - bool *amtp = m_canvas->thread->allowMainThreadProcessing(); + bool *amtp = m_canvas->windowManager->allowMainThreadProcessing(); while (incubatingObjectCount()) { if (amtp) incubateWhile(amtp); @@ -127,83 +116,6 @@ private: bool m_eventSent; }; -class QQuickCanvasPlainRenderLoop : public QObject, public QQuickCanvasRenderLoop -{ -public: - QQuickCanvasPlainRenderLoop() - : updatePending(false) - , animationRunning(false) - { - } - - virtual void paint() { - updatePending = false; - if (animationRunning && animationDriver()) - animationDriver()->advance(); - polishItems(); - syncSceneGraph(); - makeCurrent(); - glViewport(0, 0, size.width(), size.height()); - renderSceneGraph(size); - swapBuffers(); - - if (animationRunning) - maybeUpdate(); - } - - virtual QImage grab() { - return qt_gl_read_framebuffer(size, false, false); - } - - virtual void startRendering() { - if (!glContext()) { - createGLContext(); - makeCurrent(); - initializeSceneGraph(); - } else { - makeCurrent(); - } - maybeUpdate(); - } - - virtual void stopRendering() { - cleanupNodesOnShutdown(); - } - - virtual void maybeUpdate() { - if (!updatePending) { - QCoreApplication::postEvent(this, new QEvent(QEvent::User)); - updatePending = true; - } - } - - virtual void animationStarted() { - animationRunning = true; - maybeUpdate(); - } - - virtual void animationStopped() { - animationRunning = false; - } - - virtual bool isRunning() const { return glContext(); } // Event loop is always running... - virtual void resize(const QSize &s) { size = s; } - virtual void setWindowSize(const QSize &s) { size = s; } - - bool event(QEvent *e) { - if (e->type() == QEvent::User) { - paint(); - return true; - } - return QObject::event(e); - } - - QSize size; - - uint updatePending : 1; - uint animationRunning : 1; -}; - /* @@ -225,74 +137,11 @@ thus the first item that has focus will get it (assuming the scope doesn't alrea have a scope focused item), and the other items will have their focus cleared. */ -/* - Threaded Rendering - ================== - - The threaded rendering uses a number of different variables to track potential - states used to handle resizing, initial paint, grabbing and driving animations - while ALWAYS keeping the GL context in the rendering thread and keeping the - overhead of normal one-shot paints and vblank driven animations at a minimum. - - Resize, initial show and grab suffer slightly in this model as they are locked - to the rendering in the rendering thread, but this is a necessary evil for - the system to work. - - Variables that are used: - - Private::animationRunning: This is true while the animations are running, and only - written to inside locks. - - RenderThread::isGuiBlocked: This is used to indicate that the GUI thread owns the - lock. This variable is an integer to allow for recursive calls to lockInGui() - without using a recursive mutex. See isGuiBlockPending. - - RenderThread::isPaintComplete: This variable is cleared when rendering starts and - set once rendering is complete. It is monitored in the paintEvent(), - resizeEvent() and grab() functions to force them to wait for rendering to - complete. - - RenderThread::isGuiBlockPending: This variable is set in the render thread just - before the sync event is sent to the GUI thread. It is used to avoid deadlocks - in the case where render thread waits while waiting for GUI to pick up the sync - event and GUI thread gets a resizeEvent, the initial paintEvent or a grab. - When this happens, we use the - exhaustSyncEvent() function to do the sync right there and mark the coming - sync event to be discarded. There can only ever be one sync incoming. - - RenderThread::isRenderBlock: This variable is true when animations are not - running and the render thread has gone to sleep, waiting for more to do. - - RenderThread::isExternalUpdatePending: This variable is set to false when - a new render pass is started and to true in maybeUpdate(). It is an - indication to the render thread that another render pass needs to take - place, rather than the render thread going to sleep after completing its swap. - - RenderThread::doGrab: This variable is set by the grab() function and - tells the renderer to do a grab after rendering is complete and before - swapping happens. - - RenderThread::shouldExit: This variable is used to determine if the render - thread should do a nother pass. It is typically set as a result of show() - and unset as a result of hide() or during shutdown() - - RenderThread::hasExited: Used by the GUI thread to synchronize the shutdown - after shouldExit has been set to true. - */ // #define FOCUS_DEBUG // #define MOUSE_DEBUG // #define TOUCH_DEBUG // #define DIRTY_DEBUG -// #define THREAD_DEBUG - -// #define FRAME_TIMING - -#ifdef FRAME_TIMING -static QTime frameTimer; -int sceneGraphRenderTime; -int readbackTime; -#endif QQuickItem::UpdatePaintNodeData::UpdatePaintNodeData() : transformNode(0) @@ -306,47 +155,23 @@ QQuickRootItem::QQuickRootItem() void QQuickCanvas::exposeEvent(QExposeEvent *) { Q_D(QQuickCanvas); - d->thread->paint(); + d->windowManager->paint(this); } void QQuickCanvas::resizeEvent(QResizeEvent *) { Q_D(QQuickCanvas); - d->thread->resize(size()); -} - -void QQuickCanvas::animationStarted() -{ - d_func()->thread->animationStarted(); -} - -void QQuickCanvas::animationStopped() -{ - d_func()->thread->animationStopped(); + d->windowManager->resize(this, size()); } void QQuickCanvas::showEvent(QShowEvent *) { - Q_D(QQuickCanvas); - if (d->vsyncAnimations) { - if (!d->animationDriver) { - d->animationDriver = d->context->createAnimationDriver(this); - connect(d->animationDriver, SIGNAL(started()), this, SLOT(animationStarted()), Qt::DirectConnection); - connect(d->animationDriver, SIGNAL(stopped()), this, SLOT(animationStopped()), Qt::DirectConnection); - } - d->animationDriver->install(); - } - - if (!d->thread->isRunning()) { - d->thread->setWindowSize(size()); - d->thread->startRendering(); - } + d_func()->windowManager->show(this); } void QQuickCanvas::hideEvent(QHideEvent *) { - Q_D(QQuickCanvas); - d->thread->stopRendering(); + d_func()->windowManager->hide(this); } void QQuickCanvas::focusOutEvent(QFocusEvent *) @@ -362,64 +187,6 @@ void QQuickCanvas::focusInEvent(QFocusEvent *) } -/*! - Sets weither this canvas should use vsync driven animations. - - This option can only be set on one single QQuickCanvas, and that it's - vsync signal will then be used to drive all animations in the - process. - - This feature is primarily useful for single QQuickCanvas, QML-only - applications. - - \warning Enabling vsync on multiple QQuickCanvas instances has - undefined behavior. - */ -void QQuickCanvas::setVSyncAnimations(bool enabled) -{ - Q_D(QQuickCanvas); - if (visible()) { - qWarning("QQuickCanvas::setVSyncAnimations: Cannot be changed when widget is shown"); - return; - } - d->vsyncAnimations = enabled; -} - - - -/*! - Returns true if this canvas should use vsync driven animations; - otherwise returns false. - */ -bool QQuickCanvas::vsyncAnimations() const -{ - Q_D(const QQuickCanvas); - return d->vsyncAnimations; -} - -void QQuickCanvasPrivate::initializeSceneGraph() -{ - if (!context) - context = QSGContext::createDefaultContext(); - - if (context->isReady()) - return; - - QOpenGLContext *glctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext()); - context->initialize(glctx); - - Q_Q(QQuickCanvas); - - if (!QQuickItemPrivate::get(rootItem)->itemNode()->parent()) { - context->rootNode()->appendChildNode(QQuickItemPrivate::get(rootItem)->itemNode()); - } - - engine = new QSGEngine(); - engine->setCanvas(q); - - emit q_func()->sceneGraphInitialized(); -} - void QQuickCanvasPrivate::polishItems() { while (!itemsToPolish.isEmpty()) { @@ -435,51 +202,46 @@ void QQuickCanvasPrivate::polishItems() void QQuickCanvasPrivate::syncSceneGraph() { + if (!renderer) { + QSGRootNode *rootNode = new QSGRootNode; + rootNode->appendChildNode(QQuickItemPrivate::get(rootItem)->itemNode()); + renderer = context->createRenderer(); + renderer->setRootNode(rootNode); + } + updateDirtyNodes(); // Copy the current state of clearing from canvas into renderer. - context->renderer()->setClearColor(clearColor); + renderer->setClearColor(clearColor); QSGRenderer::ClearMode mode = QSGRenderer::ClearStencilBuffer | QSGRenderer::ClearDepthBuffer; if (clearBeforeRendering) mode |= QSGRenderer::ClearColorBuffer; - context->renderer()->setClearMode(mode); + renderer->setClearMode(mode); } void QQuickCanvasPrivate::renderSceneGraph(const QSize &size) { Q_Q(QQuickCanvas); - context->renderer()->setDeviceRect(QRect(QPoint(0, 0), size)); - context->renderer()->setViewportRect(QRect(QPoint(0, 0), renderTarget ? renderTarget->size() : size)); - context->renderer()->setProjectionMatrixToDeviceRect(); + renderer->setDeviceRect(QRect(QPoint(0, 0), size)); + renderer->setViewportRect(QRect(QPoint(0, 0), renderTarget ? renderTarget->size() : size)); + renderer->setProjectionMatrixToDeviceRect(); emit q->beforeRendering(); - context->renderNextFrame(renderTarget); + context->renderNextFrame(renderer, renderTarget); emit q->afterRendering(); - -#ifdef FRAME_TIMING - sceneGraphRenderTime = frameTimer.elapsed(); -#endif - -#ifdef FRAME_TIMING -// int pixel; -// glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel); - readbackTime = frameTimer.elapsed(); -#endif } - QQuickCanvasPrivate::QQuickCanvasPrivate() : rootItem(0) , activeFocusItem(0) , mouseGrabberItem(0) , dirtyItemList(0) , context(0) + , renderer(0) + , windowManager(0) , clearColor(Qt::white) - , vsyncAnimations(false) , clearBeforeRendering(true) - , thread(0) - , animationDriver(0) , renderTarget(0) , incubationController(0) { @@ -491,10 +253,6 @@ QQuickCanvasPrivate::~QQuickCanvasPrivate() void QQuickCanvasPrivate::init(QQuickCanvas *c) { - QUnifiedTimer* ut = QUnifiedTimer::instance(true); - if (qmlFixedAnimationStep()) - ut->setConsistentTiming(true); - q_ptr = c; Q_Q(QQuickCanvas); @@ -510,26 +268,18 @@ void QQuickCanvasPrivate::init(QQuickCanvas *c) //It is important that this call happens after the rootItem has a canvas.. rootItem->setFocus(true); - bool threaded = !qmlNoThreadedRenderer(); - - if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) { - qWarning("QQuickCanvas: platform does not support threaded rendering!"); - threaded = false; - } - - if (threaded) - thread = new QQuickCanvasRenderThread(); - else - thread = new QQuickCanvasPlainRenderLoop(); - - thread->renderer = q; - thread->d = this; - - context = QSGContext::createDefaultContext(); - thread->moveContextToThread(context); - + windowManager = QQuickWindowManager::instance(); + context = windowManager->sceneGraphContext(); q->setSurfaceType(QWindow::OpenGLSurface); q->setFormat(context->defaultSurfaceFormat()); + + QObject::connect(context, SIGNAL(initialized()), q, SIGNAL(sceneGraphInitialized())); + QObject::connect(context, SIGNAL(invalidated()), q, SIGNAL(sceneGraphInvalidated())); + QObject::connect(context, SIGNAL(invalidated()), q, SLOT(cleanupSceneGraph())); + + // ### TODO: remove QSGEngine + engine = new QSGEngine(); + engine->setCanvas(q); } QDeclarativeListProperty<QObject> QQuickCanvasPrivate::data() @@ -844,8 +594,6 @@ void QQuickCanvasPrivate::cleanup(QSGNode *n) \since QtQuick 2.0 \brief The QQuickCanvas class provides the canvas for displaying a graphical QML scene - \inmodule QtQuick - QQuickCanvas provides the graphical scene management needed to interact with and display a scene of QQuickItems. @@ -872,8 +620,7 @@ QQuickCanvas::~QQuickCanvas() { Q_D(QQuickCanvas); - if (d->thread->isRunning()) - d->thread->stopRendering(); + d->windowManager->canvasDestroyed(this); // ### should we change ~QQuickItem to handle this better? // manually cleanup for the root item (item destructor only handles these when an item is parented) @@ -883,8 +630,6 @@ QQuickCanvas::~QQuickCanvas() delete d->incubationController; d->incubationController = 0; delete d->rootItem; d->rootItem = 0; - - delete d->thread; d->thread = 0; } /*! @@ -1918,9 +1663,20 @@ void QQuickCanvasPrivate::updateDirtyNode(QQuickItem *item) void QQuickCanvas::maybeUpdate() { Q_D(QQuickCanvas); + d->windowManager->maybeUpdate(this); +} + +void QQuickCanvas::cleanupSceneGraph() +{ + Q_D(QQuickCanvas); + + if (!d->renderer) + return; + + delete d->renderer->rootNode(); + delete d->renderer; - if (d->thread && d->thread->isRunning()) - d->thread->maybeUpdate(); + d->renderer = 0; } /*! @@ -2001,7 +1757,7 @@ QOpenGLFramebufferObject *QQuickCanvas::renderTarget() const QImage QQuickCanvas::grabFrameBuffer() { Q_D(QQuickCanvas); - return d->thread ? d->thread->grab() : QImage(); + return d->windowManager->grab(this); } /*! @@ -2120,7 +1876,7 @@ bool QQuickCanvas::clearBeforeRendering() const QSGTexture *QQuickCanvas::createTextureFromImage(const QImage &image) const { Q_D(const QQuickCanvas); - if (d->context) + if (d->context && d->context->isReady()) return d->context->createTexture(image); else return 0; @@ -2143,7 +1899,7 @@ QSGTexture *QQuickCanvas::createTextureFromImage(const QImage &image) const QSGTexture *QQuickCanvas::createTextureFromId(uint id, const QSize &size, CreateTextureOptions options) const { Q_D(const QQuickCanvas); - if (d->context) { + if (d->context && d->context->isReady()) { QSGPlainTexture *texture = new QSGPlainTexture(); texture->setTextureId(id); texture->setHasAlphaChannel(options & TextureHasAlphaChannel); @@ -2166,9 +1922,11 @@ QSGTexture *QQuickCanvas::createTextureFromId(uint id, const QSize &size, Create void QQuickCanvas::setClearColor(const QColor &color) { - if (color == d_func()->clearColor) + Q_D(QQuickCanvas); + if (color == d->clearColor) return; - d_func()->clearColor = color; + + d->clearColor = color; emit clearColorChanged(color); } @@ -2185,462 +1943,6 @@ QColor QQuickCanvas::clearColor() const -void QQuickCanvasRenderLoop::createGLContext() -{ - gl = new QOpenGLContext(); - gl->setFormat(renderer->requestedFormat()); - gl->create(); -} - -void QQuickCanvasRenderThread::run() -{ -#ifdef THREAD_DEBUG - qDebug("QML Rendering Thread Started"); -#endif - - if (!glContext()) { - createGLContext(); - makeCurrent(); - initializeSceneGraph(); - } else { - makeCurrent(); - } - - while (!shouldExit) { - lock(); - - bool sizeChanged = false; - isExternalUpdatePending = false; - - if (renderedSize != windowSize) { -#ifdef THREAD_DEBUG - printf(" RenderThread: window has changed size...\n"); -#endif - glViewport(0, 0, windowSize.width(), windowSize.height()); - sizeChanged = true; - } - -#ifdef THREAD_DEBUG - printf(" RenderThread: preparing to sync...\n"); -#endif - - if (!isGuiBlocked) { - isGuiBlockPending = true; - -#ifdef THREAD_DEBUG - printf(" RenderThread: aquired sync lock...\n"); -#endif - allowMainThreadProcessingFlag = false; - QCoreApplication::postEvent(this, new QEvent(QEvent::User)); -#ifdef THREAD_DEBUG - printf(" RenderThread: going to sleep...\n"); -#endif - wait(); - - isGuiBlockPending = false; - } - -#ifdef THREAD_DEBUG - printf(" RenderThread: Doing locked sync\n"); -#endif -#ifdef QQUICK_CANVAS_TIMING - if (qquick_canvas_timing) - threadTimer.start(); -#endif - inSync = true; - syncSceneGraph(); - inSync = false; - - // Wake GUI after sync to let it continue animating and event processing. - allowMainThreadProcessingFlag = true; - wake(); - unlock(); -#ifdef THREAD_DEBUG - printf(" RenderThread: sync done\n"); -#endif -#ifdef QQUICK_CANVAS_TIMING - if (qquick_canvas_timing) - syncTime = threadTimer.elapsed(); -#endif - -#ifdef THREAD_DEBUG - printf(" RenderThread: rendering... %d x %d\n", windowSize.width(), windowSize.height()); -#endif - - renderSceneGraph(windowSize); -#ifdef QQUICK_CANVAS_TIMING - if (qquick_canvas_timing) - renderTime = threadTimer.elapsed() - syncTime; -#endif - - // The content of the target buffer is undefined after swap() so grab needs - // to happen before swap(); - if (doGrab) { -#ifdef THREAD_DEBUG - printf(" RenderThread: doing a grab...\n"); -#endif - grabContent = qt_gl_read_framebuffer(windowSize, false, false); - doGrab = false; - } - -#ifdef THREAD_DEBUG - printf(" RenderThread: wait for swap...\n"); -#endif - - swapBuffers(); -#ifdef THREAD_DEBUG - printf(" RenderThread: swap complete...\n"); -#endif -#ifdef QQUICK_CANVAS_TIMING - if (qquick_canvas_timing) { - swapTime = threadTimer.elapsed() - renderTime; - qDebug() << "- Breakdown of frame time: sync:" << syncTime - << "ms render:" << renderTime << "ms swap:" << swapTime - << "ms total:" << swapTime + renderTime << "ms"; - } -#endif - - lock(); - isPaintCompleted = true; - if (sizeChanged) - renderedSize = windowSize; - - // Wake the GUI thread now that rendering is complete, to signal that painting - // is done, resizing is done or grabbing is completed. For grabbing, we're - // signalling this much later than needed (we could have done it before swap) - // but we don't want to lock an extra time. - wake(); - - if (!animationRunning && !isExternalUpdatePending && !shouldExit && !doGrab) { -#ifdef THREAD_DEBUG - printf(" RenderThread: nothing to do, going to sleep...\n"); -#endif - isRenderBlocked = true; - wait(); - isRenderBlocked = false; - } - - unlock(); - - QCoreApplication::processEvents(); - - // Process any "deleteLater" objects... - QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); - } - -#ifdef THREAD_DEBUG - printf(" RenderThread: deleting all outstanding nodes\n"); -#endif - cleanupNodesOnShutdown(); - -#ifdef THREAD_DEBUG - printf(" RenderThread: render loop exited... Good Night!\n"); -#endif - - doneCurrent(); - - lock(); - hasExited = true; -#ifdef THREAD_DEBUG - printf(" RenderThread: waking GUI for final sleep..\n"); -#endif - wake(); - unlock(); - -#ifdef THREAD_DEBUG - printf(" RenderThread: All done...\n"); -#endif -} - - - -bool QQuickCanvasRenderThread::event(QEvent *e) -{ - Q_ASSERT(QThread::currentThread() == qApp->thread()); - - if (e->type() == QEvent::User) { - if (!syncAlreadyHappened) - sync(false); - - syncAlreadyHappened = false; - - if (animationRunning && animationDriver()) { -#ifdef THREAD_DEBUG - qDebug("GUI: Advancing animations...\n"); -#endif - - animationDriver()->advance(); - -#ifdef THREAD_DEBUG - qDebug("GUI: Animations advanced...\n"); -#endif - } - - return true; - } - - return QThread::event(e); -} - - - -void QQuickCanvasRenderThread::exhaustSyncEvent() -{ - if (isGuiBlockPending) { - sync(true); - syncAlreadyHappened = true; - } -} - - - -void QQuickCanvasRenderThread::sync(bool guiAlreadyLocked) -{ -#ifdef THREAD_DEBUG - printf("GUI: sync - %s\n", guiAlreadyLocked ? "outside event" : "inside event"); -#endif - if (!guiAlreadyLocked) - lockInGui(); - - renderThreadAwakened = false; - - polishItems(); - - wake(); - wait(); - - if (!guiAlreadyLocked) - unlockInGui(); -} - - - - -/*! - Acquires the mutex for the GUI thread. The function uses the isGuiBlocked - variable to keep track of how many recursion levels the gui is locked with. - We only actually acquire the mutex for the first level to avoid deadlocking - ourselves. - */ - -void QQuickCanvasRenderThread::lockInGui() -{ - // We must avoid recursive locking in the GUI thread, hence we - // only lock when we are the first one to try to block. - if (!isGuiBlocked) - lock(); - - isGuiBlocked++; - -#ifdef THREAD_DEBUG - printf("GUI: aquired lock... %d\n", isGuiBlocked); -#endif -} - - - -void QQuickCanvasRenderThread::unlockInGui() -{ -#ifdef THREAD_DEBUG - printf("GUI: releasing lock... %d\n", isGuiBlocked); -#endif - --isGuiBlocked; - if (!isGuiBlocked) - unlock(); -} - - - - -void QQuickCanvasRenderThread::animationStarted() -{ -#ifdef THREAD_DEBUG - printf("GUI: animationStarted()\n"); -#endif - - lockInGui(); - - animationRunning = true; - - if (isRenderBlocked) - wake(); - - unlockInGui(); -} - - - -void QQuickCanvasRenderThread::animationStopped() -{ -#ifdef THREAD_DEBUG - printf("GUI: animationStopped()...\n"); -#endif - - lockInGui(); - animationRunning = false; - unlockInGui(); -} - - -void QQuickCanvasRenderThread::paint() -{ -#ifdef THREAD_DEBUG - printf("GUI: paint called..\n"); -#endif - - lockInGui(); - exhaustSyncEvent(); - - isPaintCompleted = false; - while (isRunning() && !isPaintCompleted) { - if (isRenderBlocked) - wake(); - wait(); - } - unlockInGui(); -} - - - -void QQuickCanvasRenderThread::resize(const QSize &size) -{ -#ifdef THREAD_DEBUG - printf("GUI: Resize Event: %dx%d\n", size.width(), size.height()); -#endif - - if (!isRunning()) { - windowSize = size; - return; - } - - lockInGui(); - exhaustSyncEvent(); - - windowSize = size; - - while (isRunning() && renderedSize != windowSize) { - if (isRenderBlocked) - wake(); - wait(); - } - unlockInGui(); -} - - - -void QQuickCanvasRenderThread::startRendering() -{ -#ifdef THREAD_DEBUG - printf("GUI: Starting Render Thread\n"); -#endif - hasExited = false; - shouldExit = false; - isGuiBlocked = 0; - isGuiBlockPending = false; - start(); -} - - - -void QQuickCanvasRenderThread::stopRendering() -{ -#ifdef THREAD_DEBUG - printf("GUI: stopping render thread\n"); -#endif - - lockInGui(); - exhaustSyncEvent(); - shouldExit = true; - - if (isRenderBlocked) { -#ifdef THREAD_DEBUG - printf("GUI: waking up render thread\n"); -#endif - wake(); - } - - while (!hasExited) { -#ifdef THREAD_DEBUG - printf("GUI: waiting for render thread to have exited..\n"); -#endif - wait(); - } - - unlockInGui(); - -#ifdef THREAD_DEBUG - printf("GUI: waiting for render thread to terminate..\n"); -#endif - // Actually wait for the thread to terminate. Otherwise we can delete it - // too early and crash. - QThread::wait(); - -#ifdef THREAD_DEBUG - printf("GUI: thread has terminated and we're all good..\n"); -#endif - -} - - - -QImage QQuickCanvasRenderThread::grab() -{ - if (!isRunning()) - return QImage(); - - if (QThread::currentThread() != qApp->thread()) { - qWarning("QQuickCanvas::grabFrameBuffer: can only be called from the GUI thread"); - return QImage(); - } - -#ifdef THREAD_DEBUG - printf("GUI: doing a pixelwise grab..\n"); -#endif - - lockInGui(); - exhaustSyncEvent(); - - doGrab = true; - isPaintCompleted = false; - while (isRunning() && !isPaintCompleted) { - if (isRenderBlocked) - wake(); - wait(); - } - - QImage grabbed = grabContent; - grabContent = QImage(); - - unlockInGui(); - - return grabbed; -} - - - -void QQuickCanvasRenderThread::maybeUpdate() -{ - Q_ASSERT_X(QThread::currentThread() == QCoreApplication::instance()->thread() || inSync, - "QQuickCanvas::update", - "Function can only be called from GUI thread or during QQuickItem::updatePaintNode()"); - - if (inSync) { - isExternalUpdatePending = true; - - } else if (!renderThreadAwakened) { -#ifdef THREAD_DEBUG - printf("GUI: doing update...\n"); -#endif - renderThreadAwakened = true; - lockInGui(); - isExternalUpdatePending = true; - if (isRenderBlocked) - wake(); - unlockInGui(); - } -} - - #include "moc_qquickcanvas.cpp" QT_END_NAMESPACE diff --git a/src/quick/items/qquickcanvas.h b/src/quick/items/qquickcanvas.h index 9ea73b087ba4f8055ce7a27f6997ef8503af3ac1..d38ed97028edffc80e94df255b6d55152a955681 100644 --- a/src/quick/items/qquickcanvas.h +++ b/src/quick/items/qquickcanvas.h @@ -90,9 +90,6 @@ public: QSGEngine *sceneGraphEngine() const; - void setVSyncAnimations(bool enabled); - bool vsyncAnimations() const; - QImage grabFrameBuffer(); void setRenderTarget(QOpenGLFramebufferObject *fbo); @@ -115,6 +112,7 @@ public: Q_SIGNALS: void frameSwapped(); void sceneGraphInitialized(); + void sceneGraphInvalidated(); void beforeRendering(); void afterRendering(); void clearColorChanged(const QColor &); @@ -144,8 +142,7 @@ protected: private Q_SLOTS: void maybeUpdate(); - void animationStarted(); - void animationStopped(); + void cleanupSceneGraph(); private: friend class QQuickItem; diff --git a/src/quick/items/qquickcanvas_p.h b/src/quick/items/qquickcanvas_p.h index 633f3829543cbdb2898f75b14a47ce6b15bab382..d9d130c2805404cc852fdd97ca542a6015a95030 100644 --- a/src/quick/items/qquickcanvas_p.h +++ b/src/quick/items/qquickcanvas_p.h @@ -74,6 +74,8 @@ QT_BEGIN_NAMESPACE //Make it easy to identify and customize the root item if needed +class QQuickWindowManager; + class QQuickRootItem : public QQuickItem { Q_OBJECT @@ -145,7 +147,6 @@ public: void dirtyItem(QQuickItem *); void cleanup(QSGNode *); - void initializeSceneGraph(); void polishItems(); void syncSceneGraph(); void renderSceneGraph(const QSize &size); @@ -166,16 +167,13 @@ public: QSGEngine *engine; QSGContext *context; - QColor clearColor; + QSGRenderer *renderer; - uint vsyncAnimations : 1; - uint clearBeforeRendering : 1; + QQuickWindowManager *windowManager; - QQuickCanvasRenderLoop *thread; - QSize widgetSize; - QSize viewportSize; + QColor clearColor; - QAnimationDriver *animationDriver; + uint clearBeforeRendering : 1; QOpenGLFramebufferObject *renderTarget; @@ -187,134 +185,6 @@ private: static void cleanupNodesOnShutdown(QQuickItem *); }; -class QQuickCanvasRenderLoop -{ -public: - QQuickCanvasRenderLoop() - : d(0) - , renderer(0) - , gl(0) - { - } - virtual ~QQuickCanvasRenderLoop() - { - delete gl; - } - - friend class QQuickCanvasPrivate; - - virtual void paint() = 0; - virtual void resize(const QSize &size) = 0; - virtual void startRendering() = 0; - virtual void stopRendering() = 0; - virtual QImage grab() = 0; - virtual void setWindowSize(const QSize &size) = 0; - virtual void maybeUpdate() = 0; - virtual bool isRunning() const = 0; - virtual void animationStarted() = 0; - virtual void animationStopped() = 0; - virtual void moveContextToThread(QSGContext *) { } - virtual bool *allowMainThreadProcessing() { return 0; } - -protected: - void initializeSceneGraph() { d->initializeSceneGraph(); } - void syncSceneGraph() { d->syncSceneGraph(); } - void cleanupNodesOnShutdown() { d->cleanupNodesOnShutdown(); } - void renderSceneGraph(const QSize &size) { d->renderSceneGraph(size); } - void polishItems() { d->polishItems(); } - QAnimationDriver *animationDriver() const { return d->animationDriver; } - - inline QOpenGLContext *glContext() const { return gl; } - void createGLContext(); - void makeCurrent() { gl->makeCurrent(renderer); } - void doneCurrent() { gl->doneCurrent(); } - void swapBuffers() { - gl->swapBuffers(renderer); - emit renderer->frameSwapped(); - } - -private: - QQuickCanvasPrivate *d; - QQuickCanvas *renderer; - - QOpenGLContext *gl; -}; - -class QQuickCanvasRenderThread : public QThread, public QQuickCanvasRenderLoop -{ - Q_OBJECT -public: - QQuickCanvasRenderThread() - : mutex(QMutex::NonRecursive) - , allowMainThreadProcessingFlag(true) - , animationRunning(false) - , isGuiBlocked(0) - , isPaintCompleted(false) - , isGuiBlockPending(false) - , isRenderBlocked(false) - , isExternalUpdatePending(false) - , syncAlreadyHappened(false) - , inSync(false) - , doGrab(false) - , shouldExit(false) - , hasExited(false) - , renderThreadAwakened(false) - {} - - inline void lock() { mutex.lock(); } - inline void unlock() { mutex.unlock(); } - inline void wait() { condition.wait(&mutex); } - inline void wake() { condition.wakeOne(); } - - void lockInGui(); - void unlockInGui(); - - void paint(); - void resize(const QSize &size); - void startRendering(); - void stopRendering(); - void exhaustSyncEvent(); - void sync(bool guiAlreadyLocked); - bool isRunning() const { return QThread::isRunning(); } - void setWindowSize(const QSize &size) { windowSize = size; } - void maybeUpdate(); - void moveContextToThread(QSGContext *c) { c->moveToThread(this); } - bool *allowMainThreadProcessing() { return &allowMainThreadProcessingFlag; } - - bool event(QEvent *); - - QImage grab(); - -public slots: - void animationStarted(); - void animationStopped(); - -public: - QMutex mutex; - QWaitCondition condition; - - bool allowMainThreadProcessingFlag; - - QSize windowSize; - QSize renderedSize; - - uint animationRunning: 1; - int isGuiBlocked; - uint isPaintCompleted : 1; - uint isGuiBlockPending : 1; - uint isRenderBlocked : 1; - uint isExternalUpdatePending : 1; - uint syncAlreadyHappened : 1; - uint inSync : 1; - uint doGrab : 1; - uint shouldExit : 1; - uint hasExited : 1; - uint renderThreadAwakened : 1; - - QImage grabContent; - - void run(); -}; Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickCanvasPrivate::FocusOptions) diff --git a/src/quick/items/qquickwindowmanager.cpp b/src/quick/items/qquickwindowmanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c6baf13758f6e4f263c452f12c081a2a7bba6680 --- /dev/null +++ b/src/quick/items/qquickwindowmanager.cpp @@ -0,0 +1,1242 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickwindowmanager_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QTime> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/private/qabstractanimation_p.h> + +#include <QtGui/QOpenGLContext> +#include <QtGui/private/qguiapplication_p.h> + +#include <QtDeclarative/private/qdeclarativeglobal_p.h> + +#include <QtQuick/QQuickCanvas> +#include <QtQuick/private/qquickcanvas_p.h> +#include <QtQuick/private/qsgcontext_p.h> + +QT_BEGIN_NAMESPACE + + +#define QQUICK_CANVAS_TIMING +#ifdef QQUICK_CANVAS_TIMING +static bool qquick_canvas_timing = !qgetenv("QML_CANVAS_TIMING").isEmpty(); +static QTime threadTimer; +static int syncTime; +static int renderTime; +static int swapTime; +#endif + +extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); + + + +/*! + expectations for this manager to work: + - one opengl context to render multiple windows + - OpenGL pipeline will not block for vsync in swap + - OpenGL pipeline will block based on a full buffer queue. + - Multiple screens can share the OpenGL context + - Animations are advanced for all windows once per swap + */ + +/* + Threaded Rendering + ================== + + The threaded rendering uses a number of different variables to track potential + states used to handle resizing, initial paint, grabbing and driving animations + while ALWAYS keeping the GL context in the rendering thread and keeping the + overhead of normal one-shot paints and vblank driven animations at a minimum. + + Resize, initial show and grab suffer slightly in this model as they are locked + to the rendering in the rendering thread, but this is a necessary evil for + the system to work. + + Variables that are used: + + Private::animationRunning: This is true while the animations are running, and only + written to inside locks. + + RenderThread::isGuiLocked: This is used to indicate that the GUI thread owns the + lock. This variable is an integer to allow for recursive calls to lockInGui() + without using a recursive mutex. See isPostingSyncEvent. + + RenderThread::isPaintComplete: This variable is cleared when rendering starts and + set once rendering is complete. It is monitored in the paintEvent(), + resizeEvent() and grab() functions to force them to wait for rendering to + complete. + + RenderThread::isPostingSyncEvent: This variable is set in the render thread just + before the sync event is sent to the GUI thread. It is used to avoid deadlocks + in the case where render thread waits while waiting for GUI to pick up the sync + event and GUI thread gets a resizeEvent, the initial paintEvent or a grab. + When this happens, we use the + exhaustSyncEvent() function to do the sync right there and mark the coming + sync event to be discarded. There can only ever be one sync incoming. + + RenderThread::isRenderBlock: This variable is true when animations are not + running and the render thread has gone to sleep, waiting for more to do. + + RenderThread::isExternalUpdatePending: This variable is set to false when + a new render pass is started and to true in maybeUpdate(). It is an + indication to the render thread that another render pass needs to take + place, rather than the render thread going to sleep after completing its swap. + + RenderThread::doGrab: This variable is set by the grab() function and + tells the renderer to do a grab after rendering is complete and before + swapping happens. + + RenderThread::shouldExit: This variable is used to determine if the render + thread should do a nother pass. It is typically set as a result of show() + and unset as a result of hide() or during shutdown() + + RenderThread::hasExited: Used by the GUI thread to synchronize the shutdown + after shouldExit has been set to true. + */ + +DEFINE_BOOL_CONFIG_OPTION(qmlFixedAnimationStep, QML_FIXED_ANIMATION_STEP); +DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP); + +//#define THREAD_DEBUG + +class QQuickRenderThreadSingleContextWindowManager : public QThread, public QQuickWindowManager +{ + Q_OBJECT +public: + QQuickRenderThreadSingleContextWindowManager() + : sg(QSGContext::createDefaultContext()) + , gl(0) + , animationTimer(-1) + , isGuiLocked(0) + , animationRunning(false) + , isPaintCompleted(false) + , isPostingSyncEvent(false) + , isRenderBlocked(false) + , syncAlreadyHappened(false) + , inSync(false) + , shouldExit(false) + , hasExited(false) + , renderThreadAwakened(false) + , canvasToGrab(0) + { + sg->moveToThread(this); + + animationDriver = sg->createAnimationDriver(this); + animationDriver->install(); + connect(animationDriver, SIGNAL(started()), this, SLOT(animationStarted())); + connect(animationDriver, SIGNAL(stopped()), this, SLOT(animationStopped())); + } + + QSGContext *sceneGraphContext() const { return sg; } + + void show(QQuickCanvas *canvas); + void hide(QQuickCanvas *canvas); + + void canvasDestroyed(QQuickCanvas *canvas); + + void paint(QQuickCanvas *canvas); + QImage grab(QQuickCanvas *canvas); + void resize(QQuickCanvas *canvas, const QSize &size); + void maybeUpdate(QQuickCanvas *canvas); + + void startRendering(); + void stopRendering(); + + void exhaustSyncEvent(); + void sync(bool guiAlreadyLocked); + + void initialize(); + + bool *allowMainThreadProcessing() { return &allowMainThreadProcessingFlag; } + + bool event(QEvent *); + + inline void lock() { mutex.lock(); } + inline void unlock() { mutex.unlock(); } + inline void wait() { condition.wait(&mutex); } + inline void wake() { condition.wakeOne(); } + void lockInGui(); + void unlockInGui(); + + void run(); + +public slots: + void animationStarted(); + void animationStopped(); + void canvasVisibilityChanged(); + +private: + void handleAddedWindows(); + void handleAddedWindow(QQuickCanvas *canvas); + void handleRemovedWindows(); + + QSGContext *sg; + QOpenGLContext *gl; + QAnimationDriver *animationDriver; + int animationTimer; + + QMutex mutex; + QWaitCondition condition; + + bool allowMainThreadProcessingFlag; + + int isGuiLocked; + uint animationRunning: 1; + uint isPaintCompleted : 1; + uint isPostingSyncEvent : 1; + uint isRenderBlocked : 1; + uint isExternalUpdatePending : 1; + uint syncAlreadyHappened : 1; + uint inSync : 1; + uint shouldExit : 1; + uint hasExited : 1; + uint renderThreadAwakened : 1; + uint isGuiAboutToBeBlockedForSync : 1; + + QQuickCanvas *canvasToGrab; + QImage grabContent; + + struct CanvasData { + QSize renderedSize; + QSize windowSize; + QSize viewportSize; + + uint sizeWasChanged : 1; + uint isExternalUpdatePending : 1; + }; + + QHash<QQuickCanvas *, CanvasData *> m_rendered_windows; + + struct CanvasTracker { + QQuickCanvas *canvas; + uint isVisible : 1; + uint toBeRemoved : 1; + }; + + QList<CanvasTracker> m_tracked_windows; + + QList<QQuickCanvas *> m_removed_windows; + QList<QQuickCanvas *> m_added_windows; +}; + + +class QQuickTrivialWindowManager : public QObject, public QQuickWindowManager +{ +public: + QQuickTrivialWindowManager(); + + void show(QQuickCanvas *canvas); + void hide(QQuickCanvas *canvas); + + void canvasDestroyed(QQuickCanvas *canvas); + + void renderCanvas(QQuickCanvas *canvas); + void paint(QQuickCanvas *canvas); + QImage grab(QQuickCanvas *canvas); + void resize(QQuickCanvas *canvas, const QSize &size); + + void maybeUpdate(QQuickCanvas *canvas); + + bool *allowMainThreadProcessing(); + + QSGContext *sceneGraphContext() const; + + bool event(QEvent *); + + struct CanvasData { + bool updatePending : 1; + bool grabOnly : 1; + }; + + QHash<QQuickCanvas *, CanvasData> m_windows; + + QOpenGLContext *gl; + QSGContext *sg; + + QImage grabContent; + + bool eventPending; +}; + + +QQuickWindowManager *QQuickWindowManager::instance() +{ + static QQuickWindowManager *theInstance; + + if (!theInstance) { + bool fancy = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL); + if (qmlNoThreadedRenderer()) + fancy = false; + if (qmlFixedAnimationStep()) + QUnifiedTimer::instance(true)->setConsistentTiming(true); + theInstance = fancy + ? (QQuickWindowManager*) new QQuickRenderThreadSingleContextWindowManager + : (QQuickWindowManager*) new QQuickTrivialWindowManager; + } + return theInstance; +} + + + + + +void QQuickRenderThreadSingleContextWindowManager::initialize() +{ + Q_ASSERT(m_rendered_windows.size()); + QQuickCanvas *win = m_rendered_windows.constBegin().key(); + + gl = new QOpenGLContext(); + // Pick up the surface format from one of them + gl->setFormat(win->requestedFormat()); + gl->create(); + gl->makeCurrent(win); + + Q_ASSERT(!sg->isReady()); + sg->initialize(gl); +} + + +/*! + This function is called when the canvas is created to register the canvas with + the window manager. + + Called on GUI Thread. + */ + +void QQuickRenderThreadSingleContextWindowManager::show(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf("GUI: Canvas added to windowing system, %p, %dx%d\n", canvas, canvas->width(), canvas->height()); +#endif + + CanvasTracker tracker; + tracker.canvas = canvas; + tracker.isVisible = false; + tracker.toBeRemoved = false; + m_tracked_windows << tracker; + + connect(canvas, SIGNAL(widthChanged(int)), this, SLOT(canvasVisibilityChanged()), Qt::DirectConnection); + connect(canvas, SIGNAL(heightChanged(int)), this, SLOT(canvasVisibilityChanged()), Qt::DirectConnection); + + canvasVisibilityChanged(); +} + + +void QQuickRenderThreadSingleContextWindowManager::handleAddedWindow(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf(" RenderThread: adding canvas: %p\n", canvas); +#endif + + CanvasData *data = new CanvasData; + data->isExternalUpdatePending = true; + data->sizeWasChanged = false; + data->windowSize = canvas->size(); + m_rendered_windows[canvas] = data; +} + + +/*! + Called on Render Thread + */ +void QQuickRenderThreadSingleContextWindowManager::handleAddedWindows() +{ +#ifdef THREAD_DEBUG + printf(" RenderThread: about to add %d\n", m_added_windows.size()); +#endif + + while (m_added_windows.size()) { + QQuickCanvas *canvas = m_added_windows.takeLast(); + handleAddedWindow(canvas); + } +} + + +/*! + Called on the GUI Thread, from the canvas' destructor + */ + +void QQuickRenderThreadSingleContextWindowManager::canvasDestroyed(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf("GUI: Canvas destroyed: %p\n", canvas); +#endif + + hide(canvas); +} + + +/*! + Called on GUI Thread + */ + +void QQuickRenderThreadSingleContextWindowManager::hide(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf("GUI: Canvas hidden: %p\n", canvas); +#endif + + int position = -1; + for (int i=0; i<m_tracked_windows.size(); ++i) { + if (m_tracked_windows.at(i).canvas == canvas) { + m_tracked_windows[i].toBeRemoved = true; + position = i; + break; + } + } + + if (position >= 0) { + disconnect(canvas, SIGNAL(widthChanged(int)), this, SLOT(canvasVisibilityChanged())); + disconnect(canvas, SIGNAL(heightChanged(int)), this, SLOT(canvasVisibilityChanged())); + canvasVisibilityChanged(); + m_tracked_windows.removeAt(position); + } + +#ifdef THREAD_DEBUG + printf("GUI: Canvas removal completed... %p\n", canvas); +#endif +} + +/*! + Called on Render Thread + */ +void QQuickRenderThreadSingleContextWindowManager::handleRemovedWindows() +{ +#ifdef THREAD_DEBUG + printf(" RenderThread: about to remove %d\n", m_removed_windows.size()); +#endif + + bool removedAnything = false; + while (m_removed_windows.size()) { + QQuickCanvas *canvas = m_removed_windows.takeLast(); +#ifdef THREAD_DEBUG + printf(" RenderThread: removing %p\n", canvas); +#endif + + QQuickCanvasPrivate::get(canvas)->cleanupNodesOnShutdown(); + delete m_rendered_windows.take(canvas); + removedAnything = true; + } + + // If a window is removed because it has been hidden it will take with it + // the gl context (at least on Mac) if bound, so disconnect the gl context + // from anything + if (removedAnything) + gl->doneCurrent(); +} + + + +/*! + Called on GUI Thread + */ + +void QQuickRenderThreadSingleContextWindowManager::canvasVisibilityChanged() +{ + bool anyoneShowing = false; + QList<QQuickCanvas *> toAdd, toRemove; + + // Not optimal, but also not frequently used... + for (int i=0; i<m_tracked_windows.size(); ++i) { + CanvasTracker &t = const_cast<CanvasTracker &>(m_tracked_windows.at(i)); + QQuickCanvas *win = t.canvas; + + Q_ASSERT(win->visible() || t.toBeRemoved); + bool canvasVisible = win->width() > 0 && win->height() > 0; + anyoneShowing |= (canvasVisible && !t.toBeRemoved); + + if ((!canvasVisible && t.isVisible) || t.toBeRemoved) { + toRemove << win; + } else if (canvasVisible && !t.isVisible) { + toAdd << win; + } + t.isVisible = canvasVisible; + } + + if (isRunning()) { + if (!anyoneShowing) { + stopRendering(); + } else { + lockInGui(); + exhaustSyncEvent(); + m_added_windows << toAdd; + m_removed_windows << toRemove; + while (isRunning() && (m_added_windows.size() || m_removed_windows.size())) { + if (isRenderBlocked) + wake(); + wait(); + } + unlockInGui(); + } + + } else if (anyoneShowing) { + Q_ASSERT(toRemove.isEmpty()); // since loop is not running, nothing is showing now + for (int i=0; i<toAdd.size(); ++i) + handleAddedWindow(toAdd.at(i)); + startRendering(); + } + +} + + +void QQuickRenderThreadSingleContextWindowManager::run() +{ +#ifdef THREAD_DEBUG + printf("QML Rendering Thread Started\n"); +#endif + + if (!gl) + initialize(); + + while (!shouldExit) { + lock(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: *** NEW FRAME ***\n"); +#endif + + handleAddedWindows(); + + if (!isGuiLocked) { + isPostingSyncEvent = true; + +#ifdef THREAD_DEBUG + printf(" RenderThread: aquired sync lock...\n"); +#endif + allowMainThreadProcessingFlag = false; + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + +#ifdef THREAD_DEBUG + printf(" RenderThread: going to sleep...\n"); +#endif + wake(); // In case the event got through all the way to wait() before this thread got to wait. + wait(); + + + isPostingSyncEvent = false; + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: Doing locked sync\n"); +#endif +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + threadTimer.start(); +#endif + inSync = true; + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + QQuickCanvas *canvas = it.key(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: Syncing canvas: %p\n", canvas); +#endif + + CanvasData *canvasData = it.value(); + QQuickCanvasPrivate *canvasPrivate = QQuickCanvasPrivate::get(canvas); + + Q_ASSERT(canvasData->windowSize.width() > 0 && canvasData->windowSize.height() > 0); + + gl->makeCurrent(canvas); + + if (canvasData->viewportSize != canvasData->windowSize) { +#ifdef THREAD_DEBUG + printf(" RenderThread: --- window has changed size...\n"); +#endif + canvasData->viewportSize = canvasData->windowSize; + canvasData->sizeWasChanged = true; + glViewport(0, 0, canvasData->viewportSize.width(), canvasData->viewportSize.height()); + } + + canvasData->isExternalUpdatePending = false; + canvasPrivate->syncSceneGraph(); + } + inSync = false; + + // Wake GUI after sync to let it continue animating and event processing. + allowMainThreadProcessingFlag = true; + wake(); + unlock(); +#ifdef THREAD_DEBUG + printf(" RenderThread: sync done\n"); +#endif +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + syncTime = threadTimer.elapsed(); +#endif + + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + QQuickCanvas *canvas = it.key(); + CanvasData *canvasData = it.value(); + QQuickCanvasPrivate *canvasPrivate = QQuickCanvasPrivate::get(canvas); + +#ifdef THREAD_DEBUG + printf(" RenderThread: Rendering canvas %p\n", canvas); +#endif + + Q_ASSERT(canvasData->windowSize.width() > 0 && canvasData->windowSize.height() > 0); + +#ifdef THREAD_DEBUG + printf(" RenderThread: --- rendering at size %dx%d\n", + canvasData->viewportSize.width(), canvasData->viewportSize.height() + ); +#endif + + // We only need to re-makeCurrent when we have multiple surfaces. + if (m_rendered_windows.size() > 1) + gl->makeCurrent(canvas); + + canvasPrivate->renderSceneGraph(canvasData->viewportSize); +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + renderTime = threadTimer.elapsed() - syncTime; +#endif + + // The content of the target buffer is undefined after swap() so grab needs + // to happen before swap(); + if (canvas == canvasToGrab) { +#ifdef THREAD_DEBUG + printf(" RenderThread: --- grabbing...\n"); +#endif + grabContent = qt_gl_read_framebuffer(canvasData->windowSize, false, false); + canvasToGrab = 0; + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: --- wait for swap...\n"); +#endif + + gl->swapBuffers(canvas); +#ifdef THREAD_DEBUG + printf(" RenderThread: --- swap complete...\n"); +#endif + + } + +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) { + swapTime = threadTimer.elapsed() - renderTime; + qDebug() << "- Breakdown of frame time; sync:" << syncTime + << "ms render:" << renderTime << "ms swap:" << swapTime + << "ms total:" << swapTime + renderTime << "ms"; + } +#endif + + lock(); + + handleRemovedWindows(); + + isPaintCompleted = true; + + bool isExternalUpdatePending = false; + + // Update sizes... + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + CanvasData *canvasData = it.value(); + if (canvasData->sizeWasChanged) { + canvasData->renderedSize = canvasData->viewportSize; + canvasData->sizeWasChanged = false; + } + isExternalUpdatePending |= canvasData->isExternalUpdatePending; + } + + + // Wake the GUI thread now that rendering is complete, to signal that painting + // is done, resizing is done or grabbing is completed. For grabbing, we're + // signalling this much later than needed (we could have done it before swap) + // but we don't want to lock an extra time. + wake(); + + if (!animationRunning && !isExternalUpdatePending && !shouldExit && !canvasToGrab) { +#ifdef THREAD_DEBUG + printf(" RenderThread: nothing to do, going to sleep...\n"); +#endif + isRenderBlocked = true; + wait(); + isRenderBlocked = false; + } + + unlock(); + + QCoreApplication::processEvents(); + + // Process any "deleteLater" objects... + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: deleting all outstanding nodes\n"); +#endif + + m_removed_windows << m_rendered_windows.keys(); + handleRemovedWindows(); + + sg->invalidate(); + + gl->doneCurrent(); + delete gl; + gl = 0; + +#ifdef THREAD_DEBUG + printf(" RenderThread: render loop exited... Good Night!\n"); +#endif + + lock(); + hasExited = true; + +#ifdef THREAD_DEBUG + printf(" RenderThread: waking GUI for final sleep..\n"); +#endif + wake(); + unlock(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: All done...\n"); +#endif +} + +bool QQuickRenderThreadSingleContextWindowManager::event(QEvent *e) +{ + Q_ASSERT(QThread::currentThread() == qApp->thread()); + + if (e->type() == QEvent::User) { + + // If all canvases have been hidden, ignore the event + if (!isRunning()) + return true; + + if (!syncAlreadyHappened) + sync(false); + + syncAlreadyHappened = false; + + if (animationRunning) { +#ifdef THREAD_DEBUG + printf("GUI: Advancing animations...\n"); +#endif + + animationDriver->advance(); + +#ifdef THREAD_DEBUG + printf("GUI: Animations advanced...\n"); +#endif + } + + return true; + + } else if (e->type() == QEvent::Timer) { +#ifdef THREAD_DEBUG + printf("GUI: Animations advanced via timer...\n"); +#endif + animationDriver->advance(); + } + + return QThread::event(e); +} + + + +void QQuickRenderThreadSingleContextWindowManager::exhaustSyncEvent() +{ + if (isPostingSyncEvent) { + sync(true); + syncAlreadyHappened = true; + } +} + + + +void QQuickRenderThreadSingleContextWindowManager::sync(bool guiAlreadyLocked) +{ +#ifdef THREAD_DEBUG + printf("GUI: sync - %s\n", guiAlreadyLocked ? "outside event" : "inside event"); +#endif + if (!guiAlreadyLocked) + lockInGui(); + + renderThreadAwakened = false; + + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + QQuickCanvasPrivate::get(it.key())->polishItems(); + } + + wake(); + wait(); + + if (!guiAlreadyLocked) + unlockInGui(); +} + + + + +/*! + Acquires the mutex for the GUI thread. The function uses the isGuiLocked + variable to keep track of how many recursion levels the gui is locked with. + We only actually acquire the mutex for the first level to avoid deadlocking + ourselves. + */ + +void QQuickRenderThreadSingleContextWindowManager::lockInGui() +{ + if (++isGuiLocked == 1) + lock(); + +#ifdef THREAD_DEBUG + printf("GUI: aquired lock... level=%d\n", isGuiLocked); +#endif +} + + + +void QQuickRenderThreadSingleContextWindowManager::unlockInGui() +{ +#ifdef THREAD_DEBUG + printf("GUI: releasing lock... level=%d\n", isGuiLocked); +#endif + + if (--isGuiLocked == 0) + unlock(); +} + + + + +void QQuickRenderThreadSingleContextWindowManager::animationStarted() +{ +#ifdef THREAD_DEBUG + printf("GUI: animationStarted()\n"); +#endif + + if (!isRunning()) { + animationTimer = startTimer(1000/60); + return; + } + + lockInGui(); + + animationRunning = true; + + if (isRenderBlocked) + wake(); + + unlockInGui(); +} + + + +void QQuickRenderThreadSingleContextWindowManager::animationStopped() +{ +#ifdef THREAD_DEBUG + printf("GUI: animationStopped()...\n"); +#endif + + if (!isRunning()) { + killTimer(animationTimer); + animationTimer = -1; + return; + } + + lockInGui(); + animationRunning = false; + unlockInGui(); +} + + +void QQuickRenderThreadSingleContextWindowManager::paint(QQuickCanvas *canvas) +{ + Q_UNUSED(canvas); +#ifdef THREAD_DEBUG + printf("GUI: paint called: %p\n", canvas); +#endif + + return; + + + + lockInGui(); + exhaustSyncEvent(); + + isPaintCompleted = false; + while (isRunning() && !isPaintCompleted) { + if (isRenderBlocked) + wake(); + wait(); + } + unlockInGui(); + +#ifdef THREAD_DEBUG + printf("GUI: paint done: %p\n", canvas); +#endif +} + + + +void QQuickRenderThreadSingleContextWindowManager::resize(QQuickCanvas *canvas, const QSize &size) +{ +#ifdef THREAD_DEBUG + printf("GUI: Resize Event: %p = %dx%d\n", canvas, size.width(), size.height()); +#endif + + // If the rendering thread is not running we do not need to do anything. + // Also if the canvas is being resized to an invalid size, it will be removed + // by the canvasVisibilityChanged slot as result of width/heightcChanged() + if (!isRunning() || size.width() <= 0 || size.height() <= 0) + return; + + lockInGui(); + exhaustSyncEvent(); + + CanvasData *canvasData = m_rendered_windows.value(canvas); + if (canvasData) { + canvasData->windowSize = size; + while (isRunning() && canvasData->renderedSize != size && size.width() > 0 && size.height() > 0) { + if (isRenderBlocked) + wake(); + wait(); + } + } + unlockInGui(); + +#ifdef THREAD_DEBUG + printf("GUI: Resize done: %p\n", canvas); +#endif +} + + + +void QQuickRenderThreadSingleContextWindowManager::startRendering() +{ +#ifdef THREAD_DEBUG + printf("GUI: Starting Render Thread\n"); +#endif + hasExited = false; + shouldExit = false; + isGuiLocked = 0; + isPostingSyncEvent = false; + syncAlreadyHappened = false; + renderThreadAwakened = false; + inSync = false; + + start(); // Start the render thread... + + // Animations will now be driven from the rendering thread. + if (animationTimer >= 0) { + killTimer(animationTimer); + animationTimer = -1; + } + + +} + + + +void QQuickRenderThreadSingleContextWindowManager::stopRendering() +{ +#ifdef THREAD_DEBUG + printf("GUI: stopping render thread\n"); +#endif + + lockInGui(); + exhaustSyncEvent(); + shouldExit = true; + + if (isRenderBlocked) { +#ifdef THREAD_DEBUG + printf("GUI: waking up render thread\n"); +#endif + wake(); + } + + while (!hasExited) { +#ifdef THREAD_DEBUG + printf("GUI: waiting for render thread to have exited..\n"); +#endif + wait(); + } + + unlockInGui(); + +#ifdef THREAD_DEBUG + printf("GUI: waiting for render thread to terminate..\n"); +#endif + // Actually wait for the thread to terminate. Otherwise we can delete it + // too early and crash. + QThread::wait(); + +#ifdef THREAD_DEBUG + printf("GUI: thread has terminated and we're all good..\n"); +#endif + + // Activate timer to keep animations running + if (animationDriver->isRunning()) + animationTimer = startTimer(1000/60); +} + + + +QImage QQuickRenderThreadSingleContextWindowManager::grab(QQuickCanvas *canvas) +{ + if (!isRunning()) + return QImage(); + + if (QThread::currentThread() != qApp->thread()) { + qWarning("QQuickCanvas::grabFrameBuffer: can only be called from the GUI thread"); + return QImage(); + } + +#ifdef THREAD_DEBUG + printf("GUI: doing a pixelwise grab..\n"); +#endif + + lockInGui(); + exhaustSyncEvent(); + + canvasToGrab = canvas; + isPaintCompleted = false; + while (isRunning() && !isPaintCompleted) { + if (isRenderBlocked) + wake(); + wait(); + } + + QImage grabbed = grabContent; + grabContent = QImage(); + + unlockInGui(); + + return grabbed; +} + + + +void QQuickRenderThreadSingleContextWindowManager::maybeUpdate(QQuickCanvas *canvas) +{ + Q_ASSERT_X(QThread::currentThread() == QCoreApplication::instance()->thread() || inSync, + "QQuickCanvas::update", + "Function can only be called from GUI thread or during QQuickItem::updatePaintNode()"); + + if (inSync) { + CanvasData *canvasData = m_rendered_windows.value(canvas); + if (canvasData) + canvasData->isExternalUpdatePending = true; + + } else if (!renderThreadAwakened) { +#ifdef THREAD_DEBUG + printf("GUI: doing update...\n"); +#endif + renderThreadAwakened = true; + bool locked = false; + + // If we are getting here from the renderer's sync event, the renderer is about + // to go to sleep anyway. + if (!isGuiAboutToBeBlockedForSync) + lockInGui(); + CanvasData *canvasData = m_rendered_windows.value(canvas); + if (canvasData) + canvasData->isExternalUpdatePending = true; + if (isRenderBlocked) + wake(); + if (!isGuiAboutToBeBlockedForSync) + unlockInGui(); + } +} + +QQuickTrivialWindowManager::QQuickTrivialWindowManager() + : gl(0) + , eventPending(false) +{ + sg = QSGContext::createDefaultContext(); +} + + +void QQuickTrivialWindowManager::show(QQuickCanvas *canvas) +{ + CanvasData data; + data.updatePending = false; + m_windows[canvas] = data; + + maybeUpdate(canvas); +} + +void QQuickTrivialWindowManager::hide(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + m_windows.remove(canvas); + QQuickCanvasPrivate *cd = QQuickCanvasPrivate::get(canvas); + cd->cleanupNodesOnShutdown(); + + if (m_windows.size() == 0) { + sg->invalidate(); + delete gl; + gl = 0; + } +} + +void QQuickTrivialWindowManager::canvasDestroyed(QQuickCanvas *canvas) +{ + hide(canvas); +} + +void QQuickTrivialWindowManager::renderCanvas(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + CanvasData &data = const_cast<CanvasData &>(m_windows[canvas]); + + if (!gl) { + gl = new QOpenGLContext(); + gl->setFormat(canvas->requestedFormat()); + gl->create(); + gl->makeCurrent(canvas); + sg->initialize(gl); + } else { + gl->makeCurrent(canvas); + } + + bool alsoSwap = data.updatePending; + data.updatePending = false; + + QQuickCanvasPrivate *cd = QQuickCanvasPrivate::get(canvas); + cd->polishItems(); + cd->syncSceneGraph(); + cd->renderSceneGraph(canvas->size()); + + if (data.grabOnly) { + grabContent = qt_gl_read_framebuffer(canvas->size(), false, false); + data.grabOnly = false; + } + + if (alsoSwap) + gl->swapBuffers(canvas); + + // Might have been set during syncSceneGraph() + if (data.updatePending) + maybeUpdate(canvas); +} + +void QQuickTrivialWindowManager::paint(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + m_windows[canvas].updatePending = true; + renderCanvas(canvas); +} + +QImage QQuickTrivialWindowManager::grab(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return QImage(); + + m_windows[canvas].grabOnly = true; + + renderCanvas(canvas); + + QImage grabbed = grabContent; + grabContent = QImage(); + return grabbed; +} + + + +void QQuickTrivialWindowManager::resize(QQuickCanvas *, const QSize &) +{ +} + + + +void QQuickTrivialWindowManager::maybeUpdate(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + m_windows[canvas].updatePending = true; + + if (!eventPending) { + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + eventPending = true; + } +} + + + +bool *QQuickTrivialWindowManager::allowMainThreadProcessing() +{ + return 0; +} + + + +QSGContext *QQuickTrivialWindowManager::sceneGraphContext() const +{ + return sg; +} + + +bool QQuickTrivialWindowManager::event(QEvent *e) +{ + if (e->type() == QEvent::User) { + eventPending = false; + for (QHash<QQuickCanvas *, CanvasData>::const_iterator it = m_windows.constBegin(); + it != m_windows.constEnd(); ++it) { + const CanvasData &data = it.value(); + if (data.updatePending) + renderCanvas(it.key()); + } + return true; + } + return QObject::event(e); +} + +#include "qquickwindowmanager.moc" + +QT_END_NAMESPACE diff --git a/src/quick/items/qquickwindowmanager_p.h b/src/quick/items/qquickwindowmanager_p.h new file mode 100644 index 0000000000000000000000000000000000000000..8a5073effd394b4fb06b93017d5ff0746021fae5 --- /dev/null +++ b/src/quick/items/qquickwindowmanager_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKWINDOWMANAGER_P_H +#define QQUICKWINDOWMANAGER_P_H + +#include <QtGui/QImage> + +QT_BEGIN_NAMESPACE + +class QQuickCanvas; +class QSGContext; + +class QQuickWindowManager +{ +public: + virtual void show(QQuickCanvas *canvas) = 0; + virtual void hide(QQuickCanvas *canvas) = 0; + + virtual void canvasDestroyed(QQuickCanvas *canvas) = 0; + + virtual void paint(QQuickCanvas *canvas) = 0; + virtual QImage grab(QQuickCanvas *canvas) = 0; + virtual void resize(QQuickCanvas *canvas, const QSize &size) = 0; + + virtual void maybeUpdate(QQuickCanvas *canvas) = 0; + + virtual bool *allowMainThreadProcessing() = 0; + + virtual QSGContext *sceneGraphContext() const = 0; + + // ### make this less of a singleton + static QQuickWindowManager *instance(); +}; + +QT_END_NAMESPACE + +#endif // QQUICKWINDOWMANAGER_P_H diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp index 93fea15921d4811a43afc6a933664709eea9f9a5..b83e328158ddd3407e6750428fe337ff878e6ef9 100644 --- a/src/quick/scenegraph/qsgcontext.cpp +++ b/src/quick/scenegraph/qsgcontext.cpp @@ -39,30 +39,25 @@ ** ****************************************************************************/ -#include "qsgcontext_p.h" -#include <QtQuick/private/qsgrenderer_p.h> -#include <QtQuick/qsgnode.h> - -#include <QtQuick/private/qdeclarativepixmapcache_p.h> - -#include <private/qsgdefaultrenderer_p.h> - +#include <QtQuick/private/qsgcontext_p.h> +#include <QtQuick/private/qsgdefaultrenderer_p.h> #include <QtQuick/private/qsgdistancefieldutil_p.h> #include <QtQuick/private/qsgdefaultdistancefieldglyphcache_p.h> -#include <private/qsgdefaultrectanglenode_p.h> -#include <private/qsgdefaultimagenode_p.h> -#include <private/qsgdefaultglyphnode_p.h> -#include <private/qsgdistancefieldglyphnode_p.h> - +#include <QtQuick/private/qsgdefaultrectanglenode_p.h> +#include <QtQuick/private/qsgdefaultimagenode_p.h> +#include <QtQuick/private/qsgdefaultglyphnode_p.h> +#include <QtQuick/private/qsgdistancefieldglyphnode_p.h> #include <QtQuick/private/qsgtexture_p.h> +#include <QtQuick/private/qdeclarativepixmapcache_p.h> + #include <QGuiApplication> #include <QOpenGLContext> #include <QDeclarativeImageProvider> +#include <private/qdeclarativeglobal_p.h> #include <private/qobject_p.h> #include <qmutex.h> -#include <private/qdeclarativeglobal_p.h> DEFINE_BOOL_CONFIG_OPTION(qmlFlashMode, QML_FLASH_MODE) DEFINE_BOOL_CONFIG_OPTION(qmlTranslucentMode, QML_TRANSLUCENT_MODE) @@ -140,14 +135,25 @@ QSGContext::QSGContext(QObject *parent) : QSGContext::~QSGContext() +{ + invalidate(); +} + + + +void QSGContext::invalidate() { Q_D(QSGContext); qDeleteAll(d->textures.values()); d->textures.clear(); - delete d->renderer; - delete d->rootNode; qDeleteAll(d->materials.values()); + d->materials.clear(); delete d->distanceFieldCacheManager; + d->distanceFieldCacheManager = 0; + + d->gl = 0; + + emit invalidated(); } @@ -181,28 +187,6 @@ void QSGContext::textureFactoryDestroyed(QObject *o) } - -/*! - Returns the renderer. The renderer instance is created through the adaptation layer. - */ -QSGRenderer *QSGContext::renderer() const -{ - Q_D(const QSGContext); - return d->renderer; -} - - -/*! - Returns the root node. The root node instance is only created once the scene graph - context becomes ready. - */ -QSGRootNode *QSGContext::rootNode() const -{ - Q_D(const QSGContext); - return d->rootNode; -} - - QOpenGLContext *QSGContext::glContext() const { Q_D(const QSGContext); @@ -218,16 +202,9 @@ void QSGContext::initialize(QOpenGLContext *context) Q_D(QSGContext); Q_ASSERT(!d->gl); - d->gl = context; - d->renderer = createRenderer(); - d->renderer->setClearColor(Qt::white); - - d->rootNode = new QSGRootNode(); - d->renderer->setRootNode(d->rootNode); - - emit ready(); + emit initialized(); } @@ -242,15 +219,15 @@ bool QSGContext::isReady() const } -void QSGContext::renderNextFrame(QOpenGLFramebufferObject *fbo) +void QSGContext::renderNextFrame(QSGRenderer *renderer, QOpenGLFramebufferObject *fbo) { Q_D(QSGContext); if (fbo) { QSGBindableFbo bindable(fbo); - d->renderer->renderScene(bindable); + renderer->renderScene(bindable); } else { - d->renderer->renderScene(); + renderer->renderScene(); } } @@ -344,7 +321,7 @@ QSGRenderer *QSGContext::createRenderer() bool QSGContext::canDecodeImageToTexture() const { - return true; + return false; } diff --git a/src/quick/scenegraph/qsgcontext_p.h b/src/quick/scenegraph/qsgcontext_p.h index ded9d2727ae35ed88488dcd7f8f08590401fdf42..0d69a4fb471f91702435b46cc4397f244e5fc7e6 100644 --- a/src/quick/scenegraph/qsgcontext_p.h +++ b/src/quick/scenegraph/qsgcontext_p.h @@ -56,6 +56,8 @@ QT_BEGIN_HEADER QT_BEGIN_NAMESPACE +QT_MODULE(Declarative) + class QSGContextPrivate; class QSGRectangleNode; class QSGImageNode; @@ -83,11 +85,7 @@ public: ~QSGContext(); virtual void initialize(QOpenGLContext *context); - - QSGRenderer *renderer() const; - - void setRootNode(QSGRootNode *node); - QSGRootNode *rootNode() const; + virtual void invalidate(); QOpenGLContext *glContext() const; @@ -95,7 +93,7 @@ public: QSGMaterialShader *prepareMaterial(QSGMaterial *material); - virtual void renderNextFrame(QOpenGLFramebufferObject *fbo = 0); + virtual void renderNextFrame(QSGRenderer *renderer, QOpenGLFramebufferObject *fbo = 0); virtual QSGDistanceFieldGlyphCache *createDistanceFieldGlyphCache(const QRawFont &font); @@ -106,8 +104,9 @@ public: virtual bool canDecodeImageToTexture() const; virtual QSGTexture *decodeImageToTexture(QIODevice *dev, - QSize *size, - const QSize &requestSize); + QSize *size, + const QSize &requestSize); + virtual QSGTexture *createTexture(const QImage &image = QImage()) const; virtual QSize minimumFBOSize() const; @@ -128,11 +127,13 @@ public: virtual QAnimationDriver *createAnimationDriver(QObject *parent); -signals: - void ready(); public slots: void textureFactoryDestroyed(QObject *o); + +signals: + void initialized(); + void invalidated(); }; QT_END_NAMESPACE diff --git a/tests/auto/qtquick2/qquickcanvas/data/AnimationsWhileHidden.qml b/tests/auto/qtquick2/qquickcanvas/data/AnimationsWhileHidden.qml new file mode 100644 index 0000000000000000000000000000000000000000..e95b029210ec0ac9cc10594dcb3cb8e5b1376845 --- /dev/null +++ b/tests/auto/qtquick2/qquickcanvas/data/AnimationsWhileHidden.qml @@ -0,0 +1,17 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 as Window + +Window.Window +{ + id: win + visible: true + width: 250 + height: 250 + + SequentialAnimation { + PauseAnimation { duration: 500 } + PropertyAction { target: win; property: "visible"; value: true } + loops: Animation.Infinite + running: true + } +} diff --git a/tests/auto/qtquick2/qquickcanvas/qquickcanvas.pro b/tests/auto/qtquick2/qquickcanvas/qquickcanvas.pro index c95d474a2125e0553d2a878189ffc55534971753..b4a4bc5d9cf8d69b76a27cb458744d4c69c20484 100644 --- a/tests/auto/qtquick2/qquickcanvas/qquickcanvas.pro +++ b/tests/auto/qtquick2/qquickcanvas/qquickcanvas.pro @@ -6,3 +6,7 @@ macx:CONFIG -= app_bundle CONFIG += parallel_test QT += core-private gui-private declarative-private quick-private testlib + +OTHER_FILES += \ + data/AnimationsWhileHidden.qml + diff --git a/tests/auto/qtquick2/qquickcanvas/tst_qquickcanvas.cpp b/tests/auto/qtquick2/qquickcanvas/tst_qquickcanvas.cpp index f894ff3d39fa9ff3cac4cf16b65b01bcacd5d09a..60522b7d44c319f8a0ec5d7126ce1569bb436a3d 100644 --- a/tests/auto/qtquick2/qquickcanvas/tst_qquickcanvas.cpp +++ b/tests/auto/qtquick2/qquickcanvas/tst_qquickcanvas.cpp @@ -203,6 +203,11 @@ private slots: void qmlCreation(); void clearColor(); + + void grab(); + void multipleWindows(); + + void animationsWhileHidden(); }; tst_qquickcanvas::tst_qquickcanvas() @@ -221,6 +226,7 @@ void tst_qquickcanvas::cleanupTestCase() void tst_qquickcanvas::constantUpdates() { QQuickCanvas canvas; + canvas.resize(250, 250); ConstantUpdateItem item(canvas.rootItem()); canvas.show(); QTRY_VERIFY(item.iterations > 60); @@ -520,6 +526,8 @@ void tst_qquickcanvas::mouseFiltering() QCOMPARE(middleItem->mousePressId, 1); QCOMPARE(bottomItem->mousePressId, 2); QCOMPARE(topItem->mousePressId, 3); + + delete canvas; } void tst_qquickcanvas::qmlCreation() @@ -552,6 +560,68 @@ void tst_qquickcanvas::clearColor() delete canvas; } +void tst_qquickcanvas::grab() +{ + QQuickCanvas canvas; + canvas.setClearColor(Qt::red); + + canvas.resize(250, 250); + canvas.show(); + + QImage content = canvas.grabFrameBuffer(); + QCOMPARE(content.width(), canvas.width()); + QCOMPARE(content.height(), canvas.height()); + QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000); +} + +void tst_qquickcanvas::multipleWindows() +{ + QList<QQuickCanvas *> windows; + for (int i=0; i<6; ++i) { + QQuickCanvas *c = new QQuickCanvas(); + c->setClearColor(Qt::GlobalColor(Qt::red + i)); + c->resize(300, 200); + c->setPos(100 + i * 30, 100 + i * 20); + c->show(); + windows << c; + QVERIFY(c->visible()); + } + + // move them + for (int i=0; i<windows.size(); ++i) { + QQuickCanvas *c = windows.at(i); + c->setPos(c->x() - 10, c->y() - 10); + } + + // resize them + for (int i=0; i<windows.size(); ++i) { + QQuickCanvas *c = windows.at(i); + c->resize(200, 150); + } + + qDeleteAll(windows); +} + +void tst_qquickcanvas::animationsWhileHidden() +{ + QDeclarativeEngine engine; + QDeclarativeComponent component(&engine); + component.loadUrl(TESTDATA("AnimationsWhileHidden.qml")); + QObject* created = component.create(); + + QQuickCanvas* canvas = qobject_cast<QQuickCanvas*>(created); + QVERIFY(canvas); + QVERIFY(canvas->visible()); + + // Now hide the window and verify that it went off screen + canvas->hide(); + QTest::qWait(10); + QVERIFY(!canvas->visible()); + + // Running animaiton should cause it to become visible again shortly. + QTRY_VERIFY(canvas->visible()); +} + QTEST_MAIN(tst_qquickcanvas) #include "tst_qquickcanvas.moc" diff --git a/tools/qmlscene/main.cpp b/tools/qmlscene/main.cpp index 6cefc8a5de5ac490617ea09d8611a88bfa42b3f8..a9a03aac6c1f2ceb9226047c9d0505ca89fc8291 100644 --- a/tools/qmlscene/main.cpp +++ b/tools/qmlscene/main.cpp @@ -159,7 +159,6 @@ struct Options bool scenegraphOnGraphicsview; bool clip; bool versionDetection; - bool vsync; }; #if defined(QMLSCENE_BUNDLE) @@ -364,8 +363,6 @@ int main(int argc, char ** argv) options.versionDetection = false; else if (lowerArgument == QLatin1String("-i") && i + 1 < argc) imports.append(QString::fromLatin1(argv[++i])); - else if (lowerArgument == QLatin1String("--no-vsync-animations")) - options.vsync = false; else if (lowerArgument == QLatin1String("--help") || lowerArgument == QLatin1String("-help") || lowerArgument == QLatin1String("--h") @@ -395,7 +392,6 @@ int main(int argc, char ** argv) if (options.versionDetection) checkAndAdaptVersion(options.file); QQuickView *qxView = new MyQQuickView(); - qxView->setVSyncAnimations(options.vsync); engine = qxView->engine(); for (int i = 0; i < imports.size(); ++i) engine->addImportPath(imports.at(i));