From 64fa6aec4316ae5320b1059cc1f36c933eae8081 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Tue, 13 Sep 2011 11:09:39 +0000 Subject: [PATCH] From Robert Milharcic, "This will hopefully fix some issues with osgQt, more precisely with GLWidget event handling. There are at least two current GL context braking events, QEvent::Hide and QEvent::ParentChange. When running in a multithreaded mode they both try to change current GL context in a wrong thread (main GUI thread). The QEvent::ParentChange is also problematic when running in a single threaded model because Qt is going to release current contex then delete it, and then it will create new one, and as a result the osg will continue to render to an invalid deleted context. This changes workaround above problems by deferring execution of the problematic evens. These events has to be enqueued and executed later. The enqueued event processing is currently done right after swap in a swapBuffersImplementation of GraphicsWindowQt while code is running in a render thread by calling QGLWidget handler directly. In principle the deferred events queue should be executed while in GUI thread but I couldn't find any reliable way to do this, that is without risking a deadlock. For now it is assumed, Qt is not going to execute any GUI thread only operations inside the QGLWidget handler." --- include/osg/GraphicsContext | 2 +- include/osgQt/GraphicsWindowQt | 45 ++++++++++++-- src/osgQt/GraphicsWindowQt.cpp | 106 ++++++++++++++++++++++++++++----- 3 files changed, 133 insertions(+), 20 deletions(-) diff --git a/include/osg/GraphicsContext b/include/osg/GraphicsContext index e6a207e41..8443e9202 100644 --- a/include/osg/GraphicsContext +++ b/include/osg/GraphicsContext @@ -261,7 +261,7 @@ class OSG_EXPORT GraphicsContext : public Object void removeAllOperations(); /** Run the operations. */ - void runOperations(); + virtual void runOperations(); typedef std::list< ref_ptr > GraphicsOperationQueue; diff --git a/include/osgQt/GraphicsWindowQt b/include/osgQt/GraphicsWindowQt index 2d10987c4..9925b6289 100644 --- a/include/osgQt/GraphicsWindowQt +++ b/include/osgQt/GraphicsWindowQt @@ -14,8 +14,13 @@ #ifndef OSGVIEWER_GRAPHICSWINDOWQT #define OSGVIEWER_GRAPHICSWINDOWQT -#include +#include +#include +#include +#include #include + +#include #include class QInputEvent; @@ -46,6 +51,7 @@ void OSGQT_EXPORT setViewer( osgViewer::ViewerBase *viewer ); class OSGQT_EXPORT GLWidget : public QGLWidget { typedef QGLWidget inherited; + public: GLWidget( QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); @@ -71,9 +77,37 @@ public: virtual void wheelEvent( QWheelEvent* event ); protected: + + int getNumDeferredEvents() + { + QMutexLocker lock(&_deferredEventQueueMutex); + return _deferredEventQueue.count(); + } + void enqueueDeferredEvent(QEvent::Type eventType, QEvent::Type removeEventType = QEvent::None) + { + QMutexLocker lock(&_deferredEventQueueMutex); + + if (removeEventType != QEvent::None) + { + if (_deferredEventQueue.removeOne(removeEventType)) + _eventCompressor.remove(eventType); + } + + if (_eventCompressor.find(eventType) == _eventCompressor.end()) + { + _deferredEventQueue.enqueue(eventType); + _eventCompressor.insert(eventType); + } + } + void processDeferredEvents(); + friend class GraphicsWindowQt; GraphicsWindowQt* _gw; + QMutex _deferredEventQueueMutex; + QQueue _deferredEventQueue; + QSet _eventCompressor; + bool _forwardKeyEvents; virtual void resizeEvent( QResizeEvent* event ); @@ -96,8 +130,8 @@ public: inline GLWidget* getGraphWidget() { return _widget; } /// deprecated inline const GLWidget* getGraphWidget() const { return _widget; } - -struct WindowData : public osg::Referenced + + struct WindowData : public osg::Referenced { WindowData( GLWidget* widget = NULL, GLWidget* parent = NULL ): _widget(widget), _parent(parent) {} GLWidget* _widget; @@ -129,12 +163,13 @@ struct WindowData : public osg::Referenced virtual bool makeCurrentImplementation(); virtual bool releaseContextImplementation(); virtual void swapBuffersImplementation(); - + virtual void runOperations(); + virtual void requestWarpPointer( float x, float y ); protected: - friend class GLWidget; + friend class GLWidget; GLWidget* _widget; bool _ownsWidget; QCursor _currentCursor; diff --git a/src/osgQt/GraphicsWindowQt.cpp b/src/osgQt/GraphicsWindowQt.cpp index 085b5ab16..df2071592 100644 --- a/src/osgQt/GraphicsWindowQt.cpp +++ b/src/osgQt/GraphicsWindowQt.cpp @@ -126,8 +126,6 @@ void timerEvent( QTimerEvent *event ); static HeartBeat heartBeat; - - GLWidget::GLWidget( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, bool forwardKeyEvents ) : QGLWidget(parent, shareWidget, f), _gw( NULL ), @@ -162,18 +160,65 @@ GLWidget::~GLWidget() } } +void GLWidget::processDeferredEvents() +{ + QQueue deferredEventQueueCopy; + { + QMutexLocker lock(&_deferredEventQueueMutex); + deferredEventQueueCopy = _deferredEventQueue; + _eventCompressor.clear(); + _deferredEventQueue.clear(); + } + + while (!deferredEventQueueCopy.isEmpty()) + { + QEvent event(deferredEventQueueCopy.dequeue()); + QGLWidget::event(&event); + } +} + bool GLWidget::event( QEvent* event ) { - if( event->type() == QEvent::Hide ) + + // QEvent::Hide + // + // workaround "Qt-workaround" that does glFinish before hiding the widget + // (the Qt workaround was seen at least in Qt 4.6.3 and 4.7.0) + // + // Qt makes the context current, performs glFinish, and releases the context. + // This makes the problem in OSG multithreaded environment as the context + // is active in another thread, thus it can not be made current for the purpose + // of glFinish in this thread. + + // QEvent::ParentChange + // + // Reparenting GLWidget may create a new underlying window and a new GL context. + // Qt will then call doneCurrent on the GL context about to be deleted. The thread + // where old GL context was current has no longer current context to render to and + // we cannot make new GL context current in this thread. + + // We workaround above problems by deferring execution of problematic event requests. + // These events has to be enqueue and executed later in a main GUI thread (GUI operations + // outside the main thread are not allowed) just before makeCurrent is called from the + // right thread. The good place for doing that is right after swap in a swapBuffersImplementation. + + if (event->type() == QEvent::Hide) { - // workaround "Qt-workaround" that does glFinish before hiding the widget - // (the Qt workaround was seen at least in Qt 4.6.3 and 4.7.0) - // - // Qt makes the context current, performs glFinish, and releases the context. - // This makes the problem in OSG multithreaded environment as the context - // is active in another thread, thus it can not be made current for the purpose - // of glFinish in this thread. We workaround it by skiping QGLWidget::event() code. - return QWidget::event( event ); + // enqueue only the last of QEvent::Hide and QEvent::Show + enqueueDeferredEvent(QEvent::Hide, QEvent::Show); + return true; + } + else if (event->type() == QEvent::Show) + { + // enqueue only the last of QEvent::Show or QEvent::Hide + enqueueDeferredEvent(QEvent::Show, QEvent::Hide); + return true; + } + else if (event->type() == QEvent::ParentChange) + { + // enqueue only the last QEvent::ParentChange + enqueueDeferredEvent(QEvent::ParentChange); + return true; } // perform regular event handling @@ -482,9 +527,13 @@ bool GraphicsWindowQt::setWindowDecorationImplementation( bool windowDecoration flags |= Qt::WindowTitleHint|Qt::WindowMinMaxButtonsHint|Qt::WindowSystemMenuHint; _traits->windowDecoration = windowDecoration; - // FIXME: Calling setWindowFlags or reparent widget will recreate the window handle, - // which makes QGLContext no longer work...How to deal with that? - //if ( _widget ) _widget->setWindowFlags( flags ); + if ( _widget ) + { + _widget->setWindowFlags( flags ); + + return true; + } + return false; } @@ -622,9 +671,26 @@ void GraphicsWindowQt::closeImplementation() _realized = false; } +void GraphicsWindowQt::runOperations() +{ + // While in graphics thread this is last chance to do something useful before + // graphics thread will execute its operations. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); + + GraphicsWindow::runOperations(); +} + bool GraphicsWindowQt::makeCurrentImplementation() { + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + _widget->makeCurrent(); + return true; } @@ -637,6 +703,18 @@ bool GraphicsWindowQt::releaseContextImplementation() void GraphicsWindowQt::swapBuffersImplementation() { _widget->swapBuffers(); + + // FIXME: the processDeferredEvents should really be executed in a GUI (main) thread context but + // I couln't find any reliable way to do this. For now, lets hope non of *GUI thread only operations* will + // be executed in a QGLWidget::event handler. On the other hand, calling GUI only operations in the + // QGLWidget event handler is an indication of a Qt bug. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + // We need to call makeCurrent here to restore our previously current context + // which may be changed by the processDeferredEvents function. + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); } void GraphicsWindowQt::requestWarpPointer( float x, float y )