diff options
author | Liang Qi <liang.qi@qt.io> | 2018-01-22 13:21:04 +0100 |
---|---|---|
committer | Liang Qi <liang.qi@qt.io> | 2018-01-22 14:08:36 +0100 |
commit | 598acd70a44f787a3e72c586576bfc028d5af02d (patch) | |
tree | 30691da4bbdb935dbf592d9689d034060b9d4d74 | |
parent | 32a511a667bab93d4fca2e71b38d89f8d76aa07c (diff) | |
parent | 2b19bde378e084331b1b7ba9aa076270492999bb (diff) |
Merge remote-tracking branch 'origin/5.10' into dev
Conflicts:
src/render/backend/renderer_p.h
src/render/geometry/geometryrenderer.cpp
src/render/geometry/qmesh.cpp
src/render/geometry/qmesh_p.h
tests/auto/render/commons/testrenderer.h
tests/auto/render/meshfunctors/tst_meshfunctors.cpp
tests/auto/render/qmesh/tst_qmesh.cpp
Change-Id: Ia078029e2faf23fe253c5ce385e393c094266e3b
20 files changed, 218 insertions, 35 deletions
diff --git a/dist/changes-5.9.4 b/dist/changes-5.9.4 new file mode 100644 index 000000000..361215783 --- /dev/null +++ b/dist/changes-5.9.4 @@ -0,0 +1,27 @@ +Qt 5.9.4 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.9.4 Changes * +**************************************************************************** + + - [QTBUG-62235] Fix viewport scaling in Scene3D on hidpi displays + - [QTBUG-63537] Fix handling of uniform arrays values in some drivers + - [QTBUG-63897][QTBUG-65407] Fix crash when deleting Scene3D items + diff --git a/src/animation/backend/animationutils.cpp b/src/animation/backend/animationutils.cpp index 8652c7df1..44bf31854 100644 --- a/src/animation/backend/animationutils.cpp +++ b/src/animation/backend/animationutils.cpp @@ -848,6 +848,13 @@ QVector<float> defaultValueForChannel(Handler *handler, return result; } +void applyComponentDefaultValues(const QVector<ComponentValue> &componentDefaults, + ClipResults &formattedClipResults) +{ + for (const auto &componentDefault : componentDefaults) + formattedClipResults[componentDefault.componentIndex] = componentDefault.value; +} + } // Animation } // Qt3DAnimation diff --git a/src/animation/backend/animationutils_p.h b/src/animation/backend/animationutils_p.h index 6a1b18936..a0ab218d4 100644 --- a/src/animation/backend/animationutils_p.h +++ b/src/animation/backend/animationutils_p.h @@ -191,6 +191,13 @@ inline QDebug operator<<(QDebug dbg, const ChannelNameAndType &nameAndType) } #endif +struct ComponentValue +{ + int componentIndex; + float value; +}; +QT3D_DECLARE_TYPEINFO_2(Qt3DAnimation, Animation, ComponentValue, Q_PRIMITIVE_TYPE) + struct ClipFormat { // TODO: Remove the mask and store both the sourceClipIndices and @@ -200,6 +207,7 @@ struct ClipFormat QVector<QBitArray> sourceClipMask; QVector<ComponentIndices> formattedComponentIndices; QVector<ChannelNameAndType> namesAndTypes; + QVector<ComponentValue> defaultComponentValues; }; #ifndef QT_NO_DEBUG_STREAM @@ -338,6 +346,10 @@ ClipResults evaluateBlendTree(Handler *handler, Q_AUTOTEST_EXPORT QVector<float> defaultValueForChannel(Handler *handler, const ChannelNameAndType &channelDescription); +Q_AUTOTEST_EXPORT +void applyComponentDefaultValues(const QVector<ComponentValue> &componentDefaults, + ClipResults &formattedClipResults); + } // Animation } // Qt3DAnimation diff --git a/src/animation/backend/buildblendtreesjob.cpp b/src/animation/backend/buildblendtreesjob.cpp index a08aa2a34..581b133b5 100644 --- a/src/animation/backend/buildblendtreesjob.cpp +++ b/src/animation/backend/buildblendtreesjob.cpp @@ -90,10 +90,17 @@ void BuildBlendTreesJob::run() QVector<QBitArray> blendTreeChannelMask; const QVector<Qt3DCore::QNodeId> valueNodeIds = gatherValueNodesToEvaluate(m_handler, blendClipAnimator->blendTreeRootId()); + + // Store the clip value nodes to avoid further lookups below. + // TODO: Refactor this next block into a function in animationutils.cpp that takes + // a QVector<QClipBlendValue*> as input. + QVector<ClipBlendValue *> valueNodes; + valueNodes.reserve(valueNodeIds.size()); for (const auto valueNodeId : valueNodeIds) { ClipBlendValue *valueNode = static_cast<ClipBlendValue *>(m_handler->clipBlendNodeManager()->lookupNode(valueNodeId)); Q_ASSERT(valueNode); + valueNodes.push_back(valueNode); const Qt3DCore::QNodeId clipId = valueNode->clipId(); AnimationClip *clip = m_handler->animationClipLoaderManager()->lookupResource(clipId); @@ -132,6 +139,34 @@ void BuildBlendTreesJob::run() } } + // Now that we know the overall blend tree mask, go back and compare this to + // the masks from each of the value nodes. If the overall mask requires a + // channel but the value node does not provide it, we need to store default + // values to use for that channel so that the blending evaluation works as + // expected. + for (const auto valueNode : valueNodes) { + ClipFormat &f = valueNode->clipFormat(blendClipAnimator->peerId()); + + const int channelCount = blendTreeChannelMask.size(); + for (int i = 0; i < channelCount; ++i) { + if (blendTreeChannelMask[i] == f.sourceClipMask[i]) + continue; // Masks match, nothing to do + + // If we get to here then we need to obtain a default value + // for this channel and store it for later application to any + // missing components of this channel. + const QVector<float> defaultValue = defaultValueForChannel(m_handler, + f.namesAndTypes[i]); + + // Find the indices where we later need to inject these default + // values and store them in the format. + const ComponentIndices &componentIndices = f.formattedComponentIndices[i]; + Q_ASSERT(componentIndices.size() == defaultValue.size()); + for (int j = 0; j < defaultValue.size(); ++j) + f.defaultComponentValues.push_back({componentIndices[j], defaultValue[j]}); + } + } + // Finally, build the mapping data vector for this blended clip animator. This // gets used during the final stage of evaluation when sending the property changes // out to the targets of the animation. We do the costly work once up front. diff --git a/src/animation/backend/clipblendvalue.cpp b/src/animation/backend/clipblendvalue.cpp index c1de1fea7..5685d5191 100644 --- a/src/animation/backend/clipblendvalue.cpp +++ b/src/animation/backend/clipblendvalue.cpp @@ -102,12 +102,16 @@ void ClipBlendValue::setClipFormat(Qt3DCore::QNodeId animatorId, const ClipForma } } -ClipFormat ClipBlendValue::clipFormat(Qt3DCore::QNodeId animatorId) +ClipFormat &ClipBlendValue::clipFormat(Qt3DCore::QNodeId animatorId) { const int animatorIndex = m_animatorIds.indexOf(animatorId); - if (animatorIndex != -1) - return m_clipFormats[animatorIndex]; - return ClipFormat(); + return m_clipFormats[animatorIndex]; +} + +const ClipFormat &ClipBlendValue::clipFormat(Qt3DCore::QNodeId animatorId) const +{ + const int animatorIndex = m_animatorIds.indexOf(animatorId); + return m_clipFormats[animatorIndex]; } } // namespace Animation diff --git a/src/animation/backend/clipblendvalue_p.h b/src/animation/backend/clipblendvalue_p.h index 63dfe6ddc..168989a89 100644 --- a/src/animation/backend/clipblendvalue_p.h +++ b/src/animation/backend/clipblendvalue_p.h @@ -79,7 +79,8 @@ public: double duration() const override; void setClipFormat(Qt3DCore::QNodeId animatorId, const ClipFormat &formatIndices); - ClipFormat clipFormat(Qt3DCore::QNodeId animatorId); + ClipFormat &clipFormat(Qt3DCore::QNodeId animatorId); + const ClipFormat &clipFormat(Qt3DCore::QNodeId animatorId) const; protected: ClipResults doBlend(const QVector<ClipResults> &blendData) const override; diff --git a/src/animation/backend/evaluateblendclipanimatorjob.cpp b/src/animation/backend/evaluateblendclipanimatorjob.cpp index 5382b9c92..d01cf3bdd 100644 --- a/src/animation/backend/evaluateblendclipanimatorjob.cpp +++ b/src/animation/backend/evaluateblendclipanimatorjob.cpp @@ -105,6 +105,7 @@ void EvaluateBlendClipAnimatorJob::run() // Reformat the clip results into the layout used by this animator/blend tree const ClipFormat format = valueNode->clipFormat(blendedClipAnimator->peerId()); ClipResults formattedClipResults = formatClipResults(rawClipResults, format.sourceClipIndices); + applyComponentDefaultValues(format.defaultComponentValues, formattedClipResults); valueNode->setClipResults(blendedClipAnimator->peerId(), formattedClipResults); } diff --git a/src/core/nodes/qnode.cpp b/src/core/nodes/qnode.cpp index 58e016cda..dbe3fd102 100644 --- a/src/core/nodes/qnode.cpp +++ b/src/core/nodes/qnode.cpp @@ -173,11 +173,21 @@ void QNodePrivate::_q_postConstructorInit() { Q_Q(QNode); + // If we've already done the work then bail out. This can happen if the + // user creates a QNode subclass with an explicit parent, then immediately + // sets the new QNode as a property on another node. In this case, the + // property setter will call this function directly, but as we can't + // un-schedule a deferred invocation, this function will be called again + // the next time the event loop spins. So, catch this case and abort. + if (m_hasBackendNode) + return; + // Check that the parent hasn't been unset since this call was enqueued auto parentNode = q->parentNode(); if (!parentNode) return; + if (m_scene) m_scene->addObservable(q); // Sets the m_changeArbiter to that of the scene @@ -369,7 +379,18 @@ void QNodePrivate::propertyChanged(int propertyIndex) const QVariant data = property.read(q); if (data.canConvert<QNode*>()) { - const QNode * const node = data.value<QNode*>(); + QNode *node = data.value<QNode*>(); + + // Ensure the node has issued a node creation change. We can end + // up here if a newly created node with a parent is immediately set + // as a property on another node. In this case the deferred call to + // _q_postConstructorInit() will not have happened yet as the event + // loop will still be blocked. So force it here and we catch this + // eventuality in the _q_postConstructorInit() function so that we + // do not repeat the creation and new child scene change events. + if (node) + QNodePrivate::get(node)->_q_postConstructorInit(); + const QNodeId id = node ? node->id() : QNodeId(); notifyPropertyChange(property.name(), QVariant::fromValue(id)); } else { diff --git a/src/plugins/sceneparsers/assimp/assimpimporter.cpp b/src/plugins/sceneparsers/assimp/assimpimporter.cpp index 66446ced5..e630ce657 100644 --- a/src/plugins/sceneparsers/assimp/assimpimporter.cpp +++ b/src/plugins/sceneparsers/assimp/assimpimporter.cpp @@ -1290,6 +1290,7 @@ void AssimpImporter::copyMaterialTextures(QMaterial *material, aiMaterial *assim QAbstractTexture *tex = QAbstractNodeFactory::createNode<QTexture2D>("QTexture2D"); QTextureImage *texImage = QAbstractNodeFactory::createNode<QTextureImage>("QTextureImage"); texImage->setSource(QUrl::fromLocalFile(fullPath)); + texImage->setMirrored(false); tex->addTextureImage(texImage); // Set proper wrapping mode diff --git a/src/quick3d/imports/scene3d/scene3drenderer.cpp b/src/quick3d/imports/scene3d/scene3drenderer.cpp index 37ae8e48b..1c9fec4d2 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer.cpp +++ b/src/quick3d/imports/scene3d/scene3drenderer.cpp @@ -135,6 +135,7 @@ Scene3DRenderer::Scene3DRenderer(Scene3DItem *item, Qt3DCore::QAspectEngine *asp , m_multisample(false) // this value is not used, will be synced from the Scene3DItem instead , m_lastMultisample(false) , m_needsShutdown(true) + , m_blocking(false) { Q_CHECK_PTR(m_item); Q_CHECK_PTR(m_item->window()); @@ -154,6 +155,9 @@ Scene3DRenderer::Scene3DRenderer(Scene3DItem *item, Qt3DCore::QAspectEngine *asp ContextSaver saver; static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderInitialize(saver.context()); scheduleRootEntityChange(); + + const bool blockingRendermode = !qgetenv("SCENE3D_BLOCKING_RENDERMODE").isEmpty(); + m_blocking = blockingRendermode; } Scene3DRenderer::~Scene3DRenderer() @@ -307,7 +311,7 @@ void Scene3DRenderer::render() m_finalFBO->bind(); // Render Qt3D Scene - static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderSynchronous(); + static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderSynchronous(m_blocking); // We may have called doneCurrent() so restore the context if the rendering surface was changed // Note: keep in mind that the ContextSave also restores the surface when destroyed diff --git a/src/quick3d/imports/scene3d/scene3drenderer_p.h b/src/quick3d/imports/scene3d/scene3drenderer_p.h index 7a85bc774..eb2b930ef 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer_p.h +++ b/src/quick3d/imports/scene3d/scene3drenderer_p.h @@ -109,6 +109,7 @@ private: bool m_multisample; bool m_lastMultisample; bool m_needsShutdown; + bool m_blocking; friend class Scene3DCleaner; }; diff --git a/src/render/backend/abstractrenderer_p.h b/src/render/backend/abstractrenderer_p.h index 4d80ec87d..c934dbfae 100644 --- a/src/render/backend/abstractrenderer_p.h +++ b/src/render/backend/abstractrenderer_p.h @@ -133,7 +133,7 @@ public: // Threaded renderer virtual void render() = 0; // Synchronous renderer - virtual void doRender() = 0; + virtual void doRender(bool scene3dBlocking = false) = 0; virtual void cleanGraphicsResources() = 0; diff --git a/src/render/backend/renderer.cpp b/src/render/backend/renderer.cpp index 0507cc0b0..535b10b85 100644 --- a/src/render/backend/renderer.cpp +++ b/src/render/backend/renderer.cpp @@ -556,7 +556,7 @@ void Renderer::render() } } -void Renderer::doRender() +void Renderer::doRender(bool scene3dBlocking) { Renderer::ViewSubmissionResultData submissionData; bool hasCleanedQueueAndProceeded = false; @@ -566,9 +566,22 @@ void Renderer::doRender() // Lock the mutex to protect access to the renderQueue while we look for its state QMutexLocker locker(m_renderQueue->mutex()); - const bool queueIsComplete = m_renderQueue->isFrameQueueComplete(); + bool queueIsComplete = m_renderQueue->isFrameQueueComplete(); const bool queueIsEmpty = m_renderQueue->targetRenderViewCount() == 0; + // Scene3D Blocking Mode + if (scene3dBlocking && !queueIsComplete && !queueIsEmpty) { + int i = 0; + // We wait at most 10ms to avoid a case we could never recover from + while (!queueIsComplete && i++ < 10) { + QThread::msleep(1); + qCDebug(Backend) << Q_FUNC_INFO << "Waiting for ready queue (try:" << i << "/ 10)"; + locker.unlock(); + queueIsComplete = m_renderQueue->isFrameQueueComplete(); + locker.relock(); + } + } + // When using synchronous rendering (QtQuick) // We are not sure that the frame queue is actually complete // Since a call to render may not be synched with the completions diff --git a/src/render/backend/renderer_p.h b/src/render/backend/renderer_p.h index 81f76bd0c..c7ca49401 100644 --- a/src/render/backend/renderer_p.h +++ b/src/render/backend/renderer_p.h @@ -170,7 +170,7 @@ public: void releaseGraphicsResources() override; void render() override; - void doRender() override; + void doRender(bool scene3dBlocking = false) override; void cleanGraphicsResources() override; bool isRunning() const override { return m_running.load(); } diff --git a/src/render/frontend/qrenderaspect.cpp b/src/render/frontend/qrenderaspect.cpp index ba9fd4451..53adf96a9 100644 --- a/src/render/frontend/qrenderaspect.cpp +++ b/src/render/frontend/qrenderaspect.cpp @@ -405,9 +405,9 @@ void QRenderAspectPrivate::renderInitialize(QOpenGLContext *context) } /*! \internal */ -void QRenderAspectPrivate::renderSynchronous() +void QRenderAspectPrivate::renderSynchronous(bool blocking) { - m_renderer->doRender(); + m_renderer->doRender(blocking); } /*! diff --git a/src/render/frontend/qrenderaspect_p.h b/src/render/frontend/qrenderaspect_p.h index b8c8538ee..26ca091f6 100644 --- a/src/render/frontend/qrenderaspect_p.h +++ b/src/render/frontend/qrenderaspect_p.h @@ -90,7 +90,7 @@ public: void loadSceneParsers(); void loadRenderPlugin(const QString &pluginName); void renderInitialize(QOpenGLContext *context); - void renderSynchronous(); + void renderSynchronous(bool blocking = false); void renderShutdown(); void registerBackendType(const QMetaObject &, const Qt3DCore::QBackendNodeMapperPtr &functor); QVector<Qt3DCore::QAspectJobPtr> createGeometryRendererJobs(); diff --git a/src/render/graphicshelpers/graphicscontext.cpp b/src/render/graphicshelpers/graphicscontext.cpp index d33aca825..9e8e3d610 100644 --- a/src/render/graphicshelpers/graphicscontext.cpp +++ b/src/render/graphicshelpers/graphicscontext.cpp @@ -515,7 +515,8 @@ void GraphicsContext::loadShader(Shader *shader, ShaderManager *manager) shaderProgram = createShaderProgram(shader); // Store in cache - m_shaderCache.insert(shader->dna(), shader->peerId(), shaderProgram); + if (shaderProgram) + m_shaderCache.insert(shader->dna(), shader->peerId(), shaderProgram); } // Ensure the Shader node knows about the program interface diff --git a/tests/auto/animation/clipblendvalue/tst_clipblendvalue.cpp b/tests/auto/animation/clipblendvalue/tst_clipblendvalue.cpp index cc9d22556..197378094 100644 --- a/tests/auto/animation/clipblendvalue/tst_clipblendvalue.cpp +++ b/tests/auto/animation/clipblendvalue/tst_clipblendvalue.cpp @@ -195,25 +195,6 @@ private Q_SLOTS: << blendNode << indexes << animatorIds << expectedClipFormat; } - // No data - { - auto blendNode = new ClipBlendValue; - QVector<Qt3DCore::QNodeId> animatorIds; - QVector<ClipFormat> expectedClipFormat; - - auto animatorId = Qt3DCore::QNodeId::createId(); - animatorIds.push_back(animatorId); - - ClipFormat clipFormat; - expectedClipFormat.push_back(clipFormat); - - // Don't set any data - QVector<int> indexes = QVector<int>() << 0; - - QTest::newRow("no entries") - << blendNode << indexes << animatorIds << expectedClipFormat; - } - // Multiple entries, ordered { auto blendNode = new ClipBlendValue; diff --git a/tests/auto/core/nodes/tst_nodes.cpp b/tests/auto/core/nodes/tst_nodes.cpp index 6dcd1bd13..0ec661fed 100644 --- a/tests/auto/core/nodes/tst_nodes.cpp +++ b/tests/auto/core/nodes/tst_nodes.cpp @@ -80,6 +80,7 @@ private slots: void removingChildEntitiesFromNode(); void checkConstructionSetParentMix(); // QTBUG-60612 + void checkConstructionWithParent(); void appendingComponentToEntity(); void appendingParentlessComponentToEntity(); @@ -169,13 +170,17 @@ void SimplePostman::notifyBackend(const Qt3DCore::QSceneChangePtr &change) m_spy->sceneChangeEventWithLock(change); } + + class MyQNode : public Qt3DCore::QNode { Q_OBJECT Q_PROPERTY(QString customProperty READ customProperty WRITE setCustomProperty NOTIFY customPropertyChanged) + Q_PROPERTY(MyQNode *nodeProperty READ nodeProperty WRITE setNodeProperty NOTIFY nodePropertyChanged) public: explicit MyQNode(Qt3DCore::QNode *parent = 0) : QNode(parent) + , m_nodeProperty(nullptr) {} ~MyQNode() @@ -211,11 +216,37 @@ public: Qt3DCore::QNodePrivate::get(this)->m_hasBackendNode = created; } + MyQNode *nodeProperty() const { return m_nodeProperty; } + +public slots: + void setNodeProperty(MyQNode *node) + { + Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this); + if (m_nodeProperty == node) + return; + + if (m_nodeProperty) + d->unregisterDestructionHelper(m_nodeProperty); + + if (node && !node->parent()) + node->setParent(this); + + m_nodeProperty = node; + + // Ensures proper bookkeeping + if (m_nodeProperty) + d->registerDestructionHelper(m_nodeProperty, &MyQNode::setNodeProperty, m_nodeProperty); + + emit nodePropertyChanged(node); + } + signals: void customPropertyChanged(); + void nodePropertyChanged(MyQNode *node); protected: QString m_customProperty; + MyQNode *m_nodeProperty; }; class MyQEntity : public Qt3DCore::QEntity @@ -847,6 +878,49 @@ void tst_Nodes::checkConstructionSetParentMix() QCOMPARE(lastEvent->addedNodeId(), subTreeRoot->id()); } +void tst_Nodes::checkConstructionWithParent() +{ + // GIVEN + ObserverSpy spy; + Qt3DCore::QScene scene; + QScopedPointer<MyQNode> root(new MyQNode()); + + // WHEN + root->setArbiterAndScene(&spy, &scene); + root->setSimulateBackendCreated(true); + + // THEN + QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr); + + // WHEN we create a child and then set it as a Node* property + auto *node = new MyQNode(root.data()); + root->setNodeProperty(node); + + // THEN we should get one creation change, one child added change + // and one property change event, in that order. + QCoreApplication::processEvents(); + QCOMPARE(root->children().count(), 1); + QCOMPARE(spy.events.size(), 3); // 1 creation change, 1 child added change, 1 property change + + // Ensure first event is child node's creation change + const auto creationEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QNodeCreatedChangeBase>(); + QVERIFY(!creationEvent.isNull()); + QCOMPARE(creationEvent->subjectId(), node->id()); + + const auto newChildEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>(); + QVERIFY(!newChildEvent.isNull()); + QCOMPARE(newChildEvent->subjectId(), root->id()); + QCOMPARE(newChildEvent->propertyName(), "children"); + QCOMPARE(newChildEvent->addedNodeId(), node->id()); + + // Ensure second and last event is property set change + const auto propertyEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyUpdatedChange>(); + QVERIFY(!propertyEvent.isNull()); + QCOMPARE(propertyEvent->subjectId(), root->id()); + QCOMPARE(propertyEvent->propertyName(), "nodeProperty"); + QCOMPARE(propertyEvent->value().value<Qt3DCore::QNodeId>(), node->id()); +} + void tst_Nodes::appendingParentlessComponentToEntity() { // GIVEN diff --git a/tests/auto/render/commons/testrenderer.h b/tests/auto/render/commons/testrenderer.h index b26a3ffad..327e08d78 100644 --- a/tests/auto/render/commons/testrenderer.h +++ b/tests/auto/render/commons/testrenderer.h @@ -52,7 +52,7 @@ public: void shutdown() override {} void releaseGraphicsResources() override {} void render() override {} - void doRender() override {} + void doRender(bool scene3dBlocking = false) override { Q_UNUSED(scene3dBlocking); } void cleanGraphicsResources() override {} bool isRunning() const override { return true; } bool shouldRender() override { return true; } |