1
0
Fork 0

UI: support OSG multi-threading modes, partially

Requires private header, so might need an additional package installed
on some Unixes.
This commit is contained in:
James Turner 2020-06-06 11:57:18 +01:00
parent 2ca06d5b69
commit 32ff21c1df
10 changed files with 194 additions and 108 deletions

View file

@ -372,7 +372,7 @@ endif (USE_DBUS)
## Qt5 setup setup
if (ENABLE_QT)
message(STATUS "Qt launcher enabled, checking for Qt >= 5.4 / qmake")
find_package(Qt5 5.4 COMPONENTS Widgets Network Qml Quick Svg)
find_package(Qt5 5.4 COMPONENTS Widgets Gui Network Qml Quick Svg)
if (Qt5Widgets_FOUND)
message(STATUS "Will enable Qt launcher GUI")
set(HAVE_QT 1)
@ -381,6 +381,13 @@ if (ENABLE_QT)
# don't try to build FGQCanvas if Qt wasn't found correctly
set(ENABLE_FGQCANVAS OFF)
endif()
if (TARGET Qt5::GuiPrivate)
message(STATUS "Have QtGUI private headers")
set(ENABLE_QQ_UI 1)
else()
message(STATUS "QtGui private headers not available.")
endif()
else()
message(STATUS "Qt support disabled")
set(ENABLE_FGQCANVAS OFF)

View file

