1 // QQuickDrawable.cxx - OSG Drawable using a QQuickRenderControl to draw
2 //
3 // Copyright (C) 2019 James Turner <james@flightgear.org>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19 #include "config.h"
20
21 #include <simgear/compiler.h>
22 #include <atomic>
23
24 #include "QQuickDrawable.hxx"
25
26 #include <QQmlComponent>
27 #include <QQmlContext>
28 #include <QQmlEngine>
29 #include <QQuickRenderControl>
30
31 #include <QTimer>
32 #include <QCoreApplication>
33 #include <QOpenGLContext>
34 #include <QOpenGLFunctions>
35 #include <QQuickItem>
36 #include <QQuickWindow>
37 #include <QSurfaceFormat>
38 #include <QThread>
39
40
41 // private Qt headers, needed to make glue work between Qt and OSG
42 // graphics window unfortunately.
43 #include <private/qopenglcontext_p.h>
44
45 #if defined(SG_MAC)
46 # include "fake_qguiapp_p.h"
47 #else
48 # include <private/qguiapplication_p.h>
49 #endif
50
51 #include <osg/GraphicsContext>
52 #include <osgGA/GUIEventAdapter>
53 #include <osgGA/GUIEventHandler>
54 #include <osgViewer/GraphicsWindow>
55 #include <osgViewer/Viewer>
56
57 #include <GUI/DialogStateController.hxx>
58 #include <GUI/FGQQWindowManager.hxx>
59 #include <GUI/FGQmlInstance.hxx>
60 #include <GUI/FGQmlPropertyNode.hxx>
61 #include <Main/fg_props.hxx>
62 #include <Main/globals.hxx>
63 #include <Viewer/OSGQtAdaption.hxx>
64 #include <simgear/structure/commands.hxx>
65
66 #if defined(HAVE_PUI)
67 #include "FlightGear_pu.h"
68 #endif
69
70 using namespace osgGA;
71
72 struct QtKey {
QtKeyQtKey73 QtKey(int _o, int _q, QString _s = {}) : osg(_o), qt(_q), s(_s) {}
74
75 int osg;
76 int qt;
77 QString s;
78 };
79
80 const std::initializer_list<QtKey> keymapInit = {
81 // contains all the key mappings except 0..9 and A..Z which are generated
82 // programatically
83
84 {GUIEventAdapter::KEY_Space, Qt::Key_Space, " "},
85 {GUIEventAdapter::KEY_Escape, Qt::Key_Escape, "\x1B"},
86 {GUIEventAdapter::KEY_Return, Qt::Key_Return, "\r"},
87 {GUIEventAdapter::KEY_Tab, Qt::Key_Tab, "\t"},
88 {GUIEventAdapter::KEY_BackSpace, Qt::Key_Backspace, "\x08"},
89 {GUIEventAdapter::KEY_Delete, Qt::Key_Delete, "\x7f"},
90
91 {GUIEventAdapter::KEY_Period, Qt::Key_Period, "."},
92 {GUIEventAdapter::KEY_Comma, Qt::Key_Comma, ","},
93 {GUIEventAdapter::KEY_Colon, Qt::Key_Colon, ":"},
94 {GUIEventAdapter::KEY_Quote, Qt::Key_QuoteLeft, "'"},
95 {GUIEventAdapter::KEY_Quotedbl, Qt::Key_QuoteDbl, "\""},
96 {GUIEventAdapter::KEY_Underscore, Qt::Key_Underscore, "_"},
97 {GUIEventAdapter::KEY_Plus, Qt::Key_Plus, "+"},
98 {GUIEventAdapter::KEY_Minus, Qt::Key_Minus, "-"},
99 {GUIEventAdapter::KEY_Asterisk, Qt::Key_Asterisk, "*"},
100 {GUIEventAdapter::KEY_Equals, Qt::Key_Equal, "="},
101 {GUIEventAdapter::KEY_Slash, Qt::Key_Slash, "/"},
102
103 {GUIEventAdapter::KEY_Left, Qt::Key_Left},
104 {GUIEventAdapter::KEY_Right, Qt::Key_Right},
105 {GUIEventAdapter::KEY_Up, Qt::Key_Up},
106 {GUIEventAdapter::KEY_Down, Qt::Key_Down},
107
108 {GUIEventAdapter::KEY_Shift_L, Qt::Key_Shift},
109 {GUIEventAdapter::KEY_Shift_R, Qt::Key_Shift},
110 {GUIEventAdapter::KEY_Control_L, Qt::Key_Control},
111 {GUIEventAdapter::KEY_Control_R, Qt::Key_Control},
112 {GUIEventAdapter::KEY_Meta_L, Qt::Key_Meta},
113 {GUIEventAdapter::KEY_Meta_R, Qt::Key_Meta},
114
115
116 };
117
118 std::vector<QtKey> global_keymap;
119
120 class CustomRenderControl : public QQuickRenderControl
121 {
122 public:
CustomRenderControl(QWindow * win)123 CustomRenderControl(QWindow* win)
124 : _qWindow(win)
125 {
126 // Q_ASSERT(win);
127 }
128
renderWindow(QPoint * offset)129 QWindow* renderWindow(QPoint* offset) override
130 {
131 if (offset) {
132 *offset = QPoint(0, 0);
133 }
134 return _qWindow;
135 }
136
137 private:
138 QWindow* _qWindow = nullptr;
139 };
140
141 class QQuickDrawablePrivate : public QObject
142 {
143 Q_OBJECT
144 public:
QQuickDrawablePrivate()145 QQuickDrawablePrivate() :
146 renderControlInited(false)
147 {
148
149 }
150
~QQuickDrawablePrivate()151 ~QQuickDrawablePrivate()
152 {
153
154 }
155
156 CustomRenderControl* renderControl = nullptr;
157
158 QQmlComponent* qmlComponent = nullptr;
159 QQmlEngine* qmlEngine = nullptr;
160 bool syncRequired = true;
161
162 QQuickItem* rootItem = nullptr;
163
164 // this window is neither created()-ed nor shown but is needed by
165 // QQuickRenderControl for historical reasons, and gives us a place to
166 // inject events from the OSG side.
167 QQuickWindow* quickWindow = nullptr;
168
169 // window representing the OSG window, needed
170 // for making our adpoted context current
171 QWindow* foreignOSGWindow = nullptr;
172 QOpenGLContext* qtContext = nullptr;
173 osg::GraphicsContext* osgContext = nullptr;
174
175 std::atomic_bool renderControlInited;
176 std::atomic_bool syncPending;
177
frameEvent()178 void frameEvent()
179 {
180 if (syncRequired) {
181 renderControl->polishItems();
182 syncRequired = false;
183 syncPending = true;
184
185 osgContext->add(flightgear::makeGraphicsOp("Sync QQ2 Render control", [this](osg::GraphicsContext*) {
186 QOpenGLContextPrivate::setCurrentContext(qtContext);
187 renderControl->sync();
188 syncPending = false;
189 }));
190 }
191 }
192 public slots:
onComponentLoaded()193 void onComponentLoaded()
194 {
195 if (qmlComponent->isError()) {
196 QList<QQmlError> errorList = qmlComponent->errors();
197 Q_FOREACH (const QQmlError& error, errorList) {
198 qWarning() << error.url() << error.line() << error;
199 }
200 return;
201 }
202
203 QObject* rootObject = qmlComponent->create();
204 if (qmlComponent->isError()) {
205 QList<QQmlError> errorList = qmlComponent->errors();
206 Q_FOREACH (const QQmlError& error, errorList) {
207 qWarning() << error.url() << error.line() << error;
208 }
209 return;
210 }
211
212 rootItem = qobject_cast<QQuickItem*>(rootObject);
213 if (!rootItem) {
214 qWarning() << Q_FUNC_INFO << "root object not a QQuickItem" << rootObject;
215 delete rootObject;
216 return;
217 }
218
219 // The root item is ready. Associate it with the window.
220 rootItem->setParentItem(quickWindow->contentItem());
221 syncRequired = true;
222 rootItem->setWidth(quickWindow->width());
223 rootItem->setHeight(quickWindow->height());
224 }
225
initRenderControl()226 void initRenderControl()
227 {
228 qtContext = flightgear::qtContextFromOSG(osgContext);
229
230 #if QT_VERSION < 0x050600
231 SG_LOG(SG_GUI, SG_ALERT, "Qt < 5.6 was used to build FlightGear, multi-threading of QtQuick is not safe");
232 #else
233 renderControl->prepareThread(QThread::currentThread());
234 #endif
235
236 QOpenGLContextPrivate::setCurrentContext(qtContext);
237 QOpenGLContextPrivate::get(qtContext)->surface = foreignOSGWindow;
238 renderControl->initialize(qtContext);
239
240 renderControlInited = true;
241 }
242
onSceneChanged()243 void onSceneChanged()
244 {
245 syncRequired = true;
246 }
247
onRenderRequested()248 void onRenderRequested()
249 {
250 qWarning() << Q_FUNC_INFO;
251 }
252
onWindowActiveFocusItemChanged()253 void onWindowActiveFocusItemChanged()
254 {
255 if (quickWindow->activeFocusItem())
256 qInfo() << Q_FUNC_INFO << "Active focus item is now:" << quickWindow->activeFocusItem();
257 else
258 qInfo() << Q_FUNC_INFO << "Active focus cleared";
259 }
260 };
261
262
fgqmlinstance_provider(QQmlEngine * engine,QJSEngine * scriptEngine)263 static QObject* fgqmlinstance_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
264 {
265 Q_UNUSED(engine)
266 Q_UNUSED(scriptEngine)
267
268 FGQmlInstance* instance = new FGQmlInstance;
269 return instance;
270 }
271
fgqq_windowManager_provider(QQmlEngine * engine,QJSEngine * scriptEngine)272 static QObject* fgqq_windowManager_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
273 {
274 Q_UNUSED(scriptEngine)
275
276 FGQQWindowManager* instance = new FGQQWindowManager(engine);
277 return instance;
278 }
279
280 class ReloadCommand : public SGCommandMgr::Command
281 {
282 public:
ReloadCommand(QQuickDrawable * qq)283 ReloadCommand(QQuickDrawable* qq) : _drawable(qq)
284 {
285 }
286
operator ()(const SGPropertyNode * aNode,SGPropertyNode * root)287 bool operator()(const SGPropertyNode* aNode, SGPropertyNode* root) override
288 {
289 SG_UNUSED(aNode);
290 SG_UNUSED(root);
291
292 QTimer::singleShot(0, [this]() {
293 std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path");
294 _drawable->reload(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath)));
295 });
296 return true;
297 }
298
299 private:
300 QQuickDrawable* _drawable;
301 };
302
303 class QuickEventHandler : public osgGA::GUIEventHandler
304 {
305 public:
QuickEventHandler(QQuickDrawablePrivate * p)306 QuickEventHandler(QQuickDrawablePrivate* p) : _drawable(p)
307 {
308 populateKeymap();
309 }
310
handle(const GUIEventAdapter & ea,GUIActionAdapter & aa,osg::Object *,osg::NodeVisitor *)311 bool handle(const GUIEventAdapter& ea, GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*) override
312 {
313 Q_UNUSED(aa);
314
315 if (ea.getHandled()) return false;
316
317 // frame event, ho hum ...
318 if (ea.getEventType() == GUIEventAdapter::FRAME) {
319 _drawable->frameEvent();
320 return false;
321 }
322
323 // Qt expects increasing downward mouse coords
324 const float fixedY = (ea.getMouseYOrientation() == GUIEventAdapter::Y_INCREASING_UPWARDS) ? ea.getWindowHeight() - ea.getY() : ea.getY();
325 const double pixelRatio = _drawable->foreignOSGWindow->devicePixelRatio();
326
327 QPointF pointInWindow{ea.getX() / pixelRatio, fixedY / pixelRatio};
328 QPointF screenPt = pointInWindow +
329 QPointF{ea.getWindowX() / pixelRatio, ea.getWindowY() / pixelRatio};
330
331 // const int scaledX = static_cast<int>(ea.getX() / static_pixelRatio);
332 // const int scaledY = static_cast<int>(fixedY / static_pixelRatio);
333
334 switch (ea.getEventType()) {
335 case (GUIEventAdapter::DRAG):
336 case (GUIEventAdapter::MOVE): {
337 QMouseEvent m(QEvent::MouseMove, pointInWindow, pointInWindow, screenPt,
338 Qt::NoButton,
339 osgButtonMaskToQt(ea), osgModifiersToQt(ea));
340 QCoreApplication::sendEvent(_drawable->quickWindow, &m);
341 return m.isAccepted();
342 }
343
344 case (GUIEventAdapter::PUSH):
345 case (GUIEventAdapter::RELEASE): {
346 const bool isUp = (ea.getEventType() == GUIEventAdapter::RELEASE);
347 QMouseEvent m(isUp ? QEvent::MouseButtonRelease : QEvent::MouseButtonPress,
348 pointInWindow, pointInWindow, screenPt,
349 osgButtonToQt(ea),
350 osgButtonMaskToQt(ea), osgModifiersToQt(ea));
351 QCoreApplication::sendEvent(_drawable->quickWindow, &m);
352
353 if (!isUp) {
354 if (m.isAccepted()) {
355 // deactivate PUI
356 auto active = puActiveWidget();
357 if (active) {
358 active->invokeDownCallback();
359 }
360 }
361
362 // on mouse downs which we don't accept, take focus back
363 // qInfo() << "Clearing QQ focus";
364 // auto focused = _drawable->quickWindow->activeFocusItem();
365 // if (focused) {
366 // focused->setFocus(false, Qt::MouseFocusReason);
367 // }
368 }
369
370 return m.isAccepted();
371 }
372
373 case (GUIEventAdapter::KEYDOWN):
374 case (GUIEventAdapter::KEYUP): {
375 if (!_drawable->quickWindow->activeFocusItem()) {
376 return false;
377 }
378
379 const bool isKeyRelease = (ea.getEventType() == GUIEventAdapter::KEYUP);
380 const auto& key = osgKeyToQt(ea.getKey());
381 QString s = key.s;
382
383 QKeyEvent k(isKeyRelease ? QEvent::KeyRelease : QEvent::KeyPress,
384 key.qt, osgModifiersToQt(ea), s);
385 QCoreApplication::sendEvent(_drawable->quickWindow, &k);
386 return k.isAccepted();
387 }
388
389 default:
390 return false;
391 }
392
393 return false;
394 }
395 private:
osgButtonMaskToQt(const osgGA::GUIEventAdapter & ea) const396 Qt::MouseButtons osgButtonMaskToQt(const osgGA::GUIEventAdapter& ea) const
397 {
398 const int mask = ea.getButtonMask();
399 Qt::MouseButtons result = Qt::NoButton;
400 if (mask & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
401 result |= Qt::LeftButton;
402
403 if (mask & osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON)
404 result |= Qt::MiddleButton;
405
406 if (mask & osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON)
407 result |= Qt::RightButton;
408
409 return result;
410 }
411
osgButtonToQt(const osgGA::GUIEventAdapter & ea) const412 Qt::MouseButton osgButtonToQt(const osgGA::GUIEventAdapter& ea) const
413 {
414 switch (ea.getButton()) {
415 case osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON: return Qt::LeftButton;
416 case osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON: return Qt::MiddleButton;
417 case osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON: return Qt::RightButton;
418
419 default:
420 break;
421 }
422
423 return Qt::NoButton;
424 }
425
osgModifiersToQt(const osgGA::GUIEventAdapter & ea) const426 Qt::KeyboardModifiers osgModifiersToQt(const osgGA::GUIEventAdapter& ea) const
427 {
428 Qt::KeyboardModifiers result = Qt::NoModifier;
429 const int mask = ea.getModKeyMask();
430 if (mask & osgGA::GUIEventAdapter::MODKEY_ALT) result |= Qt::AltModifier;
431 if (mask & osgGA::GUIEventAdapter::MODKEY_CTRL) result |= Qt::ControlModifier;
432 if (mask & osgGA::GUIEventAdapter::MODKEY_META) result |= Qt::MetaModifier;
433 if (mask & osgGA::GUIEventAdapter::MODKEY_SHIFT) result |= Qt::ShiftModifier;
434 return result;
435 }
436
osgKeyToQt(int code) const437 QtKey osgKeyToQt(int code) const
438 {
439 auto it = std::lower_bound(global_keymap.begin(), global_keymap.end(),
440 QtKey{code, 0, {}},
441 [](const QtKey& a, const QtKey& b) { return a.osg < b.osg; });
442 if ((it == global_keymap.end()) || (it->osg != code)) {
443 qWarning() << "no mapping defined for OSG key:" << code;
444 return {0, 0, ""};
445 }
446
447 return *it;
448 }
449
populateKeymap()450 void populateKeymap()
451 {
452 if (!global_keymap.empty())
453 return;
454
455 // regular keymappsing for A..Z and 0..9
456 for (int i = 0; i < 10; ++i) {
457 global_keymap.emplace_back(GUIEventAdapter::KEY_0 + i, Qt::Key_0 + i, QString::number(i));
458 }
459
460 for (int i = 0; i < 26; ++i) {
461 global_keymap.emplace_back(GUIEventAdapter::KEY_A + i, Qt::Key_A + i, QChar::fromLatin1('a' + i));
462 }
463
464 for (int i = 0; i < 26; ++i) {
465 global_keymap.emplace_back('A' + i, Qt::Key_A + i, QChar::fromLatin1('A' + i));
466 }
467
468 // custom key mappsing
469 global_keymap.insert(global_keymap.end(), keymapInit);
470
471 // sort by OSG code for fast lookups
472 std::sort(global_keymap.begin(), global_keymap.end(),
473 [](const QtKey& a, const QtKey& b) { return a.osg < b.osg; });
474 }
475
476 QQuickDrawablePrivate* _drawable;
477 };
478
QQuickDrawable()479 QQuickDrawable::QQuickDrawable() : d(new QQuickDrawablePrivate)
480 {
481 setUseDisplayList(false);
482 setDataVariance(Object::DYNAMIC);
483
484 osg::StateSet* stateSet = getOrCreateStateSet();
485 stateSet->setRenderBinDetails(1001, "RenderBin");
486
487 QSurfaceFormat format;
488 format.setRenderableType(QSurfaceFormat::OpenGL);
489 QSurfaceFormat::setDefaultFormat(format);
490
491 static bool doneQmlRegistration = false;
492 if (!doneQmlRegistration) {
493 doneQmlRegistration = true;
494
495 // singleton system object
496 qmlRegisterSingletonType<FGQmlInstance>("FlightGear", 1, 0, "System", fgqmlinstance_provider);
497 qmlRegisterSingletonType<FGQmlInstance>("FlightGear", 1, 0, "WindowManager", fgqq_windowManager_provider);
498
499 // QML types
500 qmlRegisterType<FGQmlPropertyNode>("FlightGear", 1, 0, "Property");
501 qmlRegisterType<DialogStateController>("FlightGear", 1, 0, "DialogStateController");
502 }
503
504 globals->get_commands()->addCommandObject("reload-quick-gui", new ReloadCommand(this));
505 }
506
~QQuickDrawable()507 QQuickDrawable::~QQuickDrawable()
508 {
509 delete d->qmlEngine;
510 delete d->renderControl;
511 }
512
setup(osgViewer::GraphicsWindow * gw,osgViewer::Viewer * viewer)513 void QQuickDrawable::setup(osgViewer::GraphicsWindow *gw, osgViewer::Viewer *viewer)
514 {
515 osg::GraphicsContext* gc = gw;
516
517 // none of this stuff needs the context current, so we can do it
518 // all safely on the main thread
519
520 d->foreignOSGWindow = flightgear::qtWindowFromOSG(gw);
521 // d->foreignOSGWindow->setFormat(format);
522 d->foreignOSGWindow->setSurfaceType(QSurface::OpenGLSurface);
523
524 // QWindow::requestActive would do QPA::makeKey, but on macOS this
525 // is a no-op for foreign windows. So we're going to manually set
526 // the focus window!
527 QGuiApplicationPrivate::focus_window = d->foreignOSGWindow;
528
529 d->osgContext = gc;
530 d->renderControl = new CustomRenderControl(d->foreignOSGWindow);
531 d->quickWindow = new QQuickWindow(d->renderControl);
532 d->quickWindow->setClearBeforeRendering(false);
533
534 d->qmlEngine = new QQmlEngine;
535
536 SGPath rootQMLPath = SGPath::fromUtf8(fgGetString("/sim/gui/qml-root-path"));
537 SG_LOG(SG_GENERAL, SG_INFO, "Root QML dir:" << rootQMLPath.dir());
538 d->qmlEngine->addImportPath(QString::fromStdString(rootQMLPath.dir()));
539 // d->qmlEngine->addImportPath(QStringLiteral("qrc:///"));
540
541 if (!d->qmlEngine->incubationController())
542 d->qmlEngine->setIncubationController(d->quickWindow->incubationController());
543
544 // QObject::connect(d->quickWindow, &QQuickWindow::activeFocusItemChanged,
545 // d.get(), &QQuickDrawablePrivate::onWindowActiveFocusItemChanged);
546
547 QObject::connect(d->renderControl, &QQuickRenderControl::sceneChanged,
548 d.get(), &QQuickDrawablePrivate::onSceneChanged);
549 QObject::connect(d->renderControl, &QQuickRenderControl::renderRequested,
550 d.get(), &QQuickDrawablePrivate::onRenderRequested);
551
552
553 viewer->getEventHandlers().push_front(new QuickEventHandler(d.get()));
554 }
555
drawImplementation(osg::RenderInfo & renderInfo) const556 void QQuickDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
557 {
558 if (!d->renderControlInited) {
559 d->initRenderControl();
560 }
561
562 if (QOpenGLContext::currentContext() != d->qtContext) {
563 QOpenGLContextPrivate::setCurrentContext(d->qtContext);
564 }
565
566 QOpenGLFunctions* glFuncs = d->qtContext->functions();
567 // prepare any state QQ2 needs
568 d->quickWindow->resetOpenGLState();
569
570 // and reset these manually
571 glFuncs->glPixelStorei(GL_PACK_ALIGNMENT, 4);
572 glFuncs->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
573 glFuncs->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
574 glFuncs->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
575
576 d->renderControl->render();
577
578 // otherwise the PUI camera gets confused
579 d->quickWindow->resetOpenGLState();
580 }
581
reload(QUrl url)582 void QQuickDrawable::reload(QUrl url)
583 {
584 d->qmlEngine->clearComponentCache();
585 setSource(url);
586 }
587
setSource(QUrl url)588 void QQuickDrawable::setSource(QUrl url)
589 {
590 if (d->rootItem)
591 delete d->rootItem;
592 if (d->qmlComponent)
593 delete d->qmlComponent;
594 d->rootItem = nullptr;
595
596 d->qmlComponent = new QQmlComponent(d->qmlEngine, url);
597 if (d->qmlComponent->isLoading()) {
598 QObject::connect(d->qmlComponent, &QQmlComponent::statusChanged,
599 d.get(), &QQuickDrawablePrivate::onComponentLoaded);
600 } else {
601 d->onComponentLoaded();
602 }
603 }
604
resize(int width,int height)605 void QQuickDrawable::resize(int width, int height)
606 {
607 // we need to unscale from physical pixels back to logical, otherwise we end up double-scaled.
608 const float currentPixelRatio = static_cast<float>(d->foreignOSGWindow->devicePixelRatio());
609 const int logicalWidth = static_cast<int>(width / currentPixelRatio);
610 const int logicalHeight = static_cast<int>(height / currentPixelRatio);
611
612 if (d->rootItem) {
613 d->rootItem->setWidth(logicalWidth);
614 d->rootItem->setHeight(logicalHeight);
615 }
616
617 // SG_LOG(SG_GUI, SG_INFO, "Resize:, lw=" << logicalWidth << ", lh=" << logicalHeight);
618 d->quickWindow->setGeometry(0, 0, logicalWidth, logicalHeight);
619 }
620
621 #include "QQuickDrawable.moc"
622