Renderloop for Windows
authorGunnar Sletta <gunnar.sletta@digia.com>
Tue, 16 Apr 2013 15:25:04 +0000 (17:25 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Wed, 24 Apr 2013 15:14:41 +0000 (17:14 +0200)
The normal GUI thread render loop has several problems on windows.
It does not do vsync animations and on some hardware, where the
vsync delta is higher than the time it takes to fire a 16ms timer,
the eventloop will always contain at least one event and we never
return to processing non-client area events, like resize.

Also, threaded OpenGL seems rather unstable, so the threaded renderer
is not a stable option.

So we introduce a windows based renderloop. It is fully cross platform
but written to make the most out of the limitations which exist.

The overall goal is:
  - vsync animations when allowed by the system. We get this by
    using an animation driver and advancing in sync with rendering
  - Keep system load low and allow for NC processing. The maybeUpdate
    function will start a short timer which will let the renderloop
    idle for few ms, allowing the eventloop to pick up system events.
    (this is similar to what the threaded renderer also does, btw)

Change-Id: Ic192fd0ed7d5ecdaa2c887c08cbeb42c5de6b8a8
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Samuel Rødal <samuel.rodal@digia.com>
src/quick/scenegraph/qsgrenderloop.cpp
src/quick/scenegraph/qsgwindowsrenderloop.cpp [new file with mode: 0644]
src/quick/scenegraph/qsgwindowsrenderloop_p.h [new file with mode: 0644]
src/quick/scenegraph/scenegraph.pri

index 04981b5..f71ccea 100644 (file)
@@ -41,6 +41,7 @@
 
 #include "qsgrenderloop_p.h"
 #include "qsgthreadedrenderloop_p.h"
+#include "qsgwindowsrenderloop_p.h"
 
 #include <QtCore/QCoreApplication>
 #include <QtCore/QTime>
@@ -130,15 +131,6 @@ QSGRenderLoop *QSGRenderLoop::instance()
         s_instance = QSGContext::createWindowManager();
 
         bool bufferQueuing = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::BufferQueueingOpenGL);
-#ifdef Q_OS_WIN
-        bool fancy = false; // QTBUG-28037
-#else
-        bool fancy = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL);
-#endif
-        if (qmlNoThreadedRenderer())
-            fancy = false;
-        else if (qmlForceThreadedRenderer())
-            fancy = true;
 
         // Enable fixed animation steps...
         QByteArray fixed = qgetenv("QML_FIXED_ANIMATION_STEP");
@@ -151,9 +143,45 @@ QSGRenderLoop *QSGRenderLoop::instance()
             QUnifiedTimer::instance(true)->setConsistentTiming(true);
 
         if (!s_instance) {
-            s_instance = fancy
-                    ? (QSGRenderLoop*) new QSGThreadedRenderLoop
-                    : (QSGRenderLoop*) new QSGGuiThreadRenderLoop;
+
+            enum RenderLoopType {
+                BasicRenderLoop,
+                ThreadedRenderLoop,
+                WindowsRenderLoop
+            };
+
+            RenderLoopType loopType = BasicRenderLoop;
+
+#ifdef Q_OS_WIN
+            loopType = WindowsRenderLoop;
+#else
+            if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
+                loopType = ThreadedRenderLoop;
+#endif
+            if (qmlNoThreadedRenderer())
+                loopType = BasicRenderLoop;
+            else if (qmlForceThreadedRenderer())
+                loopType = ThreadedRenderLoop;
+
+            const QByteArray loopName = qgetenv("QSG_RENDER_LOOP");
+            if (loopName == QByteArrayLiteral("windows"))
+                loopType = WindowsRenderLoop;
+            else if (loopName == QByteArrayLiteral("basic"))
+                loopType = BasicRenderLoop;
+            else if (loopName == QByteArrayLiteral("threaded"))
+                loopType = ThreadedRenderLoop;
+
+            switch (loopType) {
+            case ThreadedRenderLoop:
+                s_instance = new QSGThreadedRenderLoop();
+                break;
+            case WindowsRenderLoop:
+                s_instance = new QSGWindowsRenderLoop();
+                break;
+            default:
+                s_instance = new QSGGuiThreadRenderLoop();
+                break;
+            }
         }
     }
     return s_instance;
