mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 14:47:41 +02:00
Move qml rendering to defined thread
This commit is contained in:
parent
b1e020d3fd
commit
f987da4c43
2 changed files with 260 additions and 253 deletions
|
@ -47,11 +47,10 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWindow* _renderWindow{ nullptr };
|
QWindow* _renderWindow{ nullptr };
|
||||||
friend class OffscreenQmlRenderer;
|
friend class OffscreenQmlRenderThread;
|
||||||
friend class OffscreenQmlSurface;
|
friend class OffscreenQmlSurface;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
||||||
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
||||||
|
|
||||||
|
@ -60,264 +59,274 @@ 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);
|
||||||
|
|
||||||
class OffscreenQmlRenderer : public OffscreenGLCanvas {
|
class OffscreenQmlRenderThread : public QThread {
|
||||||
friend class OffscreenQmlSurface;
|
|
||||||
public:
|
public:
|
||||||
|
OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext);
|
||||||
|
|
||||||
OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
|
virtual void run() override;
|
||||||
OffscreenGLCanvas::create(shareContext);
|
virtual bool event(QEvent *e) override;
|
||||||
|
|
||||||
_renderControl = new QMyQuickRenderControl();
|
protected:
|
||||||
|
class Queue : public QQueue<QEvent*> {
|
||||||
|
public:
|
||||||
|
void add(QEvent::Type type);
|
||||||
|
QEvent* take();
|
||||||
|
|
||||||
// Create a QQuickWindow that is associated with out render control. Note that this
|
private:
|
||||||
// window never gets created or shown, meaning that it will never get an underlying
|
QMutex _mutex;
|
||||||
// native (platform) window.
|
QWaitCondition _waitCondition;
|
||||||
QQuickWindow::setDefaultAlphaBuffer(true);
|
bool _isWaiting{ false };
|
||||||
// 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
|
|
||||||
_renderControl->prepareThread(&_thread);
|
|
||||||
getContextObject()->moveToThread(&_thread);
|
|
||||||
moveToThread(&_thread);
|
|
||||||
_thread.setObjectName("QML Thread");
|
|
||||||
_thread.start();
|
|
||||||
post(INIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool event(QEvent *e) {
|
|
||||||
switch (int(e->type())) {
|
|
||||||
case INIT:
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&_mutex);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
case RENDER:
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&_mutex);
|
|
||||||
render(&lock);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
case RESIZE:
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&_mutex);
|
|
||||||
resize();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
case STOP:
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&_mutex);
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return QObject::event(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void post(const QEvent::Type& type) {
|
|
||||||
QCoreApplication::postEvent(this, new QEvent(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
friend class OffscreenQmlSurface;
|
||||||
|
Queue _queue;
|
||||||
|
QMutex _mutex;
|
||||||
|
QWaitCondition _waitCondition;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Event-driven methods
|
||||||
|
void init();
|
||||||
|
void render();
|
||||||
|
void resize();
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
void setupFbo() {
|
// Helper methods
|
||||||
using namespace oglplus;
|
void setupFbo();
|
||||||
_textures.setSize(_size);
|
bool allowNewFrame(uint8_t fps);
|
||||||
_depthStencil.reset(new Renderbuffer());
|
|
||||||
Context::Bound(Renderbuffer::Target::Renderbuffer, *_depthStencil)
|
|
||||||
.Storage(
|
|
||||||
PixelDataInternalFormat::DepthComponent,
|
|
||||||
_size.x, _size.y);
|
|
||||||
|
|
||||||
_fbo.reset(new Framebuffer());
|
|
||||||
_fbo->Bind(Framebuffer::Target::Draw);
|
|
||||||
_fbo->AttachRenderbuffer(Framebuffer::Target::Draw,
|
|
||||||
FramebufferAttachment::Depth, *_depthStencil);
|
|
||||||
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void init() {
|
|
||||||
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
|
|
||||||
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
|
|
||||||
|
|
||||||
if (!makeCurrent()) {
|
|
||||||
qWarning("Failed to make context current on render thread");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_renderControl->initialize(_context);
|
|
||||||
setupFbo();
|
|
||||||
_escrow.setRecycler([this](GLuint texture){
|
|
||||||
_textures.recycleTexture(texture);
|
|
||||||
});
|
|
||||||
doneCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanup() {
|
|
||||||
if (!makeCurrent()) {
|
|
||||||
qFatal("Failed to make context current on render thread");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_renderControl->invalidate();
|
|
||||||
|
|
||||||
_fbo.reset();
|
|
||||||
_depthStencil.reset();
|
|
||||||
_textures.clear();
|
|
||||||
|
|
||||||
doneCurrent();
|
|
||||||
|
|
||||||
getContextObject()->moveToThread(QCoreApplication::instance()->thread());
|
|
||||||
_thread.quit();
|
|
||||||
_cond.wakeOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
void resize() {
|
|
||||||
// Update our members
|
|
||||||
if (_quickWindow) {
|
|
||||||
_quickWindow->setGeometry(QRect(QPoint(), _newSize));
|
|
||||||
_quickWindow->contentItem()->setSize(_newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Qt bug in 5.4 forces this check of pixel ratio,
|
|
||||||
// even though we're rendering offscreen.
|
|
||||||
qreal pixelRatio = 1.0;
|
|
||||||
if (_renderControl && _renderControl->_renderWindow) {
|
|
||||||
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
|
|
||||||
}
|
|
||||||
|
|
||||||
uvec2 newOffscreenSize = toGlm(_newSize * pixelRatio);
|
|
||||||
_textures.setSize(newOffscreenSize);
|
|
||||||
if (newOffscreenSize == _size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_size = newOffscreenSize;
|
|
||||||
|
|
||||||
// Clear out any fbos with the old size
|
|
||||||
if (!makeCurrent()) {
|
|
||||||
qWarning("Failed to make context current on render thread");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Offscreen UI resizing to " << _newSize.width() << "x" << _newSize.height() << " with pixel ratio " << pixelRatio;
|
|
||||||
setupFbo();
|
|
||||||
doneCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(QMutexLocker *lock) {
|
|
||||||
if (_surface->_paused) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!makeCurrent()) {
|
|
||||||
qWarning("Failed to make context current on render thread");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderControl->sync();
|
|
||||||
_cond.wakeOne();
|
|
||||||
lock->unlock();
|
|
||||||
|
|
||||||
using namespace oglplus;
|
|
||||||
|
|
||||||
_quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y));
|
|
||||||
|
|
||||||
try {
|
|
||||||
PROFILE_RANGE("qml_render")
|
|
||||||
TexturePtr texture = _textures.getNextTexture();
|
|
||||||
_fbo->Bind(Framebuffer::Target::Draw);
|
|
||||||
_fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
|
|
||||||
_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));
|
|
||||||
_lastRenderTime = usecTimestampNow();
|
|
||||||
} catch (std::runtime_error& error) {
|
|
||||||
qWarning() << "Failed to render QML " << error.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void aboutToQuit() {
|
|
||||||
QMutexLocker lock(&_quitMutex);
|
|
||||||
_quit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 2;
|
|
||||||
void stop() {
|
|
||||||
if (_thread.isRunning()) {
|
|
||||||
qDebug() << "Stopping QML render thread " << _thread.currentThreadId();
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&_mutex);
|
|
||||||
post(STOP);
|
|
||||||
}
|
|
||||||
auto start = usecTimestampNow();
|
|
||||||
auto now = usecTimestampNow();
|
|
||||||
bool shutdownClean = false;
|
|
||||||
while (now - start < (MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) {
|
|
||||||
QMutexLocker lock(&_mutex);
|
|
||||||
if (_cond.wait(&_mutex, MSECS_PER_SECOND)) {
|
|
||||||
shutdownClean = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
now = usecTimestampNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shutdownClean) {
|
|
||||||
qWarning() << "Failed to shut down the QML render thread";
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qDebug() << "QML render thread already completed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool allowNewFrame(uint8_t fps) {
|
|
||||||
auto minRenderInterval = USECS_PER_SECOND / fps;
|
|
||||||
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
|
||||||
return (lastInterval > minRenderInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Rendering members
|
||||||
|
OffscreenGLCanvas _canvas;
|
||||||
OffscreenQmlSurface* _surface{ nullptr };
|
OffscreenQmlSurface* _surface{ nullptr };
|
||||||
QQuickWindow* _quickWindow{ nullptr };
|
QQuickWindow* _quickWindow{ nullptr };
|
||||||
QMyQuickRenderControl* _renderControl{ nullptr };
|
QMyQuickRenderControl* _renderControl{ nullptr };
|
||||||
|
|
||||||
QThread _thread;
|
|
||||||
QMutex _mutex;
|
|
||||||
QWaitCondition _cond;
|
|
||||||
QMutex _quitMutex;
|
|
||||||
|
|
||||||
QSize _newSize;
|
|
||||||
bool _quit;
|
|
||||||
FramebufferPtr _fbo;
|
FramebufferPtr _fbo;
|
||||||
RenderbufferPtr _depthStencil;
|
RenderbufferPtr _depthStencil;
|
||||||
uvec2 _size{ 1920, 1080 };
|
|
||||||
uint64_t _lastRenderTime{ 0 };
|
|
||||||
TextureRecycler _textures;
|
TextureRecycler _textures;
|
||||||
GLTextureEscrow _escrow;
|
GLTextureEscrow _escrow;
|
||||||
|
|
||||||
|
uint64_t _lastRenderTime{ 0 };
|
||||||
|
uvec2 _size{ 1920, 1080 };
|
||||||
|
QSize _newSize;
|
||||||
|
bool _quit{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void OffscreenQmlRenderThread::Queue::add(QEvent::Type type) {
|
||||||
|
QMutexLocker locker(&_mutex);
|
||||||
|
enqueue(new QEvent(type));
|
||||||
|
if (_isWaiting) {
|
||||||
|
_waitCondition.wakeOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QEvent* OffscreenQmlRenderThread::Queue::take() {
|
||||||
|
QMutexLocker locker(&_mutex);
|
||||||
|
if (size() == 0) {
|
||||||
|
_isWaiting = true;
|
||||||
|
_waitCondition.wait(&_mutex);
|
||||||
|
_isWaiting = false;
|
||||||
|
}
|
||||||
|
QEvent* e = dequeue();
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
|
||||||
|
_canvas.create(shareContext);
|
||||||
|
_renderControl = new QMyQuickRenderControl();
|
||||||
|
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||||
|
// Create a QQuickWindow that is associated with our render control.
|
||||||
|
// This window never gets created or shown, meaning that it will never get an underlying native (platform) window.
|
||||||
|
// NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events
|
||||||
|
// NOTE: Must be created on the rendering thread or it will refuse to render,
|
||||||
|
// so we wait until after its ctor to move object/context to this thread.
|
||||||
|
_quickWindow = new QQuickWindow(_renderControl);
|
||||||
|
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
||||||
|
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
||||||
|
|
||||||
|
// We can prepare, but we must wait to start() the thread until after the ctor
|
||||||
|
_renderControl->prepareThread(this);
|
||||||
|
_canvas.getContextObject()->moveToThread(this);
|
||||||
|
moveToThread(this);
|
||||||
|
|
||||||
|
_queue.add(INIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlRenderThread::run() {
|
||||||
|
while (!_quit) {
|
||||||
|
QEvent* e = _queue.take();
|
||||||
|
event(e);
|
||||||
|
delete e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OffscreenQmlRenderThread::event(QEvent *e) {
|
||||||
|
switch (int(e->type())) {
|
||||||
|
case INIT:
|
||||||
|
init();
|
||||||
|
return true;
|
||||||
|
case RENDER:
|
||||||
|
render();
|
||||||
|
return true;
|
||||||
|
case RESIZE:
|
||||||
|
resize();
|
||||||
|
return true;
|
||||||
|
case STOP:
|
||||||
|
cleanup();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return QObject::event(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlRenderThread::setupFbo() {
|
||||||
|
using namespace oglplus;
|
||||||
|
_textures.setSize(_size);
|
||||||
|
_depthStencil.reset(new Renderbuffer());
|
||||||
|
Context::Bound(Renderbuffer::Target::Renderbuffer, *_depthStencil)
|
||||||
|
.Storage(
|
||||||
|
PixelDataInternalFormat::DepthComponent,
|
||||||
|
_size.x, _size.y);
|
||||||
|
|
||||||
|
_fbo.reset(new Framebuffer());
|
||||||
|
_fbo->Bind(Framebuffer::Target::Draw);
|
||||||
|
_fbo->AttachRenderbuffer(Framebuffer::Target::Draw,
|
||||||
|
FramebufferAttachment::Depth, *_depthStencil);
|
||||||
|
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlRenderThread::init() {
|
||||||
|
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
|
||||||
|
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
|
||||||
|
|
||||||
|
if (!_canvas.makeCurrent()) {
|
||||||
|
qWarning("Failed to make context current on render thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_renderControl->initialize(_canvas.getContext());
|
||||||
|
setupFbo();
|
||||||
|
_escrow.setRecycler([this](GLuint texture){
|
||||||
|
_textures.recycleTexture(texture);
|
||||||
|
});
|
||||||
|
_canvas.doneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlRenderThread::cleanup() {
|
||||||
|
if (!_canvas.makeCurrent()) {
|
||||||
|
qFatal("Failed to make context current on render thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_renderControl->invalidate();
|
||||||
|
|
||||||
|
_fbo.reset();
|
||||||
|
_depthStencil.reset();
|
||||||
|
_textures.clear();
|
||||||
|
|
||||||
|
_canvas.doneCurrent();
|
||||||
|
|
||||||
|
_canvas.getContextObject()->moveToThread(QCoreApplication::instance()->thread());
|
||||||
|
|
||||||
|
_quit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlRenderThread::resize() {
|
||||||
|
// Lock _newSize changes
|
||||||
|
QMutexLocker locker(&_mutex);
|
||||||
|
|
||||||
|
// Update our members
|
||||||
|
if (_quickWindow) {
|
||||||
|
_quickWindow->setGeometry(QRect(QPoint(), _newSize));
|
||||||
|
_quickWindow->contentItem()->setSize(_newSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qt bug in 5.4 forces this check of pixel ratio,
|
||||||
|
// even though we're rendering offscreen.
|
||||||
|
qreal pixelRatio = 1.0;
|
||||||
|
if (_renderControl && _renderControl->_renderWindow) {
|
||||||
|
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
|
||||||
|
}
|
||||||
|
|
||||||
|
uvec2 newOffscreenSize = toGlm(_newSize * pixelRatio);
|
||||||
|
_textures.setSize(newOffscreenSize);
|
||||||
|
if (newOffscreenSize == _size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_size = newOffscreenSize;
|
||||||
|
|
||||||
|
// Clear out any fbos with the old size
|
||||||
|
if (!_canvas.makeCurrent()) {
|
||||||
|
qWarning("Failed to make context current on render thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Offscreen UI resizing to " << _newSize.width() << "x" << _newSize.height() << " with pixel ratio " << pixelRatio;
|
||||||
|
|
||||||
|
locker.unlock();
|
||||||
|
|
||||||
|
setupFbo();
|
||||||
|
_canvas.doneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlRenderThread::render() {
|
||||||
|
if (_surface->_paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_canvas.makeCurrent()) {
|
||||||
|
qWarning("Failed to make context current on render thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutexLocker locker(&_mutex);
|
||||||
|
_renderControl->sync();
|
||||||
|
_waitCondition.wakeOne();
|
||||||
|
locker.unlock();
|
||||||
|
|
||||||
|
using namespace oglplus;
|
||||||
|
|
||||||
|
_quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y));
|
||||||
|
|
||||||
|
try {
|
||||||
|
PROFILE_RANGE("qml_render")
|
||||||
|
TexturePtr texture = _textures.getNextTexture();
|
||||||
|
_fbo->Bind(Framebuffer::Target::Draw);
|
||||||
|
_fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
|
||||||
|
_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));
|
||||||
|
_lastRenderTime = usecTimestampNow();
|
||||||
|
} catch (std::runtime_error& error) {
|
||||||
|
qWarning() << "Failed to render QML " << error.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OffscreenQmlRenderThread::allowNewFrame(uint8_t fps) {
|
||||||
|
auto minRenderInterval = USECS_PER_SECOND / fps;
|
||||||
|
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
||||||
|
return (lastInterval > minRenderInterval);
|
||||||
|
}
|
||||||
|
|
||||||
OffscreenQmlSurface::OffscreenQmlSurface() {
|
OffscreenQmlSurface::OffscreenQmlSurface() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 2;
|
||||||
OffscreenQmlSurface::~OffscreenQmlSurface() {
|
OffscreenQmlSurface::~OffscreenQmlSurface() {
|
||||||
QObject::disconnect(&_updateTimer);
|
QObject::disconnect(&_updateTimer);
|
||||||
QObject::disconnect(qApp);
|
QObject::disconnect(qApp);
|
||||||
_renderer->stop();
|
|
||||||
|
qDebug() << "Stopping QML render thread " << _renderer->currentThreadId();
|
||||||
|
_renderer->_queue.add(STOP);
|
||||||
|
if (!_renderer->wait(MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) {
|
||||||
|
qWarning() << "Failed to shut down the QML render thread";
|
||||||
|
}
|
||||||
|
|
||||||
delete _rootItem;
|
delete _rootItem;
|
||||||
delete _renderer;
|
delete _renderer;
|
||||||
delete _qmlComponent;
|
delete _qmlComponent;
|
||||||
|
@ -326,15 +335,16 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
|
||||||
|
|
||||||
void OffscreenQmlSurface::onAboutToQuit() {
|
void OffscreenQmlSurface::onAboutToQuit() {
|
||||||
QObject::disconnect(&_updateTimer);
|
QObject::disconnect(&_updateTimer);
|
||||||
// Disconnecting the update timer is insufficient, since the renderer
|
|
||||||
// may attempting to render already, so we need to explicitly tell the renderer
|
|
||||||
// to stop
|
|
||||||
_renderer->aboutToQuit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||||
_renderer = new OffscreenQmlRenderer(this, shareContext);
|
_renderer = new OffscreenQmlRenderThread(this, shareContext);
|
||||||
|
_renderer->moveToThread(_renderer);
|
||||||
|
_renderer->setObjectName("QML Renderer Thread");
|
||||||
|
_renderer->start();
|
||||||
|
|
||||||
_renderer->_renderControl->_renderWindow = _proxyWindow;
|
_renderer->_renderControl->_renderWindow = _proxyWindow;
|
||||||
|
|
||||||
// Create a QML engine.
|
// Create a QML engine.
|
||||||
_qmlEngine = new QQmlEngine;
|
_qmlEngine = new QQmlEngine;
|
||||||
if (!_qmlEngine->incubationController()) {
|
if (!_qmlEngine->incubationController()) {
|
||||||
|
@ -383,11 +393,11 @@ void OffscreenQmlSurface::resize(const QSize& newSize_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker _locker(&(_renderer->_mutex));
|
QMutexLocker locker(&(_renderer->_mutex));
|
||||||
_renderer->_newSize = newSize;
|
_renderer->_newSize = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderer->post(RESIZE);
|
_renderer->_queue.add(RESIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
||||||
|
@ -487,14 +497,11 @@ void OffscreenQmlSurface::updateQuick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_render) {
|
if (_render) {
|
||||||
QMutexLocker lock(&(_renderer->_mutex));
|
// Lock the GUI size while syncing
|
||||||
_renderer->post(RENDER);
|
QMutexLocker locker(&(_renderer->_mutex));
|
||||||
while (!_renderer->_cond.wait(&(_renderer->_mutex), 100)) {
|
_renderer->_queue.add(RENDER);
|
||||||
if (_renderer->_quit) {
|
_renderer->_waitCondition.wait(&(_renderer->_mutex));
|
||||||
return;
|
|
||||||
}
|
|
||||||
qApp->processEvents();
|
|
||||||
}
|
|
||||||
_render = false;
|
_render = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,8 +84,8 @@ private slots:
|
||||||
void updateQuick();
|
void updateQuick();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class OffscreenQmlRenderer;
|
friend class OffscreenQmlRenderThread;
|
||||||
OffscreenQmlRenderer* _renderer{ nullptr };
|
OffscreenQmlRenderThread* _renderer{ nullptr };
|
||||||
QQmlEngine* _qmlEngine{ nullptr };
|
QQmlEngine* _qmlEngine{ nullptr };
|
||||||
QQmlComponent* _qmlComponent{ nullptr };
|
QQmlComponent* _qmlComponent{ nullptr };
|
||||||
QQuickItem* _rootItem{ nullptr };
|
QQuickItem* _rootItem{ nullptr };
|
||||||
|
|
Loading…
Reference in a new issue