QtQuick drawable for OpenSceneGraph
Still work in progress, especially different threading modes are not supported yet, and may crash doing QSG synchronisation.
This commit is contained in:
parent
7824571ec2
commit
45cb6849b2
6 changed files with 409 additions and 12 deletions
|
@ -66,9 +66,6 @@ if (APPLE)
|
|||
CocoaHelpers.mm)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
|
||||
if (HAVE_QT)
|
||||
qt5_wrap_ui(uic_sources Launcher.ui
|
||||
EditRatingsFilterDialog.ui
|
||||
|
@ -159,10 +156,16 @@ if (HAVE_QT)
|
|||
|
||||
set_property(TARGET fglauncher PROPERTY AUTOMOC ON)
|
||||
target_link_libraries(fglauncher Qt5::Core Qt5::Widgets Qt5::Network Qt5::Qml Qt5::Quick Qt5::QuickWidgets SimGearCore)
|
||||
target_include_directories(fglauncher PRIVATE ${PROJECT_BINARY_DIR}/src/GUI)
|
||||
target_include_directories(fglauncher PRIVATE ${PROJECT_BINARY_DIR}/src/GUI
|
||||
${Qt5Quick_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
# needed for scrollbar helper, unfortunately
|
||||
target_include_directories(fglauncher PRIVATE ${Qt5Quick_PRIVATE_INCLUDE_DIRS})
|
||||
add_library(fgqmlui QQuickDrawable.cxx
|
||||
QQuickDrawable.hxx
|
||||
)
|
||||
|
||||
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
|
||||
target_link_libraries(fgqmlui Qt5::Quick Qt5::Network Qt5::Qml SimGearCore)
|
||||
target_include_directories(fgqmlui PRIVATE ${PROJECT_BINARY_DIR}/src/GUI)
|
||||
endif()
|
||||
|
||||
|
||||
|
|
337
src/GUI/QQuickDrawable.cxx
Normal file
337
src/GUI/QQuickDrawable.cxx
Normal file
|
@ -0,0 +1,337 @@
|
|||
|
||||
|
||||
#include "QQuickDrawable.hxx"
|
||||
|
||||
#include <QQuickRenderControl>
|
||||
#include <QQmlComponent>
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlContext>
|
||||
|
||||
#include <QThread>
|
||||
#include <QOpenGLContext>
|
||||
#include <QQuickWindow>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QQuickItem>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <osg/GraphicsContext>
|
||||
#include <osgViewer/GraphicsWindow>
|
||||
|
||||
#include <GUI/FGQmlInstance.hxx>
|
||||
#include <GUI/FGQmlPropertyNode.hxx>
|
||||
|
||||
#include <Viewer/GraphicsWindowQt5.hxx>
|
||||
|
||||
/**
|
||||
* Helper run on Graphics thread to retrive its Qt wrapper
|
||||
*/
|
||||
class RetriveGraphicsThreadOperation : public osg::GraphicsOperation
|
||||
{
|
||||
public:
|
||||
RetriveGraphicsThreadOperation()
|
||||
: osg::GraphicsOperation("RetriveGraphcisThread", false)
|
||||
{}
|
||||
|
||||
QThread* thread() const { return _result; }
|
||||
QOpenGLContext* context() const { return _context; }
|
||||
|
||||
virtual void operator () (osg::GraphicsContext* context) override
|
||||
{
|
||||
_result = QThread::currentThread();
|
||||
_context = QOpenGLContext::currentContext();
|
||||
if (!_context) {
|
||||
qFatal("Use a Qt-based GraphicsWindow/Context");
|
||||
// TODO: use ability to create a QOpenGLContext from native
|
||||
// however, this will also need event-handling to be re-written
|
||||
//_context = qtContextFromOSG(context);
|
||||
}
|
||||
}
|
||||
private:
|
||||
QThread* _result = nullptr;
|
||||
QOpenGLContext* _context = nullptr;
|
||||
};
|
||||
|
||||
#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:
|
||||
CustomRenderControl(QWindow* win)
|
||||
: _qWindow(win)
|
||||
{
|
||||
Q_ASSERT(win);
|
||||
}
|
||||
|
||||
QWindow* renderWindow(QPoint* offset) override
|
||||
{
|
||||
if (offset) {
|
||||
*offset = QPoint(0,0);
|
||||
}
|
||||
return _qWindow;
|
||||
}
|
||||
private:
|
||||
QWindow* _qWindow = nullptr;
|
||||
};
|
||||
|
||||
class QQuickDrawablePrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
CustomRenderControl* renderControl = nullptr;
|
||||
|
||||
// the window our osgViewer is using
|
||||
flightgear::GraphicsWindowQt5* graphicsWindow = nullptr;
|
||||
|
||||
QQmlComponent* qmlComponent = nullptr;
|
||||
QQmlEngine* qmlEngine = nullptr;
|
||||
bool syncRequired = true;
|
||||
|
||||
QQuickItem* rootItem = nullptr;
|
||||
|
||||
// this window is neither created()-ed nor shown but is needed by
|
||||
// QQuickRenderControl for historical reasons, and gives us a place to
|
||||
// inject events from the OSG side.
|
||||
QQuickWindow* quickWindow = nullptr;
|
||||
|
||||
QOpenGLContext* qtContext = nullptr;
|
||||
|
||||
public slots:
|
||||
void onComponentLoaded()
|
||||
{
|
||||
if (qmlComponent->isError()) {
|
||||
QList<QQmlError> errorList = qmlComponent->errors();
|
||||
Q_FOREACH (const QQmlError &error, errorList) {
|
||||
qWarning() << error.url() << error.line() << error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QObject *rootObject = qmlComponent->create();
|
||||
if (qmlComponent->isError()) {
|
||||
QList<QQmlError> errorList = qmlComponent->errors();
|
||||
Q_FOREACH (const QQmlError &error, errorList) {
|
||||
qWarning() << error.url() << error.line() << error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
rootItem = qobject_cast<QQuickItem *>(rootObject);
|
||||
if (!rootItem) {
|
||||
qWarning() << Q_FUNC_INFO << "root object not a QQuickItem" << rootObject;
|
||||
delete rootObject;
|
||||
return;
|
||||
}
|
||||
|
||||
// The root item is ready. Associate it with the window.
|
||||
rootItem->setParentItem(quickWindow->contentItem());
|
||||
syncRequired = true;
|
||||
}
|
||||
|
||||
void onSceneChanged()
|
||||
{
|
||||
syncRequired = true;
|
||||
}
|
||||
|
||||
void onRenderRequested()
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO;
|
||||
}
|
||||
|
||||
bool eventFilter(QObject* obj, QEvent* event) override
|
||||
{
|
||||
if (obj != graphicsWindow->getGLWindow()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto eventType = event->type();
|
||||
switch (eventType) {
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::Wheel:
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
{
|
||||
bool result = QCoreApplication::sendEvent(quickWindow, event);
|
||||
// result always seems to be true here, not useful
|
||||
if (result && event->isAccepted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static QObject* fgqmlinstance_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||
{
|
||||
Q_UNUSED(engine)
|
||||
Q_UNUSED(scriptEngine)
|
||||
|
||||
FGQmlInstance* instance = new FGQmlInstance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
QQuickDrawable::QQuickDrawable() :
|
||||
d(new QQuickDrawablePrivate)
|
||||
{
|
||||
setUseDisplayList(false);
|
||||
setDataVariance(Object::DYNAMIC);
|
||||
|
||||
osg::StateSet* stateSet = getOrCreateStateSet();
|
||||
stateSet->setRenderBinDetails(1001, "RenderBin");
|
||||
#if 0
|
||||
// speed optimization?
|
||||
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||
// We can do translucent menus, so why not. :-)
|
||||
stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA));
|
||||
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
|
||||
stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF);
|
||||
|
||||
stateSet->setTextureAttribute(0, new osg::TexEnv(osg::TexEnv::MODULATE));
|
||||
|
||||
stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
|
||||
stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
|
||||
#endif
|
||||
|
||||
static bool doneQmlRegistration = false;
|
||||
if (!doneQmlRegistration) {
|
||||
doneQmlRegistration = true;
|
||||
|
||||
// singleton system object
|
||||
qmlRegisterSingletonType<FGQmlInstance>("flightgear.org", 1, 0, "System", fgqmlinstance_provider);
|
||||
|
||||
// QML types
|
||||
qmlRegisterType<FGQmlPropertyNode>("flightgear.org", 1, 0, "Property");
|
||||
}
|
||||
}
|
||||
|
||||
QQuickDrawable::~QQuickDrawable()
|
||||
{
|
||||
delete d->qmlEngine;
|
||||
delete d->renderControl;
|
||||
delete d;
|
||||
}
|
||||
|
||||
void QQuickDrawable::setup(osgViewer::GraphicsWindow* gw)
|
||||
{
|
||||
osg::GraphicsContext* gc = gw;
|
||||
|
||||
// install operations on the graphics context to get the context
|
||||
osg::ref_ptr<RetriveGraphicsThreadOperation> op(new RetriveGraphicsThreadOperation);
|
||||
|
||||
gc->add(op);
|
||||
gc->runOperations();
|
||||
|
||||
// hopefully done now!
|
||||
|
||||
d->qtContext = op->context();
|
||||
d->graphicsWindow = dynamic_cast<flightgear::GraphicsWindowQt5*>(gw);
|
||||
if (!d->graphicsWindow) {
|
||||
qFatal("QQuick drawable requires a GraphicsWindowQt5 for now");
|
||||
}
|
||||
d->renderControl = new CustomRenderControl(d->graphicsWindow->getGLWindow());
|
||||
|
||||
d->quickWindow = new QQuickWindow(d->renderControl);
|
||||
d->quickWindow->setClearBeforeRendering(false);
|
||||
|
||||
d->qmlEngine = new QQmlEngine;
|
||||
if (!d->qmlEngine->incubationController())
|
||||
d->qmlEngine->setIncubationController(d->quickWindow->incubationController());
|
||||
|
||||
d->renderControl->initialize(d->qtContext);
|
||||
d->renderControl->prepareThread(op->thread());
|
||||
|
||||
QObject::connect(d->renderControl, &QQuickRenderControl::sceneChanged,
|
||||
d, &QQuickDrawablePrivate::onSceneChanged);
|
||||
QObject::connect(d->renderControl, &QQuickRenderControl::renderRequested,
|
||||
d, &QQuickDrawablePrivate::onRenderRequested);
|
||||
|
||||
// insert ourselves as a filter on the GraphicsWindow, so we can intercept
|
||||
// events directly and pass on to our window
|
||||
d->graphicsWindow->getGLWindow()->installEventFilter(d);
|
||||
}
|
||||
|
||||
void QQuickDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
|
||||
{
|
||||
if (d->syncRequired) {
|
||||
// should happen on the main thread
|
||||
d->renderControl->polishItems();
|
||||
|
||||
d->syncRequired = false;
|
||||
// must ensure the GUI thread is blocked for this
|
||||
d->renderControl->sync();
|
||||
// can unblock now
|
||||
}
|
||||
|
||||
QOpenGLFunctions* glFuncs = d->qtContext->functions();
|
||||
// prepare any state QQ2 needs
|
||||
d->quickWindow->resetOpenGLState();
|
||||
|
||||
// and reset these manually
|
||||
glFuncs->glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glFuncs->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glFuncs->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
glFuncs->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
d->renderControl->render();
|
||||
|
||||
glFuncs->glFlush();
|
||||
|
||||
}
|
||||
|
||||
void QQuickDrawable::setSource(QUrl url)
|
||||
{
|
||||
d->qmlComponent = new QQmlComponent(d->qmlEngine, url);
|
||||
if (d->qmlComponent->isLoading()) {
|
||||
QObject::connect(d->qmlComponent, &QQmlComponent::statusChanged,
|
||||
d, &QQuickDrawablePrivate::onComponentLoaded);
|
||||
} else {
|
||||
d->onComponentLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
void QQuickDrawable::resize(int width, int height)
|
||||
{
|
||||
// we need to unscale from physical pixels back to logical, otherwise we end up double-scaled.
|
||||
const float currentPixelRatio = d->graphicsWindow->getGLWindow()->devicePixelRatio();
|
||||
const int logicalWidth = static_cast<int>(width / currentPixelRatio);
|
||||
const int logicalHeight = static_cast<int>(height / currentPixelRatio);
|
||||
|
||||
if (d->rootItem) {
|
||||
d->rootItem->setWidth(logicalWidth);
|
||||
d->rootItem->setHeight(logicalHeight);
|
||||
}
|
||||
|
||||
d->quickWindow->setGeometry(0, 0, logicalWidth, logicalHeight);
|
||||
}
|
||||
|
||||
#include "QQuickDrawable.moc"
|
36
src/GUI/QQuickDrawable.hxx
Normal file
36
src/GUI/QQuickDrawable.hxx
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef FG_VIEWER_QUICK_DRAWABLE_HXX
|
||||
#define FG_VIEWER_QUICK_DRAWABLE_HXX
|
||||
|
||||
#include <osg/Drawable>
|
||||
#include <QUrl>
|
||||
|
||||
#include <osgViewer/GraphicsWindow>
|
||||
|
||||
class QQuickDrawablePrivate;
|
||||
|
||||
class QQuickDrawable : public osg::Drawable
|
||||
{
|
||||
public:
|
||||
QQuickDrawable();
|
||||
virtual ~QQuickDrawable();
|
||||
|
||||
virtual osg::Object* cloneType() const { return 0; }
|
||||
virtual osg::Object* clone(const osg::CopyOp& copyop) const { return 0; }
|
||||
|
||||
void drawImplementation(osg::RenderInfo& renderInfo) const override;
|
||||
|
||||
/** Return true, FGPanelNode does support accept(PrimitiveFunctor&). */
|
||||
// virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
|
||||
|
||||
//virtual void accept(osg::PrimitiveFunctor& functor) const;
|
||||
|
||||
void setup(osgViewer::GraphicsWindow* gw);
|
||||
|
||||
void setSource(QUrl url);
|
||||
|
||||
void resize(int width, int height);
|
||||
private:
|
||||
QQuickDrawablePrivate* d;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -184,6 +184,7 @@ endif()
|
|||
if (Qt5Core_FOUND)
|
||||
target_link_libraries(fgfs Qt5::Widgets fglauncher)
|
||||
set_property(TARGET fgfs PROPERTY AUTOMOC ON)
|
||||
target_link_libraries(fgfs fglauncher fgqmlui)
|
||||
endif()
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
|
|
|
@ -112,6 +112,9 @@
|
|||
#include "CameraGroup.hxx"
|
||||
#include "FGEventHandler.hxx"
|
||||
|
||||
#include <GUI/QQuickDrawable.hxx>
|
||||
#include <Viewer/GraphicsWindowQt5.hxx>
|
||||
|
||||
#include <plib/pu.h>
|
||||
|
||||
using namespace osg;
|
||||
|
@ -445,6 +448,7 @@ FGRenderer::preinit( void )
|
|||
_viewerSceneRoot->setName("viewerSceneRoot");
|
||||
viewer->setSceneData(_viewerSceneRoot);
|
||||
|
||||
_quickDrawable = nullptr;
|
||||
_splash = new SplashScreen;
|
||||
if (_classicalRenderer) {
|
||||
_viewerSceneRoot->addChild(_splash);
|
||||
|
@ -1540,13 +1544,24 @@ 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);
|
||||
geode->addDrawable(new SGPuDrawable);
|
||||
osg::Geode* geode = new osg::Geode;
|
||||
geode->addDrawable(new SGHUDDrawable);
|
||||
geode->addDrawable(new SGPuDrawable);
|
||||
|
||||
// Draw first (eg. before Canvas GUI)
|
||||
guiCamera->insertChild(0, geode);
|
||||
guiCamera->insertChild(0, FGPanelNode::create2DPanelNode());
|
||||
std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path");
|
||||
auto graphicsWindowQt = dynamic_cast<GraphicsWindowQt5*>(guiCamera->getGraphicsContext());
|
||||
|
||||
if (graphicsWindowQt && !rootQMLPath.empty()) {
|
||||
_quickDrawable = new QQuickDrawable;
|
||||
_quickDrawable->setup(graphicsWindowQt);
|
||||
|
||||
_quickDrawable->setSource(QUrl(QString::fromStdString(rootQMLPath)));
|
||||
geode->addDrawable(_quickDrawable);
|
||||
}
|
||||
|
||||
// Draw first (eg. before Canvas GUI)
|
||||
guiCamera->insertChild(0, geode);
|
||||
guiCamera->insertChild(0, FGPanelNode::create2DPanelNode());
|
||||
}
|
||||
|
||||
osg::Switch* sw = new osg::Switch;
|
||||
|
@ -1731,6 +1746,9 @@ FGRenderer::resize( int width, int height )
|
|||
|
||||
// update splash node if present ?
|
||||
_splash->resize(width, height);
|
||||
if (_quickDrawable) {
|
||||
_quickDrawable->resize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
typedef osgUtil::LineSegmentIntersector::Intersection Intersection;
|
||||
|
|
|
@ -44,6 +44,7 @@ class CameraGroup;
|
|||
class SGSky;
|
||||
class SGUpdateVisitor;
|
||||
class SplashScreen;
|
||||
class QQuickDrawable;
|
||||
|
||||
typedef std::vector<SGSceneryPick> PickList;
|
||||
|
||||
|
@ -191,6 +192,7 @@ protected:
|
|||
void setupRoot();
|
||||
|
||||
SplashScreen* _splash;
|
||||
QQuickDrawable* _quickDrawable;
|
||||
};
|
||||
|
||||
bool fgDumpSceneGraphToFile(const char* filename);
|
||||
|
|
Loading…
Add table
Reference in a new issue