Skip to content

Instantly share code, notes, and snippets.

@jasonbeverage
Created February 3, 2022 17:35
Show Gist options
  • Save jasonbeverage/cdf366738b561a4404bae13e51464532 to your computer and use it in GitHub Desktop.
Save jasonbeverage/cdf366738b561a4404bae13e51464532 to your computer and use it in GitHub Desktop.
Multithreaded QT implementation
#include <QApplication>
#include <QEvent>
#include <QResizeEvent>
#include <QtOpenGL/QGLWidget>
#include <QTimer>
#include <sstream>
#include <osgViewer/GraphicsWindow>
#include <osgViewer/Renderer>
#include <osgDB/ReadFile>
#include <osgEarth/MapNode>
#include <osgEarth/EarthManipulator>
#include <osgEarth/Metrics>
#include <osgGA/TrackballManipulator>
#include <osgGA/StateSetManipulator>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/CompositeViewer>
#include <thread>
#include <iostream>
using namespace osgEarth;
class OSGWidgetContext : public osgViewer::GraphicsWindowEmbedded
{
public:
OSGWidgetContext(QGLWidget* widget, osg::GraphicsContext::Traits* traits):
osgViewer::GraphicsWindowEmbedded(traits),
_widget(widget)
{
}
virtual bool valid() const
{
return _widget->context()->isValid();
}
virtual bool realizeImplementation()
{
_realized = true;
return _realized;
}
virtual bool isRealizedImplementation() const
{
return _realized;
}
virtual void closeImplementation()
{
_realized = false;
}
virtual void grabFocus()
{
_widget->setFocus(Qt::OtherFocusReason);
}
virtual void grabFocusIfPointerInWindow()
{
QPoint globalPos = QCursor::pos();
QPoint localPos = _widget->mapFromGlobal(globalPos);
if (_widget->rect().contains(localPos))
{
grabFocus();
}
}
virtual bool makeCurrentImplementation()
{
OE_PROFILING_ZONE;
_widget->makeCurrent();
return true;
}
virtual bool releaseContextImplementation()
{
OE_PROFILING_ZONE;
_widget->doneCurrent();
return true;
}
virtual void swapBuffersImplementation()
{
OE_PROFILING_ZONE;
_widget->swapBuffers();
}
virtual void requestWarpPointer(float x, float y)
{
}
virtual void useCursor(bool onOff)
{
}
private:
QGLWidget* _widget;
bool _realized = false;
};
QGLFormat formatFromTraits(osg::GraphicsContext::Traits* traits)
{
QGLFormat format;
format.setDoubleBuffer(traits->doubleBuffer);
format.setSwapInterval(traits->vsync ? 1 : 0);
format.setDepthBufferSize(traits->depth);
format.setStencilBufferSize(traits->stencil);
format.setRedBufferSize(traits->red);
format.setGreenBufferSize(traits->green);
format.setBlueBufferSize(traits->blue);
format.setAlphaBufferSize(traits->alpha);
format.setSampleBuffers(traits->sampleBuffers > 0 ? true : false);
format.setSamples(traits->samples);
format.setStereo(traits->quadBufferStereo);
return format;
}
static osgViewer::CompositeViewer* s_viewer;
static osg::ref_ptr < osg::Node > s_loadedNode;
static bool vsync = false;
static osgGA::GUIEventAdapter::KeySymbol osgKeyFromQtKeyEvent(
QKeyEvent* event)
{
int key = event->key();
// These are for determining left vs right ctrl/alt/shift on Windows
#ifdef WIN32
#define EXTENDED_KEY_MASK 0x01000000
#define RSHIFT_MASK 0X36
quint32 mods = event->nativeModifiers();
bool extended = (bool)(mods & EXTENDED_KEY_MASK);
quint32 scode = event->nativeScanCode();
#endif
switch (key)
{
case Qt::Key_Escape:
return osgGA::GUIEventAdapter::KEY_Escape;
case Qt::Key_Delete:
return osgGA::GUIEventAdapter::KEY_Delete;
case Qt::Key_Backspace:
return osgGA::GUIEventAdapter::KEY_BackSpace;
case Qt::Key_SysReq:
return osgGA::GUIEventAdapter::KEY_Sys_Req;
case Qt::Key_Pause:
return osgGA::GUIEventAdapter::KEY_Pause;
case Qt::Key_Enter:
case Qt::Key_Return:
return osgGA::GUIEventAdapter::KEY_Return;
break;
case Qt::Key_Left:
return osgGA::GUIEventAdapter::KEY_Left;
case Qt::Key_Up:
return osgGA::GUIEventAdapter::KEY_Up;
case Qt::Key_Right:
return osgGA::GUIEventAdapter::KEY_Right;
case Qt::Key_Down:
return osgGA::GUIEventAdapter::KEY_Down;
case Qt::Key_Insert:
return osgGA::GUIEventAdapter::KEY_Insert;
case Qt::Key_Home:
return osgGA::GUIEventAdapter::KEY_Home;
case Qt::Key_PageUp:
return osgGA::GUIEventAdapter::KEY_Page_Up;
case Qt::Key_PageDown:
return osgGA::GUIEventAdapter::KEY_Page_Down;
case Qt::Key_End:
return osgGA::GUIEventAdapter::KEY_End;
case Qt::Key_Clear:
return osgGA::GUIEventAdapter::KEY_Clear;
case Qt::Key_F1:
return osgGA::GUIEventAdapter::KEY_F1;
case Qt::Key_F2:
return osgGA::GUIEventAdapter::KEY_F2;
case Qt::Key_F3:
return osgGA::GUIEventAdapter::KEY_F3;
case Qt::Key_F4:
return osgGA::GUIEventAdapter::KEY_F4;
case Qt::Key_F5:
return osgGA::GUIEventAdapter::KEY_F5;
case Qt::Key_F6:
return osgGA::GUIEventAdapter::KEY_F6;
case Qt::Key_F7:
return osgGA::GUIEventAdapter::KEY_F7;
case Qt::Key_F8:
return osgGA::GUIEventAdapter::KEY_F8;
case Qt::Key_F9:
return osgGA::GUIEventAdapter::KEY_F9;
case Qt::Key_F10:
return osgGA::GUIEventAdapter::KEY_F10;
case Qt::Key_F11:
return osgGA::GUIEventAdapter::KEY_F11;
case Qt::Key_F12:
return osgGA::GUIEventAdapter::KEY_F12;
#ifdef WIN32
case Qt::Key_Shift:
// Unfortunately shift doesn't use the extended flag, so we need to look at the scan code
if ((scode ^ RSHIFT_MASK) == 0)
{
return osgGA::GUIEventAdapter::KEY_Shift_R;
}
else
{
return osgGA::GUIEventAdapter::KEY_Shift_L;
}
case Qt::Key_Control:
if (extended)
{
return osgGA::GUIEventAdapter::KEY_Control_R;
}
else
{
return osgGA::GUIEventAdapter::KEY_Control_L;
}
case Qt::Key_Alt:
if (extended)
{
return osgGA::GUIEventAdapter::KEY_Alt_R;
}
else
{
return osgGA::GUIEventAdapter::KEY_Alt_L;
}
#else
case Qt::Key_Shift:
return osgGA::GUIEventAdapter::KEY_Shift_L;
case Qt::Key_Control:
return osgGA::GUIEventAdapter::KEY_Control_L;
case Qt::Key_Alt:
return osgGA::GUIEventAdapter::KEY_Alt_L;
#endif
}
// ascii representation of these does not work if ctrl modifier is
// down...
/*
if (key >= Qt::Key_A && key <= Qt::Key_Z)
{
return (osgGA::GUIEventAdapter::KeySymbol)key;
}
*/
// If we get here, use the ascii representation
int keyRep = *(event->text().toLatin1().data());
if (keyRep != 0)
{
return (osgGA::GUIEventAdapter::KeySymbol)keyRep;
}
return (osgGA::GUIEventAdapter::KeySymbol)0;
}
class ProfiledRenderer : public osgViewer::Renderer
{
public:
ProfiledRenderer(osg::Camera* camera):
osgViewer::Renderer(camera)
{
}
virtual void cull()
{
OE_PROFILING_ZONE;
osgViewer::Renderer::cull();
}
virtual void draw()
{
OE_PROFILING_ZONE;
osgViewer::Renderer::draw();
}
virtual void cull_draw()
{
OE_PROFILING_ZONE;
osgViewer::Renderer::cull_draw();
}
};
class ProfiledView : public osgViewer::View
{
public:
ProfiledView() :
osgViewer::View()
{
}
protected:
virtual osg::GraphicsOperation* createRenderer(osg::Camera* camera) override
{
return new ProfiledRenderer(camera);
}
};
class OSGWidget : public QGLWidget
{
public:
OSGWidget(osg::GraphicsContext::Traits* traits, QWidget* parent = nullptr, QGLWidget* sharedContext = nullptr):
QGLWidget(formatFromTraits(traits), parent, sharedContext)
{
_osgContext = new OSGWidgetContext(this, traits);
std::cout << "Context id " << _osgContext->getState()->getContextID() << std::endl;
//_view = new osgViewer::View();
_view = new ProfiledView();
// We do this here b/c we can't override createRenderer b/c it's called in the constructor
_view->getCamera()->setRenderer(new ProfiledRenderer(_view->getCamera()));
_view->setCameraManipulator(new osgEarth::EarthManipulator);
//_view->setCameraManipulator(new osgGA::TrackballManipulator);
_view->getCamera()->setGraphicsContext(_osgContext);
_view->getCamera()->setViewport(0, 0, traits->width, traits->height);
_view->getCamera()->setProjectionMatrixAsPerspective(45, 1, 1, 10);
_view->getCamera()->setClearColor(osg::Vec4(1, 0, 0, 1));
static bool firstView = true;
if (firstView)
{
_view->addEventHandler(new osgViewer::StatsHandler());
_view->addEventHandler(new osgViewer::ThreadingHandler);
firstView = false;
}
_view->addEventHandler(new osgGA::StateSetManipulator(_view->getCamera()->getOrCreateStateSet()));
setAutoBufferSwap(false);
doneCurrent();
}
OSGWidgetContext* osgContext()
{
return _osgContext.get();
}
osgViewer::View* view()
{
return _view.get();
}
void resizeGL(int width, int height)
{
std::cout << "resizegl" << std::endl;
_osgContext->getEventQueue()->windowResize(0, 0, width, height);
_osgContext->resized(0, 0, width, height);
_view->getCamera()->resize(width, height, osg::Camera::RESIZE_VIEWPORT | osg::Camera::RESIZE_PROJECTIONMATRIX);
}
void resizeEvent(QResizeEvent* e)
{
_osgContext->getEventQueue()->windowResize(0, 0, e->size().width(), e->size().height());
_osgContext->resized(0, 0, e->size().width(), e->size().height());
_view->getCamera()->resize(e->size().width(), e->size().height(), osg::Camera::RESIZE_VIEWPORT | osg::Camera::RESIZE_PROJECTIONMATRIX);
}
// Disable automatic painting
void paintEvent(QPaintEvent* e)
{
return;
}
// If you see this message QT is painting on on it's own
void paintGL()
{
std::cout << "QT calling paintGL, this is bad" << std::endl;
}
void closeEvent(QCloseEvent* event)
{
s_viewer->removeView(_view.get());
std::cout << "Window closed. Number of views " << s_viewer->getNumViews() << std::endl;
}
void keyPressEvent(QKeyEvent* event)
{
auto key = osgKeyFromQtKeyEvent(event);
_osgContext->getEventQueue()->keyPress(key);
}
void keyReleaseEvent(QKeyEvent* event)
{
auto key = osgKeyFromQtKeyEvent(event);
_osgContext->getEventQueue()->keyRelease(key);
}
int qtButtonToOsgButton(QMouseEvent* event)
{
int button;
switch (event->button())
{
case(Qt::LeftButton):
button = 1;
break;
case(Qt::MidButton):
button = 2;
break;
case(Qt::RightButton):
button = 3;
break;
case(Qt::NoButton):
button = 0;
break;
default:
button = 0;
break;
}
return button;
}
void mousePressEvent(QMouseEvent* event)
{
int button = qtButtonToOsgButton(event);
if (event->button() == Qt::LeftButton)
{
_leftDown = true;
}
else if (event->button() == Qt::MidButton)
{
_middleDown = true;
}
else if (event->button() == Qt::RightButton)
{
_rightDown = true;
}
_osgContext->getEventQueue()->mouseButtonPress(
widgetToNativeCoordinate(event->x()),
widgetToNativeCoordinate(event->y()), button);
}
void mouseReleaseEvent(QMouseEvent* event)
{
int button = qtButtonToOsgButton(event);
if (event->button() == Qt::LeftButton)
{
if (_leftDown)
{
_leftDown = false;
_osgContext->getEventQueue()->mouseButtonRelease(
widgetToNativeCoordinate(event->x()),
widgetToNativeCoordinate(event->y()), button);
}
}
else if (event->button() == Qt::MidButton)
{
if (_middleDown)
{
_middleDown = false;
_osgContext->getEventQueue()->mouseButtonRelease(
widgetToNativeCoordinate(event->x()),
widgetToNativeCoordinate(event->y()), button);
}
}
else if (event->button() == Qt::RightButton)
{
if (_rightDown)
{
_rightDown = false;
_osgContext->getEventQueue()->mouseButtonRelease(
widgetToNativeCoordinate(event->x()),
widgetToNativeCoordinate(event->y()), button);
}
}
}
void mouseMoveEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
return;
}
_osgContext->getEventQueue()->mouseMotion(
widgetToNativeCoordinate(event->x()),
widgetToNativeCoordinate(event->y()));
}
void mouseDoubleClickEvent(QMouseEvent* event)
{
int button = qtButtonToOsgButton(event);
if (event->button() == Qt::LeftButton)
{
_leftDown = true;
}
else if (event->button() == Qt::MidButton)
{
_middleDown = true;
}
else if (event->button() == Qt::RightButton)
{
_rightDown = true;
}
_osgContext->getEventQueue()->mouseDoubleButtonPress(
widgetToNativeCoordinate(event->x()),
widgetToNativeCoordinate(event->y()), button);
}
int widgetToNativeCoordinate(int widgetValue)
{
return widgetValue * devicePixelRatio();
}
int nativeToWidgetCoordinate(int nativeValue)
{
return nativeValue / devicePixelRatio();
}
void wheelEvent(QWheelEvent* wheelEvent)
{
osgGA::GUIEventAdapter::ScrollingMotion motion;
// If alt IS down, orientation is farked due to Qt being stupid. Ignore it.
if (wheelEvent->orientation() == Qt::Horizontal && wheelEvent->modifiers() != Qt::ALT)
{
if (wheelEvent->delta() < 0)
{
motion = osgGA::GUIEventAdapter::SCROLL_RIGHT;
}
else
{
motion = osgGA::GUIEventAdapter::SCROLL_LEFT;
}
}
else
{
if (wheelEvent->delta() < 0)
{
motion = osgGA::GUIEventAdapter::SCROLL_DOWN;
}
else
{
motion = osgGA::GUIEventAdapter::SCROLL_UP;
}
}
osgGA::GUIEventAdapter* event =
_osgContext->getEventQueue()->createEvent();
event->setEventType(osgGA::GUIEventAdapter::SCROLL);
event->setScrollingMotionDelta(0., (float)(wheelEvent->delta()));
// set scrollingMotionDelta automagically sets the scrolling motion to
// Scroll2D. We get around that be creating the event explicitly
// so we can call setScrollingMotion ourselves
event->setScrollingMotion(motion);
event->setTime(_osgContext->getEventQueue()->getTime());
_osgContext->getEventQueue()->addEvent(event);
}
private:
osg::ref_ptr< OSGWidgetContext > _osgContext;
osg::ref_ptr< osgViewer::View > _view;
bool _leftDown = false;
bool _rightDown = false;
bool _middleDown = false;
};
struct AddWindowHandler : public osgGA::GUIEventHandler
{
AddWindowHandler()
{
}
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
switch (ea.getEventType())
{
case(osgGA::GUIEventAdapter::KEYUP):
if (ea.getKey() == 'q')
{
osg::GraphicsContext::Traits* traits = new osg::GraphicsContext::Traits();
traits->x = 0;
traits->y = 0;
traits->width = 500;
traits->height = 500;
traits->windowDecoration = false;
traits->doubleBuffer = true;
traits->vsync = vsync;
#if 0
if (firstWindow)
{
traits->sharedContext = firstWindow->osgContext();
}
#endif
OSGWidget* osgWidget = new OSGWidget(traits, nullptr, nullptr);
osgWidget->move(0, 0);
osgWidget->resize(traits->width, traits->height);
std::stringstream buf;
buf << "OSG " << s_viewer->getNumViews();
osgWidget->setWindowTitle(QString(buf.str().c_str()));
osgWidget->show();
osgWidget->view()->setSceneData(s_loadedNode.get());
osgWidget->osgContext()->realize();
s_viewer->addView(osgWidget->view());
osgWidget->view()->addEventHandler(new AddWindowHandler());
std::cout << "Window closed. Number of views " << s_viewer->getNumViews() << std::endl;
return true;
}
break;
default:
break;
};
return false;
}
};
class FrameRunner : public QObject
{
//Q_OBJECT
public:
FrameRunner()
{
timer = new QTimer(this);
timer->setInterval(0);
connect(timer, &QTimer::timeout, this, &FrameRunner::runFrame);
}
void start()
{
timer->start();
}
private slots:
void runFrame()
{
#if 0
s_viewer->frame();
#else
{
OE_PROFILING_ZONE_NAMED("advance");
s_viewer->advance();
}
{
OE_PROFILING_ZONE_NAMED("eventTraversal");
s_viewer->eventTraversal();
}
{
OE_PROFILING_ZONE_NAMED("updateTraversal");
s_viewer->updateTraversal();
}
{
OE_PROFILING_ZONE_NAMED("renderingTraversals");
s_viewer->renderingTraversals();
}
OE_PROFILING_FRAME_MARK;
#endif
}
QTimer* timer;
};
int
main(int argc, char** argv)
{
osgEarth::initialize();
osg::ArgumentParser args(&argc, argv);
// Disable checking of opengl thread afinity. We still need to make sure that it's only called on a single thread ourselves but this will allow us to avoid using qthreads for the render thread.
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv);
// Disable checking of opengl thread afinity. We still need to make sure that it's only called on a single thread ourselves but this will allow us to avoid using qthreads for the render thread.
//QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
osg::ref_ptr< osg::Node > loadedNode = osgDB::readNodeFiles(args);
s_loadedNode = loadedNode.get();
osgViewer::CompositeViewer viewer(args);
//viewer.setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
//viewer.setThreadingModel(osgViewer::ViewerBase::CullDrawThreadPerContext);
//viewer.setThreadingModel(osgViewer::ViewerBase::DrawThreadPerContext);
//viewer.setThreadingModel(osgViewer::ViewerBase::CullThreadPerCameraDrawThreadPerContext);
s_viewer = &viewer;
unsigned int numViews = 2;
args.read("--views", numViews);
// vsync on/off?
if (args.read("--vsync"))
vsync = true;
else if (args.read("--novsync"))
vsync = false;
unsigned int x = 0;
unsigned int y = 0;
OSGWidget* firstWindow = nullptr;
for (unsigned int i = 0; i < numViews; ++i)
{
osg::GraphicsContext::Traits* traits = new osg::GraphicsContext::Traits();
traits->x = 0;
traits->y = 0;
traits->width = 500;
traits->height = 500;
traits->windowDecoration = false;
traits->doubleBuffer = true;
traits->vsync = vsync;
// context sharing doesn't actually seem to work b/c osg doesn't properly share vao's. It thinks it can share them between contexts but it actually can't.
#if 0
if (firstWindow)
{
traits->sharedContext = firstWindow->osgContext();
}
#endif
OSGWidget* osgWidget = new OSGWidget(traits, nullptr, firstWindow);
if (!firstWindow)
{
firstWindow = osgWidget;
}
osgWidget->move(x, y);
osgWidget->resize(traits->width, traits->height);
std::stringstream buf;
buf << "OSG " << i;
osgWidget->setWindowTitle(QString(buf.str().c_str()));
osgWidget->show();
// Need to call realize b/c osg won't do it manually...
osgWidget->osgContext()->realize();
osgWidget->view()->setSceneData(loadedNode.get());
viewer.addView(osgWidget->view());
osgWidget->view()->addEventHandler(new AddWindowHandler());
x += 500;
if (x >= 2000)
{
x = 0;
y += 500;
}
}
// This is normally done in CompositeViewer::run() but we aren't calling that....
s_viewer->setReleaseContextAtEndOfFrameHint(false);
s_viewer->realize();
FrameRunner runner;
// Wait for the views to start up before we start calling frame.
QTimer::singleShot(5000, [&] {
runner.start();
});
return app.exec();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment