Merge pull request #6613 from jherico/threaded_qml

Moving QML rendering off the main thread
This commit is contained in:
Brad Hefta-Gaub 2015-12-11 11:43:04 -08:00
commit 30fd96b509
6 changed files with 105 additions and 106 deletions

View file

@ -8,16 +8,18 @@
#include "OffscreenQmlSurface.h" #include "OffscreenQmlSurface.h"
#include "OglplusHelpers.h" #include "OglplusHelpers.h"
#include <QWidget> #include <QtWidgets/QWidget>
#include <QtQml> #include <QtQml/QtQml>
#include <QQmlEngine> #include <QtQml/QQmlEngine>
#include <QQmlComponent> #include <QtQml/QQmlComponent>
#include <QQuickItem> #include <QtQuick/QQuickItem>
#include <QQuickWindow> #include <QtQuick/QQuickWindow>
#include <QQuickRenderControl> #include <QtQuick/QQuickRenderControl>
#include <QWaitCondition> #include <QtCore/QWaitCondition>
#include <QMutex> #include <QtCore/QMutex>
#include <QtGui/QOpenGLContext>
#include <shared/NsightHelpers.h>
#include <PerfStat.h> #include <PerfStat.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
@ -25,8 +27,6 @@
#include "GLEscrow.h" #include "GLEscrow.h"
#include "OffscreenGLCanvas.h" #include "OffscreenGLCanvas.h"
// FIXME move to threaded rendering with Qt 5.5
//#define QML_THREADED
// Time between receiving a request to render the offscreen UI actually triggering // Time between receiving a request to render the offscreen UI actually triggering
// the render. Could possibly be increased depending on the framerate we expect to // the render. Could possibly be increased depending on the framerate we expect to
@ -56,13 +56,11 @@ private:
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
#ifdef QML_THREADED
static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1); static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1);
static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2); static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2);
static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3); static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3);
static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4); static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5); static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5);
#endif
class OffscreenQmlRenderer : public OffscreenGLCanvas { class OffscreenQmlRenderer : public OffscreenGLCanvas {
friend class OffscreenQmlSurface; friend class OffscreenQmlSurface;
@ -70,22 +68,30 @@ public:
OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) { OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
OffscreenGLCanvas::create(shareContext); OffscreenGLCanvas::create(shareContext);
#ifdef QML_THREADED
_renderControl = new QMyQuickRenderControl();
// Create a QQuickWindow that is associated with out render control. Note that this
// window never gets created or shown, meaning that it will never get an underlying
// native (platform) window.
QQuickWindow::setDefaultAlphaBuffer(true);
// Weirdness... QQuickWindow NEEDS to be created on the rendering thread, or it will refuse to render
// because it retains an internal 'context' object that retains the thread it was created on,
// regardless of whether you later move it to another thread.
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
// Qt 5.5 // Qt 5.5
_renderControl->prepareThread(_renderThread); _renderControl->prepareThread(&_thread);
_context->moveToThread(&_thread); _context->moveToThread(&_thread);
moveToThread(&_thread); moveToThread(&_thread);
_thread.setObjectName("QML Thread"); _thread.setObjectName("QML Thread");
_thread.start(); _thread.start();
post(INIT); post(INIT);
#else
init();
#endif
} }
#ifdef QML_THREADED bool event(QEvent *e) {
bool event(QEvent *e)
{
switch (int(e->type())) { switch (int(e->type())) {
case INIT: case INIT:
{ {
@ -120,7 +126,6 @@ public:
QCoreApplication::postEvent(this, new QEvent(type)); QCoreApplication::postEvent(this, new QEvent(type));
} }
#endif
private: private:
@ -143,27 +148,9 @@ private:
void init() { void init() {
_renderControl = new QMyQuickRenderControl();
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender); connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate); connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
// Create a QQuickWindow that is associated with out render control. Note that this
// window never gets created or shown, meaning that it will never get an underlying
// native (platform) window.
QQuickWindow::setDefaultAlphaBuffer(true);
// Weirdness... QQuickWindow NEEDS to be created on the rendering thread, or it will refuse to render
// because it retains an internal 'context' object that retains the thread it was created on,
// regardless of whether you later move it to another thread.
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
#ifdef QML_THREADED
// However, because we want to use synchronous events with the quickwindow, we need to move it back to the main
// thread after it's created.
_quickWindow->moveToThread(qApp->thread());
#endif
if (!makeCurrent()) { if (!makeCurrent()) {
qWarning("Failed to make context current on render thread"); qWarning("Failed to make context current on render thread");
return; return;
@ -189,17 +176,15 @@ private:
doneCurrent(); doneCurrent();
#ifdef QML_THREADED
_context->moveToThread(QCoreApplication::instance()->thread()); _context->moveToThread(QCoreApplication::instance()->thread());
_cond.wakeOne(); _cond.wakeOne();
#endif
} }
void resize(const QSize& newSize) { void resize() {
// Update our members // Update our members
if (_quickWindow) { if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), newSize)); _quickWindow->setGeometry(QRect(QPoint(), _newSize));
_quickWindow->contentItem()->setSize(newSize); _quickWindow->contentItem()->setSize(_newSize);
} }
// Qt bug in 5.4 forces this check of pixel ratio, // Qt bug in 5.4 forces this check of pixel ratio,
@ -209,7 +194,7 @@ private:
pixelRatio = _renderControl->_renderWindow->devicePixelRatio(); pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
} }
uvec2 newOffscreenSize = toGlm(newSize * pixelRatio); uvec2 newOffscreenSize = toGlm(_newSize * pixelRatio);
_textures.setSize(newOffscreenSize); _textures.setSize(newOffscreenSize);
if (newOffscreenSize == _size) { if (newOffscreenSize == _size) {
return; return;
@ -222,7 +207,7 @@ private:
return; return;
} }
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; qDebug() << "Offscreen UI resizing to " << _newSize.width() << "x" << _newSize.height() << " with pixel ratio " << pixelRatio;
setupFbo(); setupFbo();
doneCurrent(); doneCurrent();
} }
@ -237,54 +222,44 @@ private:
return; return;
} }
//Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _size);
//Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _textures._size);
_renderControl->sync(); _renderControl->sync();
#ifdef QML_THREADED
_cond.wakeOne(); _cond.wakeOne();
lock->unlock(); lock->unlock();
#endif
using namespace oglplus; using namespace oglplus;
_quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y)); _quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y));
TexturePtr texture = _textures.getNextTexture();
_fbo->Bind(Framebuffer::Target::Draw);
_fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
_fbo->Complete(Framebuffer::Target::Draw);
//Context::Clear().ColorBuffer();
{ {
_renderControl->render(); PROFILE_RANGE("qml_render")
// FIXME The web browsers seem to be leaving GL in an error state. TexturePtr texture = _textures.getNextTexture();
// Need a debug context with sync logging to figure out why. _fbo->Bind(Framebuffer::Target::Draw);
// for now just clear the errors _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
glGetError(); _fbo->Complete(Framebuffer::Target::Draw);
{
_renderControl->render();
// FIXME The web browsers seem to be leaving GL in an error state.
// Need a debug context with sync logging to figure out why.
// for now just clear the errors
glGetError();
}
// FIXME probably unecessary
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
_quickWindow->resetOpenGLState();
_escrow.submit(GetName(*texture));
} }
// FIXME probably unecessary
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
_quickWindow->resetOpenGLState();
_escrow.submit(GetName(*texture));
_lastRenderTime = usecTimestampNow(); _lastRenderTime = usecTimestampNow();
} }
void aboutToQuit() { void aboutToQuit() {
#ifdef QML_THREADED
QMutexLocker lock(&_quitMutex); QMutexLocker lock(&_quitMutex);
_quit = true; _quit = true;
#endif
} }
void stop() { void stop() {
#ifdef QML_THREADED QMutexLocker lock(&_mutex);
QMutexLocker lock(&_quitMutex);
post(STOP); post(STOP);
_cond.wait(&_mutex); _cond.wait(&_mutex);
#else
cleanup();
#endif
} }
bool allowNewFrame(uint8_t fps) { bool allowNewFrame(uint8_t fps) {
@ -297,13 +272,12 @@ private:
QQuickWindow* _quickWindow{ nullptr }; QQuickWindow* _quickWindow{ nullptr };
QMyQuickRenderControl* _renderControl{ nullptr }; QMyQuickRenderControl* _renderControl{ nullptr };
#ifdef QML_THREADED
QThread _thread; QThread _thread;
QMutex _mutex; QMutex _mutex;
QWaitCondition _cond; QWaitCondition _cond;
QMutex _quitMutex; QMutex _quitMutex;
#endif
QSize _newSize;
bool _quit; bool _quit;
FramebufferPtr _fbo; FramebufferPtr _fbo;
RenderbufferPtr _depthStencil; RenderbufferPtr _depthStencil;
@ -346,9 +320,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
} }
void OffscreenQmlSurface::resize(const QSize& newSize) { void OffscreenQmlSurface::resize(const QSize& newSize) {
#ifdef QML_THREADED
QMutexLocker _locker(&(_renderer->_mutex));
#endif
if (!_renderer || !_renderer->_quickWindow) { if (!_renderer || !_renderer->_quickWindow) {
QSize currentSize = _renderer->_quickWindow->geometry().size(); QSize currentSize = _renderer->_quickWindow->geometry().size();
if (newSize == currentSize) { if (newSize == currentSize) {
@ -362,11 +334,12 @@ void OffscreenQmlSurface::resize(const QSize& newSize) {
_rootItem->setSize(newSize); _rootItem->setSize(newSize);
} }
#ifdef QML_THREADED {
QMutexLocker _locker(&(_renderer->_mutex));
_renderer->_newSize = newSize;
}
_renderer->post(RESIZE); _renderer->post(RESIZE);
#else
_renderer->resize(newSize);
#endif
} }
QQuickItem* OffscreenQmlSurface::getRootItem() { QQuickItem* OffscreenQmlSurface::getRootItem() {
@ -466,11 +439,7 @@ void OffscreenQmlSurface::updateQuick() {
} }
if (_render) { if (_render) {
#ifdef QML_THREADED
_renderer->post(RENDER); _renderer->post(RENDER);
#else
_renderer->render(nullptr);
#endif
_render = false; _render = false;
} }

View file

@ -16,13 +16,6 @@
#if defined(NSIGHT_FOUND) #if defined(NSIGHT_FOUND)
#include "nvToolsExt.h" #include "nvToolsExt.h"
ProfileRange::ProfileRange(const char *name) {
nvtxRangePush(name);
}
ProfileRange::~ProfileRange() {
nvtxRangePop();
}
ProfileRangeBatch::ProfileRangeBatch(gpu::Batch& batch, const char *name) : _batch(batch) { ProfileRangeBatch::ProfileRangeBatch(gpu::Batch& batch, const char *name) : _batch(batch) {
_batch.pushProfileRange(name); _batch.pushProfileRange(name);
} }

View file

@ -15,6 +15,8 @@
#include <mutex> #include <mutex>
#include <functional> #include <functional>
#include <shared/NsightHelpers.h>
#include "Framebuffer.h" #include "Framebuffer.h"
#include "Pipeline.h" #include "Pipeline.h"
#include "Query.h" #include "Query.h"
@ -22,18 +24,6 @@
#include "Texture.h" #include "Texture.h"
#include "Transform.h" #include "Transform.h"
#if defined(NSIGHT_FOUND)
class ProfileRange {
public:
ProfileRange(const char *name);
~ProfileRange();
};
#define PROFILE_RANGE(name) ProfileRange profileRangeThis(name);
#else
#define PROFILE_RANGE(name)
#endif
class QDebug; class QDebug;
namespace gpu { namespace gpu {

View file

@ -4,3 +4,4 @@ set(TARGET_NAME shared)
setup_hifi_library(Gui Network Script Widgets) setup_hifi_library(Gui Network Script Widgets)
target_zlib() target_zlib()
target_nsight()

View file

@ -0,0 +1,22 @@
//
// Created by Bradley Austin Davis on 2015/12/10
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "NsightHelpers.h"
#if defined(NSIGHT_FOUND)
#include "nvToolsExt.h"
ProfileRange::ProfileRange(const char *name) {
nvtxRangePush(name);
}
ProfileRange::~ProfileRange() {
nvtxRangePop();
}
#endif

View file

@ -0,0 +1,24 @@
//
// Created by Bradley Austin Davis on 2015/12/10
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_gl_NsightHelpers_h
#define hifi_gl_NsightHelpers_h
#if defined(NSIGHT_FOUND)
class ProfileRange {
public:
ProfileRange(const char *name);
~ProfileRange();
};
#define PROFILE_RANGE(name) ProfileRange profileRangeThis(name);
#else
#define PROFILE_RANGE(name)
#endif
#endif