@ -135,11 +135,16 @@ if (HAVE_QT)
${qml_sources})
set_property(TARGET fglauncher PROPERTY AUTOMOC ON)
target_link_libraries(fglauncher Qt5::Core Qt5::Widgets Qt5::Network Qt5::Qml Qt5::Quick Qt5::Svg SimGearCore)
target_include_directories(fglauncher PRIVATE ${PROJECT_BINARY_DIR}/src/GUI)
target_link_libraries(fglauncher Qt5::Core Qt5::Widgets Qt5::Network Qt5::Qml Qt5::Quick Qt5::Svg SimGearCore)
add_library(fgqmlui QQuickDrawable.cxx
QQuickDrawable.hxx
if (ENABLE_QQ_UI)
set(QQUI_SOURCES
QQuickDrawable.cxx
QQuickDrawable.hxx)
endif()
add_library(fgqmlui
QtQuickFGCanvasItem.cxx
QtQuickFGCanvasItem.hxx
PropertyItemModel.cxx
@ -188,12 +193,18 @@ if (HAVE_QT)
PathUrlHelper.hxx
DialogStateController.cxx
DialogStateController.hxx
${QQUI_SOURCES}
)
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
target_link_libraries(fgqmlui Qt5::Quick Qt5::Widgets Qt5::Network Qt5::Qml SimGearCore)
target_link_libraries(fgqmlui Qt5::Quick Qt5::Widgets Qt5::Network Qt5::Qml Qt5::Gui SimGearCore)
target_include_directories(fgqmlui PRIVATE ${PROJECT_BINARY_DIR}/src/GUI)
if (ENABLE_QQ_UI)
# this is a headers-only dependency, so we can include <private/qopenglcontext_p.h>
target_link_libraries(fgqmlui Qt5::GuiPrivate)
endif()
if (TARGET fgfs_qm_files)
add_dependencies(fglauncher fgfs_qm_files)
add_dependencies(fgqmlui fgfs_qm_files)

View file

@ -18,6 +18,8 @@
#include "config.h"
#include <simgear/compiler.h>
#include "QQuickDrawable.hxx"
#include <QQmlComponent>
@ -25,6 +27,7 @@
#include <QQmlEngine>
#include <QQuickRenderControl>
#include <QTimer>
#include <QCoreApplication>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
@ -33,6 +36,17 @@
#include <QSurfaceFormat>
#include <QThread>
// private Qt headers, needed to make glue work between Qt and OSG
// graphics window unfortunately.
#include <private/qopenglcontext_p.h>
#if defined(SG_MAC)
# include "fake_qguiapp_p.h"
#else
# include <private/qguiapplication_p.h>
#endif
#include <osg/GraphicsContext>
#include <osgGA/GUIEventAdapter>
#include <osgGA/GUIEventHandler>
@ -52,7 +66,6 @@
#include <plib/pu.h>
#endif
using namespace osgGA;
struct QtKey {
@ -103,32 +116,6 @@ const std::initializer_list<QtKey> keymapInit = {
std::vector<QtKey> global_keymap;
#if 0
class SyncPolishOperation : public osg::Operation
{
public:
SyncPolishOperation(QQuickRenderControl* rc) :
renderControl(rc)
{
setKeep(true);
}
virtual void operator () (osg::Object*) override
{
if (syncRequired) {
//syncRequired = false;
}
renderControl->polishItems();
}
bool syncRequired = true;
QQuickRenderControl* renderControl;
};
#endif
class CustomRenderControl : public QQuickRenderControl
{
public:
@ -154,6 +141,17 @@ class QQuickDrawablePrivate : public QObject
{
Q_OBJECT
public:
QQuickDrawablePrivate() :
renderControlInited(false)
{
}
~QQuickDrawablePrivate()
{
}
CustomRenderControl* renderControl = nullptr;
QQmlComponent* qmlComponent = nullptr;
@ -171,20 +169,23 @@ public:
// for making our adpoted context current
QWindow* foreignOSGWindow = nullptr;
QOpenGLContext* qtContext = nullptr;
osg::GraphicsContext* osgContext = nullptr;
std::atomic_bool renderControlInited;
std::atomic_bool syncPending;
void frameEvent()
{
if (syncRequired) {
renderControl->polishItems();
syncRequired = false;
syncPending = true;
if (QOpenGLContext::currentContext() != qtContext) {
bool ok = qtContext->makeCurrent(foreignOSGWindow);
if (!ok) {
SG_LOG(SG_GUI, SG_ALERT, "make current failed");
}
}
osgContext->add(flightgear::makeGraphicsOp("Sync QQ2 Render control", [this](osg::GraphicsContext*) {
QOpenGLContextPrivate::setCurrentContext(qtContext);
renderControl->sync();
syncPending = false;
}));
}
}
public slots:
@ -221,6 +222,23 @@ public slots:
rootItem->setHeight(quickWindow->height());
}
void initRenderControl()
{
qtContext = flightgear::qtContextFromOSG(osgContext);
#if QT_VERSION < 0x050600
SG_LOG(SG_GUI, SG_ALERT, "Qt < 5.6 was used to build FlightGear, multi-threading of QtQuick is not safe");
#else
renderControl->prepareThread(QThread::currentThread());
#endif
QOpenGLContextPrivate::setCurrentContext(qtContext);
QOpenGLContextPrivate::get(qtContext)->surface = foreignOSGWindow;
renderControl->initialize(qtContext);
renderControlInited = true;
}
void onSceneChanged()
{
syncRequired = true;
@ -230,8 +248,17 @@ public slots:
{
qWarning() << Q_FUNC_INFO;
}
void onWindowActiveFocusItemChanged()
{
if (quickWindow->activeFocusItem())
qInfo() << Q_FUNC_INFO << "Active focus item is now:" << quickWindow->activeFocusItem();
else
qInfo() << Q_FUNC_INFO << "Active focus cleared";
}
};
static QObject* fgqmlinstance_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
{
Q_UNUSED(engine)
@ -260,8 +287,11 @@ public:
{
SG_UNUSED(aNode);
SG_UNUSED(root);
QTimer::singleShot(0, [this]() {
std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path");
_drawable->reload(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath)));
});
return true;
}
@ -341,6 +371,10 @@ public:
case (GUIEventAdapter::KEYDOWN):
case (GUIEventAdapter::KEYUP): {
if (!_drawable->quickWindow->activeFocusItem()) {
return false;
}
const bool isKeyRelease = (ea.getEventType() == GUIEventAdapter::KEYUP);
const auto& key = osgKeyToQt(ea.getKey());
QString s = key.s;
@ -465,6 +499,8 @@ QQuickDrawable::QQuickDrawable() : d(new QQuickDrawablePrivate)
qmlRegisterType<FGQmlPropertyNode>("FlightGear", 1, 0, "Property");
qmlRegisterType<DialogStateController>("FlightGear", 1, 0, "DialogStateController");
}
globals->get_commands()->addCommandObject("reload-quick-gui", new ReloadCommand(this));
}
QQuickDrawable::~QQuickDrawable()
@ -476,29 +512,22 @@ QQuickDrawable::~QQuickDrawable()
void QQuickDrawable::setup(osgViewer::GraphicsWindow *gw, osgViewer::Viewer *viewer)
{
osg::GraphicsContext* gc = gw;
osg::ref_ptr<flightgear::RetriveGraphicsThreadOperation> op(new flightgear::RetriveGraphicsThreadOperation);
gc->add(op);
gc->runOperations();
// hopefully done now!
d->qtContext = op->context();
// d->qtContext->setFormat(format);
// none of this stuff needs the context current, so we can do it
// all safely on the main thread
d->foreignOSGWindow = flightgear::qtWindowFromOSG(gw);
// d->foreignOSGWindow->setFormat(format);
d->foreignOSGWindow->setSurfaceType(QSurface::OpenGLSurface);
// QWindow::requestActive would do QPA::makeKey, but on macOS this
// is a no-op for foreign windows. So we're going to manually set
// the focus window!
QGuiApplicationPrivate::focus_window = d->foreignOSGWindow;
d->osgContext = gc;
d->renderControl = new CustomRenderControl(d->foreignOSGWindow);
d->quickWindow = new QQuickWindow(d->renderControl);
bool ok = d->qtContext->makeCurrent(d->foreignOSGWindow);
if (!ok) {
SG_LOG(SG_GUI, SG_ALERT, "context not current");
}
d->quickWindow->setClearBeforeRendering(false);
d->qmlEngine = new QQmlEngine;
@ -511,13 +540,9 @@ void QQuickDrawable::setup(osgViewer::GraphicsWindow *gw, osgViewer::Viewer *vie
if (!d->qmlEngine->incubationController())
d->qmlEngine->setIncubationController(d->quickWindow->incubationController());
d->renderControl->initialize(d->qtContext);
// QObject::connect(d->quickWindow, &QQuickWindow::activeFocusItemChanged,
// d.get(), &QQuickDrawablePrivate::onWindowActiveFocusItemChanged);
#if QT_VERSION < 0x050600
SG_LOG(SG_GUI, SG_ALERT, "Qt < 5.6 was used to build FlightGear, multi-threading of QtQuick is not safe");
#else
d->renderControl->prepareThread(op->thread());
#endif
QObject::connect(d->renderControl, &QQuickRenderControl::sceneChanged,
d.get(), &QQuickDrawablePrivate::onSceneChanged);
QObject::connect(d->renderControl, &QQuickRenderControl::renderRequested,
@ -529,6 +554,14 @@ void QQuickDrawable::setup(osgViewer::GraphicsWindow *gw, osgViewer::Viewer *vie
void QQuickDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
{
if (!d->renderControlInited) {
d->initRenderControl();
}
if (QOpenGLContext::currentContext() != d->qtContext) {
QOpenGLContextPrivate::setCurrentContext(d->qtContext);
}
QOpenGLFunctions* glFuncs = d->qtContext->functions();
// prepare any state QQ2 needs
d->quickWindow->resetOpenGLState();
@ -580,7 +613,7 @@ void QQuickDrawable::resize(int width, int height)
d->rootItem->setHeight(logicalHeight);
}
SG_LOG(SG_GUI, SG_INFO, "Resize:, lw=" << logicalWidth << ", lh=" << logicalHeight);
// SG_LOG(SG_GUI, SG_INFO, "Resize:, lw=" << logicalWidth << ", lh=" << logicalHeight);
d->quickWindow->setGeometry(0, 0, logicalWidth, logicalHeight);
}

23
src/GUI/fake_qguiapp_p.h Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <QPointer>
QT_BEGIN_NAMESPACE
// fake the qguiapplication_p.h header, which is problematic
// to include directly on macOS, becuase we don't synchronize
// our macOS min version with the Qt one. As a result, we
// get errors including the real version. To work around
// this, delcare the one symnbol we need
class Q_GUI_EXPORT QGuiApplicationPrivate
{
public:
#if QT_VERSION < QT_VERSION_CHECK(5, 12,7)
static QWindow* focus_window;
#else
static QPointer<QWindow> focus_window;
#endif
};
QT_END_NAMESPACE

View file

@ -51,6 +51,7 @@
#cmakedefine ENABLE_PLIB_JOYSTICK
#cmakedefine HAVE_QT
#cmakedefine ENABLE_QQ_UI
#cmakedefine HAVE_SYS_TIME_H
#cmakedefine HAVE_SYS_TIMEB_H

View file

@ -117,10 +117,30 @@ QWindow* qtWindowFromOSG(osgViewer::GraphicsWindow* graphicsWindow)
#endif
void RetriveGraphicsThreadOperation::operator()(osg::GraphicsContext* context)
class GraphicsFunctorWrapper : public osg::GraphicsOperation
{
_result = QThread::currentThread();
_context = qtContextFromOSG(context);
public:
GraphicsFunctorWrapper(const std::string& name, GraphicsFunctor gf) :
osg::GraphicsOperation(name, false),
_functor(gf)
{
}
void operator()(osg::GraphicsContext* gc) override
{
_functor(gc);
}
private:
GraphicsFunctor _functor;
};
osg::ref_ptr<osg::GraphicsOperation> makeGraphicsOp(const std::string& name, GraphicsFunctor func)
{
osg::ref_ptr<osg::GraphicsOperation> r;
r = new GraphicsFunctorWrapper(name, func);
return r;
}
} // of namespace

View file

@ -16,6 +16,9 @@
#pragma once
#include <functional>
#include <string>
#include <osgViewer/GraphicsWindow>
#include <osg/GraphicsContext>
@ -29,25 +32,8 @@ namespace flightgear
QOpenGLContext* qtContextFromOSG(osg::GraphicsContext* context);
QWindow* qtWindowFromOSG(osgViewer::GraphicsWindow* graphicsWindow);
/**
* Helper run on Graphics thread to retrive its Qt wrapper
*/
class RetriveGraphicsThreadOperation : public osg::GraphicsOperation
{
public:
RetriveGraphicsThreadOperation()
: osg::GraphicsOperation("RetriveGraphicsThread", false)
{
}
QThread* thread() const { return _result; }
QOpenGLContext* context() const { return _context; }
void operator()(osg::GraphicsContext* context) override;
private:
QThread* _result = nullptr;
QOpenGLContext* _context = nullptr;
};
using GraphicsFunctor = std::function<void(osg::GraphicsContext* context)>;
osg::ref_ptr<osg::GraphicsOperation> makeGraphicsOp(const std::string& name, GraphicsFunctor func);
}

View file

@ -108,7 +108,7 @@
#include "CameraGroup.hxx"
#include "FGEventHandler.hxx"
#if defined(HAVE_QT)
#if defined(ENABLE_QQ_UI)
#include <GUI/QQuickDrawable.hxx>
#endif
@ -600,18 +600,18 @@ FGRenderer::setupView( void )
stateSet->setAttributeAndModes(fog);
stateSet->setUpdateCallback(new FGFogEnableUpdateCallback);
// plug in the GUI
osg::Camera* guiCamera = getGUICamera(CameraGroup::getDefault());
if (guiCamera) {
osg::Geode* geode = new osg::Geode;
geode->addDrawable(new SGHUDDrawable);
osg::Geode* hudGeode = new osg::Geode;
hudGeode->addDrawable(new SGHUDDrawable);
guiCamera->addChild(hudGeode);
#if defined(HAVE_PUI)
_puiCamera = new flightgear::PUICamera;
_puiCamera->init(guiCamera, viewer);
#endif
#if defined(HAVE_QT)
#if defined(ENABLE_QQ_UI)
std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path");
auto graphicsWindow = dynamic_cast<osgViewer::GraphicsWindow*>(guiCamera->getGraphicsContext());
@ -620,10 +620,12 @@ FGRenderer::setupView( void )
_quickDrawable->setup(graphicsWindow, viewer);
_quickDrawable->setSource(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath)));
geode->addDrawable(_quickDrawable);
osg::Geode* qqGeode = new osg::Geode;
qqGeode->addDrawable(_quickDrawable);
guiCamera->addChild(qqGeode);
}
#endif
guiCamera->addChild(geode);
guiCamera->insertChild(0, FGPanelNode::create2DPanelNode());
}
@ -803,7 +805,7 @@ FGRenderer::resize( int width, int height )
// update splash node if present
_splash->resize(width, height);
#if defined(HAVE_QT)
#if defined(ENABLE_QQ_UI)
if (_quickDrawable) {
_quickDrawable->resize(width, height);
}

