diff options
author | Christian Strømme <[email protected]> | 2025-04-30 15:36:43 +0200 |
---|---|---|
committer | Christian Strømme <[email protected]> | 2025-05-30 09:32:42 +0200 |
commit | f525940cd5432e4e40d6767a22ca881a175a4b91 (patch) | |
tree | 38cc59f87d3bed3cc0906ea09b8996235d659dc6 /src/runtimerender/graphobjects | |
parent | e98926608818498046dbc55ae561cbed87b6e126 (diff) |
Improves the collection of nodes and rendering data during the preparation
phase by:
- Only traversing and collecting nodes when the node tree changes.
- Assigning a unique ID to each node in the tree for fast access to its
associated rendering data.
- Having a single root node for the entire window. If there are multiple
views within a window, they all operate on their respective sections
of the tree.
- Storing rendering data separately which avoids collecting and copying
in addition to make it more feasibly to just uploading the data in bulk
when needed.
- Proper separation of data allows for the calculation of non-dependent
data in parallel, once the global state has been updated.
This is merely the initial step towards a more advanced storage solution.
However, some adjustments are still necessary to transition a significant
portion of the preparation phase to the GPU. Our somewhat contrived
benchmark demonstrates a 6x improvement, which is also applicable to the
worst case scenario when nodes are dynamically added.
Change-Id: I1dd12734f6a043ca8145cb1c667921bb664aa3db
Reviewed-by: Christian Strømme <[email protected]>
Diffstat (limited to 'src/runtimerender/graphobjects')
-rw-r--r-- | src/runtimerender/graphobjects/qssgrendercamera.cpp | 24 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrendercamera_p.h | 16 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrendergraphobject.cpp | 1 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrendergraphobject.h | 1 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrenderlayer.cpp | 29 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrenderlayer_p.h | 29 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrendermodel_p.h | 2 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrendernode.cpp | 139 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrendernode_p.h | 35 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrenderroot.cpp | 39 | ||||
-rw-r--r-- | src/runtimerender/graphobjects/qssgrenderroot_p.h | 89 |
11 files changed, 260 insertions, 144 deletions
diff --git a/src/runtimerender/graphobjects/qssgrendercamera.cpp b/src/runtimerender/graphobjects/qssgrendercamera.cpp index 3308f150..1bedeb58 100644 --- a/src/runtimerender/graphobjects/qssgrendercamera.cpp +++ b/src/runtimerender/graphobjects/qssgrendercamera.cpp @@ -27,14 +27,11 @@ QSSGRenderCamera::QSSGRenderCamera(QSSGRenderGraphObject::Type type) markDirty(DirtyFlag::CameraDirty); } -QSSGCameraGlobalCalculationResult QSSGRenderCamera::calculateProjectionInternal(QSSGRenderCamera &camera, - const QRectF &inViewport, - Configuration config) +bool QSSGRenderCamera::calculateProjectionInternal(QSSGRenderCamera &camera, + const QRectF &inViewport, + Configuration config) { - // For convenience we ensure that the global variables are up to date, as this function - // is intended to be used with internally set-up cameras. - bool wasDirty = camera.calculateGlobalVariables(); - return QSSGCameraGlobalCalculationResult{ wasDirty, camera.calculateProjection(inViewport, config) }; + return camera.calculateProjection(inViewport, config); } bool QSSGRenderCamera::calculateProjection(const QRectF &inViewport, Configuration config) @@ -163,12 +160,12 @@ static QQuaternion rotationQuaternionForLookAt(const QVector3D &sourcePosition, void QSSGRenderCamera::lookAt(const QVector3D &inCameraPos, const QVector3D &inUpDir, const QVector3D &inTargetPos, const QVector3D &pivot) { - QQuaternion rotation = rotationQuaternionForLookAt(inCameraPos, getScalingCorrectDirection(), inTargetPos, inUpDir.normalized()); - globalTransform = localTransform = QSSGRenderNode::calculateTransformMatrix(inCameraPos, QSSGRenderNode::initScale, pivot, rotation); + QQuaternion rotation = rotationQuaternionForLookAt(inCameraPos, getScalingCorrectDirection(localTransform), inTargetPos, inUpDir.normalized()); + localTransform = QSSGRenderNode::calculateTransformMatrix(inCameraPos, QSSGRenderNode::initScale, pivot, rotation); QSSGRenderNode::markDirty(QSSGRenderNode::DirtyFlag::TransformDirty); } -void QSSGRenderCamera::calculateViewProjectionMatrix(QMatrix4x4 &outMatrix) const +void QSSGRenderCamera::calculateViewProjectionMatrix(const QMatrix4x4 &globalTransform, QMatrix4x4 &outMatrix) const { QMatrix4x4 nonScaledGlobal(Qt::Uninitialized); nonScaledGlobal.setColumn(0, globalTransform.column(0).normalized()); @@ -178,7 +175,7 @@ void QSSGRenderCamera::calculateViewProjectionMatrix(QMatrix4x4 &outMatrix) cons outMatrix = projection * nonScaledGlobal.inverted(); } -void QSSGRenderCamera::calculateViewProjectionWithoutTranslation(float clipNear, float clipFar, QMatrix4x4 &outMatrix) const +void QSSGRenderCamera::calculateViewProjectionWithoutTranslation(const QMatrix4x4 &globalTransform, float clipNear, float clipFar, QMatrix4x4 &outMatrix) const { if (qFuzzyIsNull(clipFar - clipNear)) { qWarning() << "QSSGRenderCamera::calculateViewProjection: far == near"; @@ -196,7 +193,8 @@ void QSSGRenderCamera::calculateViewProjectionWithoutTranslation(float clipNear, outMatrix = proj * nonScaledGlobal.inverted(); } -QSSGRenderRay QSSGRenderCamera::unproject(const QVector2D &inViewportRelativeCoords, +QSSGRenderRay QSSGRenderCamera::unproject(const QMatrix4x4 &globalTransform, + const QVector2D &inViewportRelativeCoords, const QRectF &inViewport) const { QSSGRenderRay theRay; @@ -225,7 +223,7 @@ QSSGRenderRay QSSGRenderCamera::unproject(const QVector2D &inViewportRelativeCoo } outOrigin = QSSGUtils::mat44::transform(globalTransform, outOrigin); - QMatrix3x3 theNormalMatrix = calculateNormalMatrix(); + QMatrix3x3 theNormalMatrix = globalTransform.normalMatrix(); outDir = QSSGUtils::mat33::transform(theNormalMatrix, outDir); outDir.normalize(); diff --git a/src/runtimerender/graphobjects/qssgrendercamera_p.h b/src/runtimerender/graphobjects/qssgrendercamera_p.h index 7310e32f..013fcb18 100644 --- a/src/runtimerender/graphobjects/qssgrendercamera_p.h +++ b/src/runtimerender/graphobjects/qssgrendercamera_p.h @@ -164,12 +164,12 @@ public: /*! * Convenience function intended for use with internal cameras that's not part of the scene graph! - * This function will ensure that the camera's global transform and projection matrix - * is updated in one go. + * This function will NOT ensure that the camera's global transform, projection matrix, or state + * is updated!. */ - static QSSGCameraGlobalCalculationResult calculateProjectionInternal(QSSGRenderCamera &camera, - const QRectF &inViewport, - Configuration config = {}); + static bool calculateProjectionInternal(QSSGRenderCamera &camera, + const QRectF &inViewport, + Configuration config = {}); /*! * \brief calculateProjection @@ -204,12 +204,12 @@ public: const QMatrix4x4 &projection, QMatrix4x4 &outMatrix); - void calculateViewProjectionMatrix(QMatrix4x4 &outMatrix) const; - void calculateViewProjectionWithoutTranslation(float near, float far, QMatrix4x4 &outMatrix) const; + void calculateViewProjectionMatrix(const QMatrix4x4 &globalTransform, QMatrix4x4 &outMatrix) const; + void calculateViewProjectionWithoutTranslation(const QMatrix4x4 &globalTransform, float near, float far, QMatrix4x4 &outMatrix) const; // Unproject a point (x,y) in viewport relative coordinates meaning // left, bottom is 0,0 and values are increasing right,up respectively. - QSSGRenderRay unproject(const QVector2D &inLayerRelativeMouseCoords, const QRectF &inViewport) const; + QSSGRenderRay unproject(const QMatrix4x4 &globalTransform, const QVector2D &inLayerRelativeMouseCoords, const QRectF &inViewport) const; [[nodiscard]] inline bool isDirty(DirtyFlag dirtyFlag = DirtyMask) const { diff --git a/src/runtimerender/graphobjects/qssgrendergraphobject.cpp b/src/runtimerender/graphobjects/qssgrendergraphobject.cpp index 2e57f301..43435f08 100644 --- a/src/runtimerender/graphobjects/qssgrendergraphobject.cpp +++ b/src/runtimerender/graphobjects/qssgrendergraphobject.cpp @@ -14,6 +14,7 @@ static const char *asString(QSSGRenderGraphObject::Type type) switch (type) { RETURN_AS_STRING(Type::Unknown) RETURN_AS_STRING(Type::Node) + RETURN_AS_STRING(Type::Root) RETURN_AS_STRING(Type::Layer) RETURN_AS_STRING(Type::Joint) RETURN_AS_STRING(Type::Skeleton) diff --git a/src/runtimerender/graphobjects/qssgrendergraphobject.h b/src/runtimerender/graphobjects/qssgrendergraphobject.h index 41c1e7e2..4c66c36a 100644 --- a/src/runtimerender/graphobjects/qssgrendergraphobject.h +++ b/src/runtimerender/graphobjects/qssgrendergraphobject.h @@ -58,6 +58,7 @@ public: Unknown = 0, // Nodes Node = BaseType::Node, + Root, // Node Layer, // Node Joint, // Node Skeleton, // Node (A resource to the model node) diff --git a/src/runtimerender/graphobjects/qssgrenderlayer.cpp b/src/runtimerender/graphobjects/qssgrenderlayer.cpp index 9e842ad1..eac5e458 100644 --- a/src/runtimerender/graphobjects/qssgrenderlayer.cpp +++ b/src/runtimerender/graphobjects/qssgrenderlayer.cpp @@ -5,10 +5,23 @@ #include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h> #include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h> -#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h> +#include "../rendererimpl/qssglayerrenderdata_p.h" +#include "qssgrenderroot_p.h" QT_BEGIN_NAMESPACE +void QSSGRenderLayer::markDirty(DirtyFlag dirtyFlag) +{ + m_layerDirtyFlags |= FlagT(dirtyFlag); + QSSGRenderNode::markDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty); +} + +void QSSGRenderLayer::clearDirty(DirtyFlag dirtyFlag) +{ + m_layerDirtyFlags &= ~FlagT(dirtyFlag); + QSSGRenderNode::clearDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty); +} + QSSGRenderLayer::QSSGRenderLayer() : QSSGRenderNode(QSSGRenderNode::Type::Layer) , firstEffect(nullptr) @@ -25,11 +38,23 @@ QSSGRenderLayer::QSSGRenderLayer() flags = { FlagT(LocalState::Active) | FlagT(GlobalState::Active) }; // The layer node is alway active and not dirty. } -QSSGRenderLayer::~QSSGRenderLayer() +void QSSGRenderLayer::release() { delete importSceneNode; importSceneNode = nullptr; delete renderData; + renderData = nullptr; +} + +QSSGRenderLayer::~QSSGRenderLayer() +{ + if (rootNode) + rootNode->removeChild(*this); + + rootNode = nullptr; + rootNodeRef = nullptr; + + release(); } void QSSGRenderLayer::setProbeOrientation(const QVector3D &angles) diff --git a/src/runtimerender/graphobjects/qssgrenderlayer_p.h b/src/runtimerender/graphobjects/qssgrenderlayer_p.h index 2470a2a1..0361a763 100644 --- a/src/runtimerender/graphobjects/qssgrenderlayer_p.h +++ b/src/runtimerender/graphobjects/qssgrenderlayer_p.h @@ -133,6 +133,34 @@ struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderLayer : public QSSGRenderNode WeightedBlended }; + enum class DirtyFlag : quint8 + { + TreeDirty = 0x1 + }; + using FlagT = std::underlying_type_t<DirtyFlag>; + + static constexpr DirtyFlag DirtyMask { std::numeric_limits<FlagT>::max() }; + + [[nodiscard]] bool isDirty(DirtyFlag dirtyFlag = DirtyMask) const + { + return ((m_layerDirtyFlags & FlagT(dirtyFlag)) != 0) + || ((dirtyFlag == DirtyMask) && QSSGRenderNode::isDirty()); + } + void markDirty(DirtyFlag dirtyFlag); + void clearDirty(DirtyFlag dirtyFlag); + + void ref(QSSGRenderRoot *inRootNode) + { + rootNode = inRootNode; + rootNodeRef = &rootNode; + } + + void release(); + + QSSGRenderRoot *rootNode = nullptr; + + QSSGRenderLayerHandle lh; + // First effect in a list of effects. QSSGRenderEffect *firstEffect; QSSGLayerRenderData *renderData = nullptr; @@ -145,6 +173,7 @@ struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderLayer : public QSSGRenderNode QSSGRenderLayer::Background background; QVector3D clearColor; + FlagT m_layerDirtyFlags = FlagT(DirtyFlag::TreeDirty); quint8 viewCount = 1; // Ambient occlusion diff --git a/src/runtimerender/graphobjects/qssgrendermodel_p.h b/src/runtimerender/graphobjects/qssgrendermodel_p.h index 8abeb77a..4f56a404 100644 --- a/src/runtimerender/graphobjects/qssgrendermodel_p.h +++ b/src/runtimerender/graphobjects/qssgrendermodel_p.h @@ -69,6 +69,8 @@ struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderModel : public QSSGRenderNode float levelOfDetailBias = 1.0f; // values < 1.0 will decrease usage of LODs, values > 1.0 will increase usage of LODs + QSSGRenderModelHandle mh; + QSSGRenderModel(); }; QT_END_NAMESPACE diff --git a/src/runtimerender/graphobjects/qssgrendernode.cpp b/src/runtimerender/graphobjects/qssgrendernode.cpp index 26e2c85b..4a3a9bfb 100644 --- a/src/runtimerender/graphobjects/qssgrendernode.cpp +++ b/src/runtimerender/graphobjects/qssgrendernode.cpp @@ -5,9 +5,12 @@ #include "qssgrendernode_p.h" +#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h> + #include <QtQuick3DUtils/private/qssgutils_p.h> -#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h> +#include "qssgrendermodel_p.h" +#include "qssgrenderroot_p.h" #include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h> @@ -23,7 +26,7 @@ QSSGRenderNode::QSSGRenderNode() QSSGRenderNode::QSSGRenderNode(Type type, QSSGRenderGraphObject::FlagT flags) : QSSGRenderGraphObject(type, flags) { - globalTransform = localTransform = calculateTransformMatrix({}, initScale, {}, {}); + localTransform = calculateTransformMatrix({}, initScale, {}, {}); } QSSGRenderNode::~QSSGRenderNode() @@ -35,8 +38,8 @@ void QSSGRenderNode::markDirty(DirtyFlag dirtyFlag) flags |= FlagT(dirtyFlag); const bool markSubtreeDirty = ((FlagT(dirtyFlag) & FlagT(DirtyFlag::GlobalValuesDirty)) != 0); if (markSubtreeDirty) { - for (auto &cld : children) - cld.markDirty(dirtyFlag); + for (auto &child : children) + child.markDirty(dirtyFlag); } } } @@ -56,6 +59,8 @@ void QSSGRenderNode::setState(LocalState state, bool on) switch (state) { case QSSGRenderNode::LocalState::Active: markDirty(DirtyFlag::ActiveDirty); + if (QSSGRenderRoot *rootNode = QSSGRenderRoot::get(rootNodeRef)) + rootNode->markDirty(QSSGRenderRoot::DirtyFlag::TreeDirty); break; case QSSGRenderNode::LocalState::Pickable: markDirty(DirtyFlag::PickableDirty); @@ -65,74 +70,6 @@ void QSSGRenderNode::setState(LocalState state, bool on) } -// Calculate global transform and opacity -// Walks up the graph ensure all parents are not dirty so they have -// valid global transforms. - -bool QSSGRenderNode::calculateGlobalVariables() -{ - bool retval = isDirty(DirtyFlag::GlobalValuesDirty); - if (retval) { - globalOpacity = localOpacity; - globalTransform = localTransform; - - if (parent) { - retval = parent->calculateGlobalVariables() || retval; - const bool globallyActive = getLocalState(LocalState::Active) && parent->getGlobalState(GlobalState::Active); - flags = globallyActive ? (flags | FlagT(GlobalState::Active)) : (flags & ~FlagT(GlobalState::Active)); - const bool globallyPickable = getLocalState(LocalState::Pickable) || parent->getGlobalState(GlobalState::Pickable); - flags = globallyPickable ? (flags | FlagT(GlobalState::Pickable)) : (flags & ~FlagT(GlobalState::Pickable)); - globalOpacity *= parent->globalOpacity; - // Skip calculating the transform for non-active nodes - if (globallyActive && parent->type != QSSGRenderGraphObject::Type::Layer) { - globalTransform = parent->globalTransform * localTransform; - if (this == instanceRoot) { - globalInstanceTransform = parent->globalTransform; - localInstanceTransform = localTransform; - } else if (instanceRoot) { - globalInstanceTransform = instanceRoot->globalInstanceTransform; - //### technically O(n^2) -- we could cache localInstanceTransform if every node in the - // tree is guaranteed to have the same instance root. That would require an API change. - localInstanceTransform = localTransform; - auto *p = parent; - while (p) { - if (p == instanceRoot) { - localInstanceTransform = p->localInstanceTransform * localInstanceTransform; - break; - } - localInstanceTransform = p->localTransform * localInstanceTransform; - p = p->parent; - } - } else { - // By default, we do magic: translation is applied to the global instance transform, - // while scale/rotation is local - - localInstanceTransform = localTransform; - auto &localInstanceMatrix = *reinterpret_cast<float (*)[4][4]>(localInstanceTransform.data()); - QVector3D localPos{localInstanceMatrix[3][0], localInstanceMatrix[3][1], localInstanceMatrix[3][2]}; - localInstanceMatrix[3][0] = 0; - localInstanceMatrix[3][1] = 0; - localInstanceMatrix[3][2] = 0; - globalInstanceTransform = parent->globalTransform; - globalInstanceTransform.translate(localPos); - } - } - } else { - const bool globallyActive = getLocalState(LocalState::Active); - flags = globallyActive ? (flags | FlagT(GlobalState::Active)) : (flags & ~FlagT(GlobalState::Active)); - const bool globallyPickable = getLocalState(LocalState::Pickable); - flags = globallyPickable ? (flags | FlagT(GlobalState::Pickable)) : (flags & ~FlagT(GlobalState::Pickable)); - localInstanceTransform = localTransform; - globalInstanceTransform = {}; - } - // Clear dirty flags - clearDirty(DirtyFlag::GlobalValuesDirty); - } - // We always clear dirty in a reasonable manner but if we aren't active - // there is no reason to tell the universe if we are dirty or not. - return retval && getLocalState(LocalState::Active); -} - QMatrix4x4 QSSGRenderNode::calculateTransformMatrix(QVector3D position, QVector3D scale, QVector3D pivot, QQuaternion rotation) { QMatrix4x4 transform; @@ -163,15 +100,29 @@ QMatrix4x4 QSSGRenderNode::calculateTransformMatrix(QVector3D position, QVector3 void QSSGRenderNode::addChild(QSSGRenderNode &inChild) { + QSSG_ASSERT_X(inChild.parent != this, "Already a child of this node!", return); + // Adding children to a layer does not reset parent // because layers can share children over with other layers - if (type != QSSGRenderNode::Type::Layer) { + if (type != QSSGRenderNode::Type::Layer && type != QSSGRenderNode::Type::ImportScene) { if (inChild.parent && inChild.parent != this) inChild.parent->removeChild(inChild); inChild.parent = this; } + + QSSG_ASSERT_X(type != QSSGRenderNode::Type::Root || inChild.type == QSSGRenderNode::Type::Layer, + "Only layers can be added to the root!", return); + + if (inChild.type != QSSGRenderNode::Type::Root) + inChild.rootNodeRef = rootNodeRef; + children.push_back(inChild); - inChild.markDirty(DirtyFlag::GlobalValuesDirty); + // Don't mark dirty if this is a layer, because layers won't have any global values + // calculated and therefore will not have the dirty flag cleared! + if (inChild.type != QSSGRenderNode::Type::Layer) + inChild.markDirty(DirtyFlag::GlobalValuesDirty); + if (QSSGRenderRoot *rootNode = QSSGRenderRoot::get(rootNodeRef)) + rootNode->markDirty(QSSGRenderRoot::DirtyFlag::TreeDirty); } void QSSGRenderNode::removeChild(QSSGRenderNode &inChild) @@ -182,7 +133,12 @@ void QSSGRenderNode::removeChild(QSSGRenderNode &inChild) } inChild.parent = nullptr; + if (inChild.type != QSSGRenderNode::Type::Root) + inChild.rootNodeRef = nullptr; + inChild.h = {}; children.remove(inChild); + if (QSSGRenderRoot *rootNode = QSSGRenderRoot::get(rootNodeRef)) + rootNode->markDirty(QSSGRenderRoot::DirtyFlag::TreeDirty); inChild.markDirty(DirtyFlag::GlobalValuesDirty); } @@ -228,7 +184,7 @@ QSSGBounds3 QSSGRenderNode::getChildBounds(QSSGBufferManager &inManager) const return retval; } -QVector3D QSSGRenderNode::getDirection() const +QVector3D QSSGRenderNode::getDirection(const QMatrix4x4 &globalTransform) { const float *dataPtr(globalTransform.data()); QVector3D retval(dataPtr[8], dataPtr[9], dataPtr[10]); @@ -236,7 +192,7 @@ QVector3D QSSGRenderNode::getDirection() const return retval; } -QVector3D QSSGRenderNode::getScalingCorrectDirection() const +QVector3D QSSGRenderNode::getScalingCorrectDirection(const QMatrix4x4 &globalTransform) { QMatrix3x3 theDirMatrix = globalTransform.normalMatrix(); QVector3D theOriginalDir(0, 0, -1); @@ -246,24 +202,14 @@ QVector3D QSSGRenderNode::getScalingCorrectDirection() const return retval; } -QVector3D QSSGRenderNode::getGlobalPivot() const +void QSSGRenderNode::calculateMVP(const QMatrix4x4 &globalTransform, const QMatrix4x4 &inViewProjection, QMatrix4x4 &outMVP) { - QVector3D retval(QSSGUtils::mat44::getPosition(localTransform)); - retval.setZ(retval.z() * -1); - - if (parent && parent->type != QSSGRenderGraphObject::Type::Layer) { - const QVector4D direction(retval.x(), retval.y(), retval.z(), 1.0f); - const QVector4D result = parent->globalTransform * direction; - return QVector3D(result.x(), result.y(), result.z()); - } - - return retval; + outMVP = inViewProjection * globalTransform; } -void QSSGRenderNode::calculateMVPAndNormalMatrix(const QMatrix4x4 &inViewProjection, QMatrix4x4 &outMVP, QMatrix3x3 &outNormalMatrix) const +void QSSGRenderNode::calculateNormalMatrix(const QMatrix4x4 &globalTransform, QMatrix3x3 &outNormalMatrix) { - outMVP = inViewProjection * globalTransform; - outNormalMatrix = calculateNormalMatrix(); + outNormalMatrix = globalTransform.normalMatrix(); } void QSSGRenderNode::calculateMVPAndNormalMatrix(const QMatrix4x4 &globalTransform, @@ -271,17 +217,8 @@ void QSSGRenderNode::calculateMVPAndNormalMatrix(const QMatrix4x4 &globalTransfo QMatrix4x4 &outMVP, QMatrix3x3 &outNormalMatrix) { - outMVP = inViewProjection * globalTransform; - outNormalMatrix = globalTransform.normalMatrix(); -} - -QMatrix3x3 QSSGRenderNode::calculateNormalMatrix() const -{ - // NB! QMatrix4x4:normalMatrix() uses double precision for determinant - // calculations when inverting the matrix, which is good and is important - // in practice e.g. in scenes with with small scale factors. - - return globalTransform.normalMatrix(); + calculateMVP(globalTransform, inViewProjection, outMVP); + calculateNormalMatrix(globalTransform, outNormalMatrix); } QT_END_NAMESPACE diff --git a/src/runtimerender/graphobjects/qssgrendernode_p.h b/src/runtimerender/graphobjects/qssgrendernode_p.h index b9cabf8a..6473f73c 100644 --- a/src/runtimerender/graphobjects/qssgrendernode_p.h +++ b/src/runtimerender/graphobjects/qssgrendernode_p.h @@ -18,6 +18,7 @@ #include <QtQuick3DRuntimeRender/private/qssgrendergraphobject_p.h> +#include <QtQuick3DUtils/private/qssgutils_p.h> #include <QtQuick3DUtils/private/qssgbounds3_p.h> #include <QtQuick3DUtils/private/qssginvasivelinkedlist_p.h> @@ -32,6 +33,8 @@ class QSSGRenderCamera; struct QSSGRenderText; struct QSSGRenderNode; class QSSGBufferManager; +class QSSGLayerRenderData; +class QSSGRenderRoot; struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderNode : public QSSGRenderGraphObject { @@ -75,19 +78,16 @@ struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderNode : public QSSGRenderGraphObje FlagT flags { FlagT(DirtyFlag::GlobalValuesDirty) | FlagT(LocalState::Active) }; // These end up right handed QMatrix4x4 localTransform; - QMatrix4x4 globalTransform; - QMatrix4x4 localInstanceTransform; - QMatrix4x4 globalInstanceTransform; - float globalOpacity = 1.0f; // node graph members. + QSSGRenderRoot **rootNodeRef = nullptr; QSSGRenderNode *parent = nullptr; QSSGRenderNode *nextSibling = nullptr; QSSGRenderNode *previousSibling = nullptr; QSSGRenderNode *instanceRoot = nullptr; - // Property maintained solely by the render system. - // Depth-first-search index assigned and maintained by render system. - quint32 dfsIndex = 0; + + // Handle(s) to the render data. + QSSGRenderNodeHandle h; using ChildList = QSSGInvasiveLinkedList<QSSGRenderNode, &QSSGRenderNode::previousSibling, &QSSGRenderNode::nextSibling>; ChildList children; @@ -116,11 +116,6 @@ struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderNode : public QSSGRenderGraphObje // finally they are no longer siblings of each other. void removeFromGraph(); - // Calculate global transform and opacity - // Walks up the graph ensure all parents are not dirty so they have - // valid global transforms. - bool calculateGlobalVariables(); - // Calculates a tranform matrix based on the position, scale, pivot and rotation arguments. // NOTE!!!: This function does not update or mark any nodes as dirty, if the returned matrix is set on a node then // markDirty, calculateGlobalVariables etc. needs to be called as needed! @@ -131,25 +126,25 @@ struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderNode : public QSSGRenderGraphObje bool inIncludeChildren = true) const; QSSGBounds3 getChildBounds(QSSGBufferManager &inManager) const; // Assumes CalculateGlobalVariables has already been called. - QVector3D getGlobalPos() const { return QVector3D(globalTransform(0, 3), globalTransform(1, 3), globalTransform(2, 3)); } - QVector3D getGlobalPivot() const; + [[nodiscard]] static QVector3D getGlobalPos(const QMatrix4x4 &globalTransform) { return QVector3D(globalTransform(0, 3), globalTransform(1, 3), globalTransform(2, 3)); } // Pulls the 3rd column out of the global transform. - QVector3D getDirection() const; + [[nodiscard]] static QVector3D getDirection(const QMatrix4x4 &globalTransform); // Multiplies (0,0,-1) by the inverse transpose of the upper 3x3 of the global transform. // This is correct w/r/t to scaling and which the above getDirection is not. - QVector3D getScalingCorrectDirection() const; + [[nodiscard]] static QVector3D getScalingCorrectDirection(const QMatrix4x4 &globalTransform); // outMVP and outNormalMatrix are returned ready to upload to openGL, meaning they are // row-major. - void calculateMVPAndNormalMatrix(const QMatrix4x4 &inViewProjection, QMatrix4x4 &outMVP, QMatrix3x3 &outNormalMatrix) const; + static void calculateMVP(const QMatrix4x4 &globalTransform, + const QMatrix4x4 &inViewProjection, + QMatrix4x4 &outMVP); + static void calculateNormalMatrix(const QMatrix4x4 &globalTransform, + QMatrix3x3 &outNormalMatrix); static void calculateMVPAndNormalMatrix(const QMatrix4x4 &globalTransfor, const QMatrix4x4 &inViewProjection, QMatrix4x4 &outMVP, QMatrix3x3 &outNormalMatrix); - // This should be in a utility file somewhere - QMatrix3x3 calculateNormalMatrix() const; - // The Squared value of \a val // This is mainly used for setting the sorting bias on models and particles // since we're using the squared distance when sorting. diff --git a/src/runtimerender/graphobjects/qssgrenderroot.cpp b/src/runtimerender/graphobjects/qssgrenderroot.cpp new file mode 100644 index 00000000..66a3f6a2 --- /dev/null +++ b/src/runtimerender/graphobjects/qssgrenderroot.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qssgrenderroot_p.h" + +#include "../rendererimpl/qssgrenderdata_p.h" + +QT_BEGIN_NAMESPACE + +QSSGRenderRoot::QSSGRenderRoot() + : QSSGRenderNode(Type::Root) + , m_gnd(std::make_shared<QSSGGlobalRenderNodeData>()) +{ + rootNodeRef = &self; + localTransform = calculateTransformMatrix({}, initScale, {}, {}); +} + +QSSGRenderRoot::~QSSGRenderRoot() {} + +void QSSGRenderRoot::markDirty(DirtyFlag dirtyFlag) +{ + m_rootDirtyFlags |= FlagT(dirtyFlag); + QSSGRenderNode::markDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty); +} + +void QSSGRenderRoot::clearDirty(DirtyFlag dirtyFlag) +{ + m_rootDirtyFlags &= ~FlagT(dirtyFlag); + QSSGRenderNode::clearDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty); +} + +void QSSGRenderRoot::reindex() +{ + // Reindex the world root node + m_gnd->reindex(this); + clearDirty(QSSGRenderRoot::DirtyFlag::TreeDirty); +} + +QT_END_NAMESPACE diff --git a/src/runtimerender/graphobjects/qssgrenderroot_p.h b/src/runtimerender/graphobjects/qssgrenderroot_p.h new file mode 100644 index 00000000..82cc26a0 --- /dev/null +++ b/src/runtimerender/graphobjects/qssgrenderroot_p.h @@ -0,0 +1,89 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QSSGRENDERROOT_P_H +#define QSSGRENDERROOT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick3DRuntimeRender/private/qssgrendernode_p.h> + +#include <QtGui/qmatrix4x4.h> + +#include <memory> + +QT_BEGIN_NAMESPACE + +class QSSGGlobalRenderNodeData; + +class Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRenderRoot final : public QSSGRenderNode +{ +public: + QSSGRenderRoot(); + ~QSSGRenderRoot(); + + enum class DirtyFlag : quint8 + { + TreeDirty = 0x1 + }; + using FlagT = std::underlying_type_t<DirtyFlag>; + + static constexpr DirtyFlag DirtyMask { std::numeric_limits<FlagT>::max() }; + + [[nodiscard]] bool isDirty(DirtyFlag dirtyFlag = DirtyMask) const + { + return ((m_rootDirtyFlags & FlagT(dirtyFlag)) != 0) + || ((dirtyFlag == DirtyMask) && QSSGRenderNode::isDirty()); + } + void markDirty(DirtyFlag dirtyFlag); + void clearDirty(DirtyFlag dirtyFlag); + + [[nodiscard]] const std::shared_ptr<QSSGGlobalRenderNodeData> &globalNodeData() const + { + return m_gnd; + } + + // This is the starting point when indexing the nodes. + // By default it is 0, which means the first version when + // indexing will be 1. The start version can be set to a + // different value when needed. For example we'll set this + // to the last version of the layer node when the window + // changes, as that avoids accidentally re-using the same + // version when re-indexing the layer node, as that can cause + // issues. + void setStartVersion(quint32 startVersion) + { + m_startVersion = startVersion; + } + + [[nodiscard]] quint32 startVersion() const + { + return m_startVersion; + } + + [[nodiscard]] static QSSGRenderRoot *get(QSSGRenderRoot **ref) + { + return (ref && *ref) ? *ref : nullptr; + } + + void reindex(); + +private: + QSSGRenderRoot *self = this; + quint32 m_startVersion = 0; + std::shared_ptr<QSSGGlobalRenderNodeData> m_gnd; + FlagT m_rootDirtyFlags = FlagT(DirtyFlag::TreeDirty); +}; + +QT_END_NAMESPACE + +#endif // QSSGRENDERROOT_P_H |