aboutsummaryrefslogtreecommitdiffstats
path: root/src/runtimerender/graphobjects
diff options
context:
space:
mode:
authorChristian Strømme <[email protected]>2025-04-30 15:36:43 +0200
committerChristian Strømme <[email protected]>2025-05-30 09:32:42 +0200
commitf525940cd5432e4e40d6767a22ca881a175a4b91 (patch)
tree38cc59f87d3bed3cc0906ea09b8996235d659dc6 /src/runtimerender/graphobjects
parente98926608818498046dbc55ae561cbed87b6e126 (diff)
Improve node collection and frame prepHEADdev
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.cpp24
-rw-r--r--src/runtimerender/graphobjects/qssgrendercamera_p.h16
-rw-r--r--src/runtimerender/graphobjects/qssgrendergraphobject.cpp1
-rw-r--r--src/runtimerender/graphobjects/qssgrendergraphobject.h1
-rw-r--r--src/runtimerender/graphobjects/qssgrenderlayer.cpp29
-rw-r--r--src/runtimerender/graphobjects/qssgrenderlayer_p.h29
-rw-r--r--src/runtimerender/graphobjects/qssgrendermodel_p.h2
-rw-r--r--src/runtimerender/graphobjects/qssgrendernode.cpp139
-rw-r--r--src/runtimerender/graphobjects/qssgrendernode_p.h35
-rw-r--r--src/runtimerender/graphobjects/qssgrenderroot.cpp39
-rw-r--r--src/runtimerender/graphobjects/qssgrenderroot_p.h89
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