View file

@ -108,7 +108,7 @@
#include "CameraGroup.hxx"
#include "FGEventHandler.hxx"
#if defined(HAVE_QT)
#if defined(ENABLE_QQ_UI)
#include <GUI/QQuickDrawable.hxx>
#endif
@ -1498,15 +1498,16 @@ FGRenderer::setupView( void )
// plug in the GUI
osg::Camera* guiCamera = getGUICamera(CameraGroup::getDefault());
if (guiCamera) {
osg::Geode* geode = new osg::Geode;
geode->addDrawable(new SGHUDDrawable);
osg::Geode* hudGeode = new osg::Geode;
hudGeode->addDrawable(new SGHUDDrawable);
guiCamera->addChild(hudGeode);
#if defined(HAVE_PUI)
_puiCamera = new flightgear::PUICamera;
_puiCamera->init(guiCamera, viewer);
#endif
#if defined(HAVE_QT)
#if defined(ENABLE_QQ_UI)
std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path");
auto graphicsWindow = dynamic_cast<osgViewer::GraphicsWindow*>(guiCamera->getGraphicsContext());
@ -1515,10 +1516,12 @@ FGRenderer::setupView( void )
_quickDrawable->setup(graphicsWindow, viewer);
_quickDrawable->setSource(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath)));
geode->addDrawable(_quickDrawable);
osg::Geode* qqGeode = new osg::Geode;
qqGeode->addDrawable(_quickDrawable);
guiCamera->addChild(qqGeode);
}
#endif
guiCamera->addChild(geode);
guiCamera->insertChild(0, FGPanelNode::create2DPanelNode());
}
@ -1717,7 +1720,7 @@ FGRenderer::resize( int width, int height )
// update splash node if present
_splash->resize(width, height);
#if defined(HAVE_QT)
#if defined(ENABLE_QQ_UI)
if (_quickDrawable) {
_quickDrawable->resize(width, height);
}

View file

@ -194,7 +194,7 @@ protected:
void setupRoot();
SplashScreen* _splash;
QQuickDrawable* _quickDrawable;
QQuickDrawable* _quickDrawable = nullptr;
flightgear::PUICamera* _puiCamera = nullptr;
};