From 7d2918e0150821b0d272522887aa1821ac5789d9 Mon Sep 17 00:00:00 2001 From: Daniel Nachbaur Date: Thu, 27 Oct 2016 12:03:49 +0200 Subject: [PATCH] QmlStreamer: Use asynchronous rendering and image streaming; reuse QuickRenderer from Tide --- deflect/qt/CMakeLists.txt | 3 + deflect/qt/EventReceiver.cpp | 2 +- deflect/qt/EventReceiver.h | 1 + deflect/qt/QmlStreamer.cpp | 5 + deflect/qt/QmlStreamer.h | 3 + deflect/qt/QmlStreamerImpl.cpp | 204 +++++++++++++++------------------ deflect/qt/QmlStreamerImpl.h | 44 ++++--- deflect/qt/QuickRenderer.cpp | 199 ++++++++++++++++++++++++++++++++ deflect/qt/QuickRenderer.h | 152 ++++++++++++++++++++++++ deflect/types.h | 7 ++ doc/Changelog.md | 12 +- 11 files changed, 495 insertions(+), 137 deletions(-) create mode 100644 deflect/qt/QuickRenderer.cpp create mode 100644 deflect/qt/QuickRenderer.h diff --git a/deflect/qt/CMakeLists.txt b/deflect/qt/CMakeLists.txt index 95fdfa0..829b46a 100644 --- a/deflect/qt/CMakeLists.txt +++ b/deflect/qt/CMakeLists.txt @@ -10,13 +10,16 @@ set(DEFLECTQT_HEADERS ) set(DEFLECTQT_PUBLIC_HEADERS QmlStreamer.h + QuickRenderer.h TouchInjector.h ) set(DEFLECTQT_SOURCES EventReceiver.cpp QmlStreamer.cpp QmlStreamerImpl.cpp + QuickRenderer.cpp TouchInjector.cpp + ) set(DEFLECTQT_LINK_LIBRARIES PUBLIC Deflect Qt5::Quick PRIVATE Qt5::Qml) diff --git a/deflect/qt/EventReceiver.cpp b/deflect/qt/EventReceiver.cpp index 5e3e251..8f4a9d6 100644 --- a/deflect/qt/EventReceiver.cpp +++ b/deflect/qt/EventReceiver.cpp @@ -83,7 +83,7 @@ void EventReceiver::_onEvent( int socket ) { case Event::EVT_CLOSE: _notifier->setEnabled( false ); - QCoreApplication::quit(); + emit closed(); break; case Event::EVT_PRESS: emit pressed( _pos( deflectEvent )); diff --git a/deflect/qt/EventReceiver.h b/deflect/qt/EventReceiver.h index 90b99fd..0c83a4f 100644 --- a/deflect/qt/EventReceiver.h +++ b/deflect/qt/EventReceiver.h @@ -68,6 +68,7 @@ class EventReceiver : public QObject void moved( QPointF position ); void resized( QSize newSize ); + void closed(); void keyPress( int key, int modifiers, QString text ); void keyRelease( int key, int modifiers, QString text ); diff --git a/deflect/qt/QmlStreamer.cpp b/deflect/qt/QmlStreamer.cpp index e23eaf9..484b7ca 100644 --- a/deflect/qt/QmlStreamer.cpp +++ b/deflect/qt/QmlStreamer.cpp @@ -60,6 +60,11 @@ QmlStreamer::~QmlStreamer() { } +void QmlStreamer::useAsyncSend( const bool async ) +{ + _impl->useAsyncSend( async ); +} + QQuickItem* QmlStreamer::getRootItem() { return _impl->getRootItem(); diff --git a/deflect/qt/QmlStreamer.h b/deflect/qt/QmlStreamer.h index a4a38bb..9687148 100644 --- a/deflect/qt/QmlStreamer.h +++ b/deflect/qt/QmlStreamer.h @@ -93,6 +93,9 @@ class QmlStreamer : public QObject DEFLECTQT_API ~QmlStreamer(); + /** Use asynchronous send of images via Deflect stream. Default off. */ + DEFLECTQT_API void useAsyncSend( bool async ); + /** @return the QML root item, might be nullptr if not ready yet. */ DEFLECTQT_API QQuickItem* getRootItem(); diff --git a/deflect/qt/QmlStreamerImpl.cpp b/deflect/qt/QmlStreamerImpl.cpp index bbd45cc..d54315f 100644 --- a/deflect/qt/QmlStreamerImpl.cpp +++ b/deflect/qt/QmlStreamerImpl.cpp @@ -39,7 +39,9 @@ /*********************************************************************/ #include "QmlStreamerImpl.h" + #include "EventReceiver.h" +#include "QuickRenderer.h" #include "QmlGestures.h" #include "TouchInjector.h" @@ -54,10 +56,7 @@ #include #include #include - -#if DEFLECT_USE_QT5GUI -#include -#endif +#include namespace { @@ -66,6 +65,13 @@ const QString GESTURES_CONTEXT_PROPERTY( "deflectgestures" ); const QString WEBENGINEVIEW_OBJECT_NAME( "webengineview" ); const int TOUCH_TAPANDHOLD_DIST_PX = 20; const int TOUCH_TAPANDHOLD_TIMEOUT_MS = 200; + +deflect::Stream::Future make_ready_future( const bool value ) +{ + std::promise< bool > promise; + promise.set_value( value ); + return promise.get_future(); +} } class RenderControl : public QQuickRenderControl @@ -94,8 +100,6 @@ namespace qt QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, const std::string& streamId ) : QWindow() - , _context( new QOpenGLContext ) - , _offscreenSurface( new QOffscreenSurface ) , _renderControl( new RenderControl( this )) // Create a QQuickWindow that is associated with out render control. Note // that this window never gets created or shown, meaning that it will never @@ -103,20 +107,13 @@ QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, , _quickWindow( new QQuickWindow( _renderControl )) , _qmlEngine( new QQmlEngine ) , _qmlComponent( new QQmlComponent( _qmlEngine, QUrl( qmlFile ))) - , _rootItem( nullptr ) - , _fbo( nullptr ) - , _renderTimer( 0 ) - , _stopRenderingDelayTimer( 0 ) - , _stream( nullptr ) - , _eventHandler( nullptr ) , _qmlGestures( new QmlGestures ) , _touchInjector( new TouchInjector( *_quickWindow, std::bind( &Impl::_mapToScene, this, std::placeholders::_1 ))) - , _streaming( false ) , _streamHost( streamHost ) , _streamId( streamId ) - , _mouseMode( false ) + , _sendFuture( make_ready_future( true )) { _mouseModeTimer.setSingleShot( true ); _mouseModeTimer.setInterval( TOUCH_TAPANDHOLD_TIMEOUT_MS ); @@ -127,46 +124,17 @@ QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, setSurfaceType( QSurface::OpenGLSurface ); - // Qt Quick may need a depth and stencil buffer - QSurfaceFormat format_; - format_.setDepthBufferSize( 16 ); - format_.setStencilBufferSize( 8 ); - setFormat( format_ ); - - _context->setFormat( format_ ); - _context->create(); - - // Test if user has setup shared GL contexts (QtWebEngine::initialize). - // If so, setup global share context needed by the Qml WebEngineView. - if( QCoreApplication::testAttribute( Qt::AA_ShareOpenGLContexts )) -#if DEFLECT_USE_QT5GUI - qt_gl_set_global_share_context( _context ); -#else - qWarning() << "DeflectQt was not compiled with WebEngineView support"; -#endif - connect( &_mouseModeTimer, &QTimer::timeout, [this]() { + connect( &_mouseModeTimer, &QTimer::timeout, [&] { if( _touchIsTapAndHold( )) _switchFromTouchToMouseMode(); }); - // Pass _context->format(), not format_. Format does not specify and color - // buffer sizes, while the context, that has just been created, reports a - // format that has these values filled in. Pass this to the offscreen - // surface to make sure it will be compatible with the context's - // configuration. - _offscreenSurface->setFormat( _context->format( )); - _offscreenSurface->create(); - if( !_qmlEngine->incubationController( )) _qmlEngine->setIncubationController( _quickWindow->incubationController( )); // Now hook up the signals. For simplicy we don't differentiate between // renderRequested (only render is needed, no sync) and sceneChanged (polish // and sync is needed too). - connect( _quickWindow, &QQuickWindow::sceneGraphInitialized, - this, &QmlStreamer::Impl::_createFbo ); - connect( _quickWindow, &QQuickWindow::sceneGraphInvalidated, - this, &QmlStreamer::Impl::_destroyFbo ); connect( _renderControl, &QQuickRenderControl::renderRequested, this, &QmlStreamer::Impl::_requestRender ); connect( _renderControl, &QQuickRenderControl::sceneChanged, @@ -182,10 +150,9 @@ QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, QmlStreamer::Impl::~Impl() { - delete _eventHandler; - delete _stream; - - _context->makeCurrent( _offscreenSurface ); + _quickRenderer->stop(); + _quickRendererThread.quit(); + _quickRendererThread.wait(); // delete first to free scenegraph resources for following destructions delete _renderControl; @@ -194,98 +161,114 @@ QmlStreamer::Impl::~Impl() delete _qmlComponent; delete _quickWindow; delete _qmlEngine; - delete _fbo; - _context->doneCurrent(); - - delete _offscreenSurface; - delete _context; + delete _quickRenderer; } -void QmlStreamer::Impl::_createFbo() +void QmlStreamer::Impl::_requestRender() { - _fbo = - new QOpenGLFramebufferObject( _quickWindow->size() * devicePixelRatio(), - QOpenGLFramebufferObject::CombinedDepthStencil ); - _quickWindow->setRenderTarget( _fbo ); + killTimer( _stopRenderingDelayTimer ); + _stopRenderingDelayTimer = 0; + + if( _renderTimer == 0 ) + _renderTimer = startTimer( 5, Qt::PreciseTimer ); } -void QmlStreamer::Impl::_destroyFbo() +void QmlStreamer::Impl::_initRenderer() { - delete _fbo; - _fbo = 0; + _updateSizes( QSize( _rootItem->width(), _rootItem->height( ))); + + _quickRenderer = new QuickRenderer( *_quickWindow, *_renderControl, + true ); + +#if QT_VERSION >= 0x050500 + // Call required to make QtGraphicalEffects work in the initial scene. + _renderControl->prepareThread( &_quickRendererThread ); +#else + qDebug() << "missing QQuickRenderControl::prepareThread() on Qt < 5.5. " + "Expect some qWarnings and failing QtGraphicalEffects."; +#endif + + _quickRenderer->moveToThread( &_quickRendererThread ); + + _quickRendererThread.setObjectName( "Render" ); + _quickRendererThread.start(); + + _quickRenderer->init(); + + connect( _quickRenderer, &QuickRenderer::afterRender, + this, &QmlStreamer::Impl::_afterRender, Qt::DirectConnection ); + connect( _quickRenderer, &QuickRenderer::afterStop, + this, &QmlStreamer::Impl::_afterStop, Qt::DirectConnection ); } void QmlStreamer::Impl::_render() { - if( !_context->makeCurrent( _offscreenSurface )) - return; + // Initialize the render control and our OpenGL resources. Do this as late + // as possible to use the proper size reported by the rootItem. + if( !_quickRendererThread.isRunning( )) + _initRenderer(); - // Initialize the render control and our OpenGL resources. Do this as - // late as possible to use the proper size reported by the rootItem. - if( !_fbo ) - { - _updateSizes( QSize( _rootItem->width(), _rootItem->height( ))); + _renderControl->polishItems(); + _quickRenderer->render(); - _renderControl->initialize( _context ); + if( _stopRenderingDelayTimer == 0 ) + _stopRenderingDelayTimer = startTimer( 5000 /*ms*/ ); +} +void QmlStreamer::Impl::_afterRender() +{ + if( _stream ) + _streaming = _sendFuture.get(); + else + { if( !_setupDeflectStream( )) { qWarning() << "Could not setup Deflect stream"; - emit streamClosed(); + _streaming = false; + return; } + _streaming = true; } if( !_streaming ) return; - // Polish, synchronize and render the next frame (into our fbo). In this - // example everything happens on the same thread and therefore all three - // steps are performed in succession from here. In a threaded setup the - // render() call would happen on a separate thread. - _renderControl->polishItems(); - _renderControl->sync(); - _renderControl->render(); - - _quickWindow->resetOpenGLState(); - QOpenGLFramebufferObject::bindDefault(); - - _context->functions()->glFlush(); - - const QImage image = _fbo->toImage(); - if( image.isNull( )) + _quickRenderer->context()->functions()->glFlush(); + _image = _quickRenderer->fbo()->toImage(); + if( _image.isNull( )) { qDebug() << "Empty image not streamed"; return; } - ImageWrapper imageWrapper( image.constBits(), image.width(), image.height(), - BGRA, 0, 0 ); + ImageWrapper imageWrapper( _image.constBits(), _image.width(), + _image.height(), BGRA ); imageWrapper.compressionPolicy = COMPRESSION_ON; - imageWrapper.compressionQuality = 100; - _streaming = _stream->send( imageWrapper ) && _stream->finishFrame(); + imageWrapper.compressionQuality = 80; - if( !_streaming ) - { - killTimer( _renderTimer ); - _renderTimer = 0; - killTimer( _stopRenderingDelayTimer ); - _stopRenderingDelayTimer = 0; - emit streamClosed(); - return; - } + if( _asyncSend ) + _sendFuture = _stream->asyncSend( imageWrapper ); + else + _sendFuture = make_ready_future( _stream->send( imageWrapper ) && + _stream->finishFrame( )); +} - if( _stopRenderingDelayTimer == 0 ) - _stopRenderingDelayTimer = startTimer( 5000 /*ms*/ ); +void QmlStreamer::Impl::_afterStop() +{ + if( _sendFuture.valid() && _asyncSend ) + _sendFuture.wait(); + delete _eventHandler; + delete _stream; } -void QmlStreamer::Impl::_requestRender() +void QmlStreamer::Impl::_onStreamClosed() { + killTimer( _renderTimer ); + _renderTimer = 0; killTimer( _stopRenderingDelayTimer ); _stopRenderingDelayTimer = 0; - - if( _renderTimer == 0 ) - _renderTimer = startTimer( 5, Qt::PreciseTimer ); + emit streamClosed(); } void QmlStreamer::Impl::_onPressed( const QPointF pos ) @@ -436,8 +419,6 @@ bool QmlStreamer::Impl::_setupDeflectStream() if( !_stream->isConnected( )) return false; - _stream->setDisconnectedCallback( [this](){ emit streamClosed(); } ); - if( !_stream->registerForEvents( )) return false; @@ -478,6 +459,9 @@ bool QmlStreamer::Impl::_setupDeflectStream() connect( _eventHandler, &EventReceiver::swipeRight, _qmlGestures, &QmlGestures::swipeRight ); + connect( _eventHandler, &EventReceiver::closed, + this, &QmlStreamer::Impl::_onStreamClosed ); + return true; } @@ -552,14 +536,6 @@ void QmlStreamer::Impl::_sendMouseEvent( const QEvent::Type eventType, void QmlStreamer::Impl::resizeEvent( QResizeEvent* e ) { _updateSizes( e->size( )); - - if( _fbo && _fbo->size() != e->size() * devicePixelRatio() && - _context->makeCurrent( _offscreenSurface )) - { - _destroyFbo(); - _createFbo(); - _context->doneCurrent(); - } } void QmlStreamer::Impl::mousePressEvent( QMouseEvent* e ) diff --git a/deflect/qt/QmlStreamerImpl.h b/deflect/qt/QmlStreamerImpl.h index 4df7604..3b5b4bf 100644 --- a/deflect/qt/QmlStreamerImpl.h +++ b/deflect/qt/QmlStreamerImpl.h @@ -42,25 +42,25 @@ #define QMLSTREAMERIMPL_H #include +#include #include #include "QmlStreamer.h" #include "../SizeHints.h" +#include + QT_FORWARD_DECLARE_CLASS(QOpenGLContext) QT_FORWARD_DECLARE_CLASS(QOpenGLFramebufferObject) -QT_FORWARD_DECLARE_CLASS(QOffscreenSurface) -QT_FORWARD_DECLARE_CLASS(QQuickRenderControl) -QT_FORWARD_DECLARE_CLASS(QQuickWindow) -QT_FORWARD_DECLARE_CLASS(QQmlEngine) QT_FORWARD_DECLARE_CLASS(QQmlComponent) +QT_FORWARD_DECLARE_CLASS(QQmlEngine) QT_FORWARD_DECLARE_CLASS(QQuickItem) +QT_FORWARD_DECLARE_CLASS(QQuickRenderControl) +QT_FORWARD_DECLARE_CLASS(QQuickWindow) namespace deflect { -class Stream; - namespace qt { @@ -78,6 +78,7 @@ class QmlStreamer::Impl : public QWindow ~Impl(); + void useAsyncSend( const bool async ) { _asyncSend = async; } QQuickItem* getRootItem() { return _rootItem; } QQmlEngine* getQmlEngine() { return _qmlEngine; } Stream* getStream() { return _stream; } @@ -91,11 +92,12 @@ class QmlStreamer::Impl : public QWindow private slots: bool _setupRootItem(); - void _createFbo(); - void _destroyFbo(); void _render(); void _requestRender(); + void _afterRender(); + void _afterStop(); + void _onPressed( QPointF position ); void _onReleased( QPointF position ); void _onMoved( QPointF position ); @@ -105,10 +107,13 @@ private slots: void _onKeyPress( int key, int modifiers, QString text ); void _onKeyRelease( int key, int modifiers, QString text ); + void _onStreamClosed(); + signals: void streamClosed(); private: + void _initRenderer(); void _send( QKeyEvent& keyEvent ); bool _sendToWebengineviewItems( QKeyEvent& keyEvent ); std::string _getDeflectStreamIdentifier() const; @@ -124,31 +129,34 @@ private slots: QPointF _mapToScene( const QPointF& normalizedPos ) const; - QOpenGLContext* _context; - QOffscreenSurface* _offscreenSurface; QQuickRenderControl* _renderControl; + QuickRenderer* _quickRenderer{ nullptr }; + QThread _quickRendererThread; QQuickWindow* _quickWindow; QQmlEngine* _qmlEngine; QQmlComponent* _qmlComponent; - QQuickItem* _rootItem; - QOpenGLFramebufferObject* _fbo; + QQuickItem* _rootItem{ nullptr }; - int _renderTimer; - int _stopRenderingDelayTimer; + int _renderTimer{ 0 }; + int _stopRenderingDelayTimer{ 0 }; - Stream* _stream; - EventReceiver* _eventHandler; + Stream* _stream{ nullptr }; + EventReceiver* _eventHandler{ nullptr }; QmlGestures* _qmlGestures; TouchInjector* _touchInjector; - bool _streaming; + bool _streaming{ false }; const std::string _streamHost; const std::string _streamId; SizeHints _sizeHints; QTimer _mouseModeTimer; - bool _mouseMode; + bool _mouseMode{ false }; QPointF _touchStartPos; QPointF _touchCurrentPos; + + bool _asyncSend { false }; + Stream::Future _sendFuture; + QImage _image; }; } diff --git a/deflect/qt/QuickRenderer.cpp b/deflect/qt/QuickRenderer.cpp new file mode 100644 index 0000000..1963931 --- /dev/null +++ b/deflect/qt/QuickRenderer.cpp @@ -0,0 +1,199 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Daniel.Nachbaur@epfl.ch */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "QuickRenderer.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEFLECT_USE_QT5GUI +# include +#endif + +namespace deflect +{ +namespace qt +{ + +QuickRenderer::QuickRenderer( QQuickWindow& quickWindow, + QQuickRenderControl& renderControl, + const bool offscreen ) + : _quickWindow( quickWindow ) + , _renderControl( renderControl ) + , _offscreen( offscreen ) + , _initialized( false ) +{ + connect( this, &QuickRenderer::init, this, + &QuickRenderer::_onInit, Qt::BlockingQueuedConnection ); + connect( this, &QuickRenderer::stop, this, + &QuickRenderer::_onStop, Qt::BlockingQueuedConnection ); +} + +void QuickRenderer::render() +{ + QMutexLocker lock( &_mutex ); + QCoreApplication::postEvent( this, new QEvent( QEvent::User )); + + // the main thread has to be blocked for sync() + _cond.wait( &_mutex ); +} + +bool QuickRenderer::event( QEvent* e ) +{ + if( e->type() == QEvent::User ) + { + _onRender(); + return true; + } + return QObject::event( e ); +} + +void QuickRenderer::_onInit() +{ + // Qt Quick may need a depth and stencil buffer + QSurfaceFormat format_; + format_.setDepthBufferSize( 16 ); + format_.setStencilBufferSize( 8 ); + + _context = new QOpenGLContext; + _context->setFormat( format_ ); + _context->create(); + + // Test if user has setup shared GL contexts (QtWebEngine::initialize). + // If so, setup global share context needed by the Qml WebEngineView. + if( QCoreApplication::testAttribute( Qt::AA_ShareOpenGLContexts )) +#if DEFLECT_USE_QT5GUI + qt_gl_set_global_share_context( _context ); +#else + qWarning() << "DeflectQt was not compiled with WebEngineView support"; +#endif + + if( _offscreen ) + { + _offscreenSurface = new QOffscreenSurface; + // Pass _context->format(), not format_. Format does not specify and color + // buffer sizes, while the context, that has just been created, reports a + // format that has these values filled in. Pass this to the offscreen + // surface to make sure it will be compatible with the context's + // configuration. + _offscreenSurface->setFormat( _context->format( )); + _offscreenSurface->create(); + } + + _context->makeCurrent( _getSurface( )); + _renderControl.initialize( _context ); + _initialized = true; +} + +void QuickRenderer::_onStop() +{ + _context->makeCurrent( _getSurface( )); + + _renderControl.invalidate(); + + delete _fbo; + _fbo = nullptr; + + _context->doneCurrent(); + + delete _offscreenSurface; + _offscreenSurface = nullptr; + delete _context; + _context = nullptr; + + emit afterStop(); +} + +void QuickRenderer::_ensureFBO() +{ + if( _fbo && + _fbo->size() != _quickWindow.size() * _quickWindow.devicePixelRatio()) + { + delete _fbo; + _fbo = nullptr; + } + + if( !_fbo ) + { + _fbo = new QOpenGLFramebufferObject( + _quickWindow.size() * _quickWindow.devicePixelRatio(), + QOpenGLFramebufferObject::CombinedDepthStencil ); + _quickWindow.setRenderTarget( _fbo ); + } +} + +QSurface* QuickRenderer::_getSurface() +{ + if( _offscreen ) + return _offscreenSurface; + return &_quickWindow; +} + +void QuickRenderer::_onRender() +{ + if( !_initialized ) + return; + + { + QMutexLocker lock( &_mutex ); + + _context->makeCurrent( _getSurface( )); + + if( _offscreen ) + _ensureFBO(); + + _renderControl.sync(); + + // unblock gui thread after sync in render thread is done + _cond.wakeOne(); + } + + _renderControl.render(); + _context->functions()->glFinish(); + + emit afterRender(); +} + +} +} diff --git a/deflect/qt/QuickRenderer.h b/deflect/qt/QuickRenderer.h new file mode 100644 index 0000000..866b8fa --- /dev/null +++ b/deflect/qt/QuickRenderer.h @@ -0,0 +1,152 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Daniel.Nachbaur@epfl.ch */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef QUICKRENDERER_H +#define QUICKRENDERER_H + +#include +#include +#include +#include + +QT_FORWARD_DECLARE_CLASS(QOffscreenSurface) +QT_FORWARD_DECLARE_CLASS(QOpenGLContext) +QT_FORWARD_DECLARE_CLASS(QQuickRenderControl) +QT_FORWARD_DECLARE_CLASS(QQuickWindow) +QT_FORWARD_DECLARE_CLASS(QSurface) + +namespace deflect +{ +namespace qt +{ + +/** + * Renders the QML scene from the given window using QQuickRenderControl onto + * the surface of the window or to an offscreen surface. Note that this object + * needs to be moved to a seperate (render)thread to function properly. + * + * Inspired by http://doc.qt.io/qt-5/qtquick-rendercontrol-window-multithreaded-cpp.html + */ +class QuickRenderer : public QObject +{ + Q_OBJECT + +public: + /** + * After construction, move the object to a dedicated render thread before + * calling any other function. + * + * @param quickWindow the window to render into if not @sa offscreen, + * otherwise an FBO with the windows' size is attached as + * the render target. + * @param renderControl associated with the QML scene of quickWindow to + * trigger the actual rendering. + * @param offscreen render into an offscreen surface rather than the + * quickWindow. It creates an FBO internally to hold the + * rendered pixels. + */ + QuickRenderer( QQuickWindow& quickWindow, + QQuickRenderControl& renderControl, + bool offscreen = false ); + + /** @return OpenGL context used for rendering; lives in render thread. */ + QOpenGLContext* context() { return _context; } + + /** + * @return nullptr if !offscreen, otherwise accessible in afterRender(); + * lives in render thread. + */ + QOpenGLFramebufferObject* fbo() { return _fbo; } + + /** + * To be called from GUI/main thread to trigger rendering. + * + * This call is blocking until sync() is done in render thread. + */ + void render(); + +signals: + /** + * Emitted at the end of render(). Does not do swapBuffers() in onscreen + * case. Originates from render thread. + */ + void afterRender(); + + /** Emitted at the end of stop(). Originates from render thread. */ + void afterStop(); + + /** + * To be called from GUI/main thread to initialize this object on render + * thread. Blocks until operation on render thread is done. + */ + void init(); + + /** + * To be called from GUI/main thread to stop using this object on the render + * thread. Blocks until operation on render thread is done. + */ + void stop(); + +private: + bool event( QEvent* qtEvent ) final; + void _onInit(); + void _onRender(); + void _onStop(); + + void _ensureFBO(); + QSurface* _getSurface(); + + QQuickWindow& _quickWindow; + QQuickRenderControl& _renderControl; + + QOpenGLContext* _context{ nullptr }; + QOffscreenSurface* _offscreenSurface{ nullptr }; + QOpenGLFramebufferObject* _fbo{ nullptr }; + + bool _offscreen; + bool _initialized{ false }; + + QMutex _mutex; + QWaitCondition _cond; +}; + +} +} + +#endif diff --git a/deflect/types.h b/deflect/types.h index 4bc5cf4..d51131d 100644 --- a/deflect/types.h +++ b/deflect/types.h @@ -67,6 +67,13 @@ typedef std::shared_ptr< Frame > FramePtr; typedef std::vector< Segment > Segments; typedef std::vector< SegmentParameters > SegmentParametersList; +namespace qt +{ + class QuickRenderer; + class QmlStreamer; + class TouchInjector; +} + } #endif diff --git a/doc/Changelog.md b/doc/Changelog.md index 562eab1..0091e45 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -5,12 +5,16 @@ Changelog {#Changelog} ### 0.12.0 (git master) -* [130](https://github.com/BlueBrain/Deflect/pull/130) +* [133](https://github.com/BlueBrain/Deflect/pull/133): + QmlStreamer: Use asynchronous rendering, add + deflect::qt::QmlStreamer::useAsyncSend() to enable asynchronous image streaming + (off by default). +* [130](https://github.com/BlueBrain/Deflect/pull/130): Replaced boost by C++11. Boost is now an optional dependency and it is used only by the tests. Some API changes were introduced by this change. -* [129](https://github.com/BlueBrain/Deflect/pull/129) +* [129](https://github.com/BlueBrain/Deflect/pull/129): Cleared Deflect from boost::serialization that was used exclusively by Tide. -* [128](https://github.com/BlueBrain/Deflect/pull/128) +* [128](https://github.com/BlueBrain/Deflect/pull/128): New events for transmitting all touch points in addition to existing gestures: - Gives the ability to handle more than one touch point in applications (e.g. draw with multiple fingers on a whiteboard). @@ -22,7 +26,7 @@ Changelog {#Changelog} - A new *pinch* event was added. Clients of Tide >= 1.2 have to adapt their code to use it instead of *wheel* events, which are no longer sent. - Minor additions to the simplestreamer demo application. -* [126](https://github.com/BlueBrain/Deflect/pull/126) +* [126](https://github.com/BlueBrain/Deflect/pull/126): DesktopStreamer: The list of default hosts can be configured using the CMake variable DEFLECT_DESKTOPSTREAMER_HOSTS. * [124](https://github.com/BlueBrain/Deflect/pull/124):