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."

This commit is contained in:
Robert Osfield
2011-09-13 11:09:39 +00:00
parent 098bc6df5e
commit 64fa6aec43
3 changed files with 133 additions and 20 deletions

View File

@@ -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<Operation> > GraphicsOperationQueue;

View File

@@ -14,8 +14,13 @@
#ifndef OSGVIEWER_GRAPHICSWINDOWQT
#define OSGVIEWER_GRAPHICSWINDOWQT
#include <osgViewer/GraphicsWindow>
#include <QtCore/QMutex>
#include <QtCore/QEvent>
#include <QtCore/QQueue>
#include <QtCore/QSet>
#include <QtOpenGL/QGLWidget>
#include <osgViewer/GraphicsWindow>
#include <osgQt/Export>
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<QEvent::Type> _deferredEventQueue;
QSet<QEvent::Type> _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;

View File

@@ -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<QEvent::Type> 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 )