diff --git a/src/quick/scenegraph/qsgwindowsrenderloop.cpp b/src/quick/scenegraph/qsgwindowsrenderloop.cpp
new file mode 100644 (file)
index 0000000..3e21af6
--- /dev/null
@@ -0,0 +1,435 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgwindowsrenderloop_p.h"
+
+#include <QtCore/QCoreApplication>
+
+#include <QtGui/QScreen>
+#include <QtGui/QGuiApplication>
+
+#include <QtQuick/private/qsgcontext_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+
+#include <QtQuick/QQuickWindow>
+
+QT_BEGIN_NAMESPACE
+
+extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
+
+// #define QSG_RENDER_LOOP_DEBUG
+
+#ifdef QSG_RENDER_LOOP_DEBUG
+static QElapsedTimer qsg_debug_timer;
+#  define RLDEBUG(x) printf("(%6d) %s : %4d - %s\n", (int) qsg_debug_timer.elapsed(), __FILE__, __LINE__, x)
+#else
+#  define RLDEBUG(x)
+#endif
+
+#ifndef QSG_NO_RENDER_TIMING
+static bool qsg_render_timing = !qgetenv("QSG_RENDER_TIMING").isEmpty();
+static QElapsedTimer qsg_render_timer;
+#define QSG_RENDER_TIMING_SAMPLE(sampleName) int sampleName = 0; if (qsg_render_timing) sampleName = qsg_render_timer.elapsed()
+#else
+#define QSG_RENDER_TIMING_SAMPLE(sampleName)
+#endif
+
+
+QSGWindowsRenderLoop::QSGWindowsRenderLoop()
+    : m_gl(0)
+    , m_sg(QSGContext::createDefaultContext())
+    , m_updateTimer(0)
+    , m_animationTimer(0)
+{
+#ifdef QSG_RENDER_LOOP_DEBUG
+    qsg_debug_timer.start();
+#endif
+
+    m_animationDriver = m_sg->createAnimationDriver(m_sg);
+    m_animationDriver->install();
+
+    connect(m_animationDriver, SIGNAL(started()), this, SLOT(started()));
+    connect(m_animationDriver, SIGNAL(stopped()), this, SLOT(stopped()));
+
+    m_vsyncDelta = 1000 / QGuiApplication::primaryScreen()->refreshRate();
+    if (m_vsyncDelta <= 0)
+        m_vsyncDelta = 16;
+
+    RLDEBUG("Windows Render Loop created");
+
+#ifndef QSG_NO_RENDER_TIMIMG
+    qsg_render_timer.start();
+#endif
+}
+
+QSGWindowsRenderLoop::WindowData *QSGWindowsRenderLoop::windowData(QQuickWindow *window)
+{
+    for (int i=0; i<m_windows.size(); ++i) {
+        WindowData &wd = m_windows[i];
+        if (wd.window == window)
+            return &wd;
+    }
+    return 0;
+}
+
+void QSGWindowsRenderLoop::maybePostUpdateTimer()
+{
+    if (!m_updateTimer) {
+        RLDEBUG(" - posting event");
+        m_updateTimer = startTimer(m_vsyncDelta / 3);
+    }
+}
+
+/*
+ * If no windows are showing, start ticking animations using a timer,
+ * otherwise, start rendering
+ */
+void QSGWindowsRenderLoop::started()
+{
+    RLDEBUG("Animations started...");
+    if (!anyoneShowing()) {
+        if (m_animationTimer == 0) {
+            RLDEBUG(" - starting non-visual animation timer");
+            m_animationTimer = startTimer(m_vsyncDelta);
+        }
+    } else {
+        maybePostUpdateTimer();
+    }
+}
+
+void QSGWindowsRenderLoop::stopped()
+{
+    RLDEBUG("Animations stopped...");
+    if (m_animationTimer) {
+        RLDEBUG(" - stopping non-visual animation timer");
+        killTimer(m_animationTimer);
+        m_animationTimer = 0;
+    }
+}
+
+void QSGWindowsRenderLoop::show(QQuickWindow *window)
+{
+    RLDEBUG("show");
+    if (windowData(window) != 0)
+        return;
+
+    // This happens before the platform window is shown, but after
+    // it is created. Creating the GL context takes a lot of time
+    // (hundreds of milliseconds) and will prevent us from rendering
+    // the first frame in time for the initial show on screen.
+    // By preparing the GL context here, it is feasible (if the app
+    // is quick enough) to have a perfect first frame.
+    if (!m_gl) {
+        QSG_RENDER_TIMING_SAMPLE(time_start);
+
+        RLDEBUG(" - creating GL context");
+        m_gl = new QOpenGLContext();
+        m_gl->setFormat(window->requestedFormat());
+        m_gl->create();
+        QSG_RENDER_TIMING_SAMPLE(time_created);
+        RLDEBUG(" - making current");
+        m_gl->makeCurrent(window);
+        RLDEBUG(" - initializing SG");
+        QSG_RENDER_TIMING_SAMPLE(time_current);
+        m_sg->initialize(m_gl);
+
+#ifndef QSG_NO_RENDER_TIMING
+        if (qsg_render_timing) {
+            qDebug("WindowsRenderLoop: GL=%d ms, makeCurrent=%d ms, SG=%d ms",
+                   int(time_created - time_start),
+                   int(time_current - time_created),
+                   int(qsg_render_timer.elapsed() - time_current));
+        }
+#endif
+
+    }
+
+    WindowData data;
+    data.window = window;
+    data.pendingUpdate = false;
+    m_windows << data;
+
+    RLDEBUG(" - done with show");
+}
+
+void QSGWindowsRenderLoop::hide(QQuickWindow *window)
+{
+    RLDEBUG("hide");
+
+    for (int i=0; i<m_windows.size(); ++i) {
+        if (m_windows.at(i).window == window) {
+            m_windows.removeAt(i);
+            break;
+        }
+    }
+
+    // The expose event is queued while hide is sent synchronously, so
+    // the value might not be updated yet. (plus that the windows plugin
+    // sends exposed=true when it goes to hidden, so it is doubly broken)
+    // The check is made here, after the removal from m_windows, so
+    // anyoneShowing will report the right value.
+    if (window->isExposed())
+        handleObscurity();
+
+    QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+    cd->cleanupNodesOnShutdown();
+
+    // If this is the last tracked window, check for persistent SG and GL and
+    // potentially clean up.
+    if (m_windows.size() == 0) {
+        if (!cd->persistentSceneGraph) {
+            m_sg->invalidate();
+            if (!cd->persistentGLContext) {
+                delete m_gl;
+                m_gl = 0;
+            }
+        }
+    }
+}
+
+void QSGWindowsRenderLoop::windowDestroyed(QQuickWindow *window)
+{
+    RLDEBUG("windowDestroyed");
+    hide(window);
+
+    // If this is the last tracked window, clean up SG and GL.
+    if (m_windows.size() == 0) {
+        m_sg->invalidate();
+        delete m_gl;
+        m_gl = 0;
+    }
+}
+
+bool QSGWindowsRenderLoop::anyoneShowing() const
+{
+    foreach (const WindowData &wd, m_windows)
+        if (wd.window->isExposed() && wd.window->size().isValid())
+            return true;
+    return false;
+}
+
+void QSGWindowsRenderLoop::exposureChanged(QQuickWindow *window)
+{
+
+    if (windowData(window) == 0)
+        return;
+
+    if (window->isExposed()) {
+
+        // Stop non-visual animation timer as we now have a window rendering
+        if (m_animationTimer && anyoneShowing()) {
+            RLDEBUG(" - stopping non-visual animation timer");
+            killTimer(m_animationTimer);
+            m_animationTimer = 0;
+        }
+
+        RLDEBUG("exposureChanged - exposed");
+        WindowData *wd = windowData(window);
+        wd->pendingUpdate = true;
+
+        // If we have a pending timer and we get an expose, we need to stop it.
+        // Otherwise we get two frames and two animation ticks in the same time-interval.
+        if (m_updateTimer) {
+            RLDEBUG(" - killing pending update timer");
+            killTimer(m_updateTimer);
+            m_updateTimer = 0;
+        }
+        render();
+    } else {
+        handleObscurity();
+    }
+}
+
+void QSGWindowsRenderLoop::handleObscurity()
+{
+    RLDEBUG("handleObscurity");
+    // Potentially start the non-visual animation timer if nobody is rendering
+    if (m_animationDriver->isRunning() && !anyoneShowing() && !m_animationTimer) {
+        RLDEBUG(" - starting non-visual animation timer");
+        m_animationTimer = startTimer(m_vsyncDelta);
+    }
+}
+
+QImage QSGWindowsRenderLoop::grab(QQuickWindow *window)
+{
+    RLDEBUG("grab");
+    if (!m_gl)
+        return QImage();
+
+    m_gl->makeCurrent(window);
+
+    QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+    d->polishItems();
+    d->syncSceneGraph();
+    d->renderSceneGraph(window->size());
+
+    QImage image = qt_gl_read_framebuffer(window->size(), false, false);
+    return image;
+}
+
+void QSGWindowsRenderLoop::update(QQuickWindow *window)
+{
+    RLDEBUG("update");
+    maybeUpdate(window);
+}
+
+void QSGWindowsRenderLoop::maybeUpdate(QQuickWindow *window)
+{
+    RLDEBUG("maybeUpdate");
+
+    WindowData *wd = windowData(window);
+    if (!wd || !anyoneShowing())
+        return;
+
+    wd->pendingUpdate = true;
+    maybePostUpdateTimer();
+}
+
+bool QSGWindowsRenderLoop::event(QEvent *event)
+{
+    switch (event->type()) {
+    case QEvent::Timer: {
+        QTimerEvent *te = static_cast<QTimerEvent *>(event);
+        if (te->timerId() == m_animationTimer) {
+            RLDEBUG("event : animation tick while nothing is showing");
+            m_animationDriver->advance();
+        } else if (te->timerId() == m_updateTimer) {
+            RLDEBUG("event : update");
+            killTimer(m_updateTimer);
+            m_updateTimer = 0;
+            render();
+        }
+        return true; }
+    default:
+        break;
+    }
+
+    return QObject::event(event);
+}
+
+/*
+ * Go through all windows we control and render them in turn.
+ * Then tick animations if active.
+ */
+void QSGWindowsRenderLoop::render()
+{
+    RLDEBUG("render");
+    foreach (const WindowData &wd, m_windows) {
+        if (wd.pendingUpdate) {
+            const_cast<WindowData &>(wd).pendingUpdate = false;
+            renderWindow(wd.window);
+        }
+    }
+
+    if (m_animationDriver->isRunning()) {
+        RLDEBUG("advancing animations");
+        QSG_RENDER_TIMING_SAMPLE(time_start);
+        m_animationDriver->advance();
+        RLDEBUG("animations advanced");
+
+#ifndef QSG_NO_RENDER_TIMING
+        if (qsg_render_timing) {
+            qDebug("WindowsRenderLoop: animations=%d ms",
+                   int(qsg_render_timer.elapsed() - time_start));
+        }
+#endif
+
+        // It is not given that animations triggered another maybeUpdate()
+        // and thus another render pass, so to keep things running,
+        // make sure there is another frame pending.
+        maybePostUpdateTimer();
+    }
+}
+
+/*
+ * Render the contents of this window. First polish, then sync, render
+ * then finally swap.
+ *
+ * Note: This render function does not implement aborting
+ * the render call when sync step results in no scene graph changes,
+ * like the threaded renderer does.
+ */
+void QSGWindowsRenderLoop::renderWindow(QQuickWindow *window)
+{
+    RLDEBUG("renderWindow");
+    QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+
+    if (!d->isRenderable())
+        return;
+
+    if (!m_gl->makeCurrent(window))
+        return;
+
+    QSG_RENDER_TIMING_SAMPLE(time_start);
+
+    RLDEBUG(" - polishing");
+    d->polishItems();
+    QSG_RENDER_TIMING_SAMPLE(time_polished);
+
+    RLDEBUG(" - syncing");
+    d->syncSceneGraph();
+    QSG_RENDER_TIMING_SAMPLE(time_synced);
+
+    RLDEBUG(" - rendering");
+    d->renderSceneGraph(window->size());
+    QSG_RENDER_TIMING_SAMPLE(time_rendered);
+
+    RLDEBUG(" - swapping");
+    m_gl->swapBuffers(window);
+    QSG_RENDER_TIMING_SAMPLE(time_swapped);
+
+    RLDEBUG(" - frameDone");
+    d->fireFrameSwapped();
+
+#ifndef QSG_NO_RENDER_TIMING
+        if (qsg_render_timing) {
+            qDebug("WindowsRenderLoop(t=%d): window=%p, polish=%d ms, sync=%d ms, render=%d ms, swap=%d ms",
+                   int(qsg_render_timer.elapsed()),
+                   window,
+                   int(time_polished - time_start),
+                   int(time_synced - time_polished),
+                   int(time_rendered - time_synced),
+                   int(time_swapped - time_rendered));
+        }
+#endif
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgwindowsrenderloop_p.h b/src/quick/scenegraph/qsgwindowsrenderloop_p.h
new file mode 100644 (file)
index 0000000..dc3a409
--- /dev/null
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSGWINDOWSRENDERLOOP_P_H
+#define QSGWINDOWSRENDERLOOP_P_H
+
+#include <QtCore/QObject>
+#include <QtCore/QElapsedTimer>
+
+#include <QtGui/QOpenGLContext>
+
+#include "qsgrenderloop_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGWindowsRenderLoop : public QObject, public QSGRenderLoop
+{
+    Q_OBJECT
+public:
+    explicit QSGWindowsRenderLoop();
+
+    void show(QQuickWindow *window);
+    void hide(QQuickWindow *window);
+
+    void windowDestroyed(QQuickWindow *window);
+
+    void exposureChanged(QQuickWindow *window);
+    QImage grab(QQuickWindow *window);
+
+    void update(QQuickWindow *window);
+    void maybeUpdate(QQuickWindow *window);
+
+    QAnimationDriver *animationDriver() const { return m_animationDriver; }
+
+    QSGContext *sceneGraphContext() const { return m_sg; }
+
+    void releaseResources(QQuickWindow *) { }
+
+    void render();
+    void renderWindow(QQuickWindow *window);
+
+    void resize(QQuickWindow *, const QSize &) { }
+
+    bool event(QEvent *event);
+
+public slots:
+    void started();
+    void stopped();
+
+private:
+    struct WindowData {
+        QQuickWindow *window;
+        bool pendingUpdate;
+    };
+
+    void handleObscurity();
+    void maybePostUpdateTimer();
+    bool anyoneShowing() const;
+    WindowData *windowData(QQuickWindow *window);
+
+    QList<WindowData> m_windows;
+
+    QOpenGLContext *m_gl;
+    QSGContext *m_sg;
+
+    QAnimationDriver *m_animationDriver;
+
+    int m_updateTimer;
+    int m_animationTimer;
+
+    int m_vsyncDelta;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGWINDOWSRENDERLOOP_P_H
index 8c87e23..c002a81 100644 (file)
@@ -51,7 +51,6 @@ SOURCES += \
     $$PWD/util/qsgpainternode.cpp \
     $$PWD/util/qsgdistancefieldutil.cpp
 
-
 # QML / Adaptations API
 HEADERS += \
     $$PWD/qsgadaptationlayer_p.h \
@@ -67,8 +66,8 @@ HEADERS += \
     $$PWD/qsgflashnode_p.h \
     $$PWD/qsgshareddistancefieldglyphcache_p.h \
     $$PWD/qsgrenderloop_p.h \
-    $$PWD/qsgthreadedrenderloop_p.h
-
+    $$PWD/qsgthreadedrenderloop_p.h \
+    $$PWD/qsgwindowsrenderloop_p.h
 
 SOURCES += \
     $$PWD/qsgadaptationlayer.cpp \
@@ -84,12 +83,5 @@ SOURCES += \
     $$PWD/qsgflashnode.cpp \
     $$PWD/qsgshareddistancefieldglyphcache.cpp \
     $$PWD/qsgrenderloop.cpp \
-    $$PWD/qsgthreadedrenderloop.cpp
-
-
-
-
-
-
-
-
+    $$PWD/qsgthreadedrenderloop.cpp \
+    $$PWD/qsgwindowsrenderloop.cpp