#include "QQuickDrawable.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * 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 errorList = qmlComponent->errors(); Q_FOREACH (const QQmlError &error, errorList) { qWarning() << error.url() << error.line() << error; } return; } QObject *rootObject = qmlComponent->create(); if (qmlComponent->isError()) { QList errorList = qmlComponent->errors(); Q_FOREACH (const QQmlError &error, errorList) { qWarning() << error.url() << error.line() << error; } return; } rootItem = qobject_cast(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("flightgear.org", 1, 0, "System", fgqmlinstance_provider); // QML types qmlRegisterType("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 op(new RetriveGraphicsThreadOperation); gc->add(op); gc->runOperations(); // hopefully done now! d->qtContext = op->context(); d->graphicsWindow = dynamic_cast(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); #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, &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(width / currentPixelRatio); const int logicalHeight = static_cast(height / currentPixelRatio); if (d->rootItem) { d->rootItem->setWidth(logicalWidth); d->rootItem->setHeight(logicalHeight); } d->quickWindow->setGeometry(0, 0, logicalWidth, logicalHeight); } #include "QQuickDrawable.moc"