Revision control
++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "testextension.h"
++ #include <ssg/qssgrenderextensions.h>
++ #include <ssg/qssgrenderhelpers.h>
++ #include <QThread>
++ #include <QTest>
++
++ int extensionCtorCount = 0;
++ int extensionDtorCount = 0;
++ int extensionFunctional = 0;
++ int childExtensionCtorCount = 0;
++ int childExtensionDtorCount = 0;
++ int childExtensionFunctional = 0;
++
++ class ChildExtension : public QQuick3DRenderExtension
++ {
++ public:
++ ChildExtension(QQuick3DObject *parent = nullptr)
++ : QQuick3DRenderExtension(parent)
++ {
++ }
++
++ QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override;
++ };
++
++ class ChildRenderer : public QSSGRenderExtension
++ {
++ public:
++ ChildRenderer();
++ ~ChildRenderer();
++
++ bool prepareData(QSSGFrameData &data) override;
++ void prepareRender(QSSGFrameData &data) override;
++ void render(QSSGFrameData &data) override;
++ void resetForFrame() override;
++ RenderMode mode() const override { return RenderMode::Main; }
++ RenderStage stage() const override { return RenderStage::PostColor; }
++
++ QThread *t;
++ };
++
++ QSSGRenderGraphObject *ChildExtension::updateSpatialNode(QSSGRenderGraphObject *node)
++ {
++ if (!node)
++ node = new ChildRenderer;
++
++ return node;
++ }
++
++ ChildRenderer::ChildRenderer()
++ {
++ t = QThread::currentThread();
++ childExtensionCtorCount += 1;
++ }
++
++ ChildRenderer::~ChildRenderer()
++ {
++ // There is no ordering guarantee when it comes to the destruction of nodes.
++ // The Renderer (the other extension backend node) may or may not be alive at this point.
++
++ // with the threaded render loop it is important that the dtor is called on the same thread as the ctor
++ QCOMPARE(QThread::currentThread(), t);
++
++ childExtensionDtorCount += 1;
++ }
++
++ bool ChildRenderer::prepareData(QSSGFrameData &data)
++ {
++ auto camera = data.activeCamera();
++ if (camera == QSSGCameraId::Invalid)
++ qWarning("No camera in prepareData");
++
++ QMatrix4x4 vp = QSSGCameraHelpers::getViewProjectionMatrix(camera);
++ if (!vp.isIdentity())
++ childExtensionFunctional += 1;
++ else
++ qWarning("Camera's view-projection matrix does not look valid.");
++
++ return true;
++ }
++
++ void ChildRenderer::prepareRender(QSSGFrameData &)
++ {
++ QVERIFY(hasGraphicsResources()); // should be set implicitly
++
++ childExtensionFunctional += 1;
++ }
++
++ void ChildRenderer::render(QSSGFrameData &)
++ {
++ childExtensionFunctional += 1;
++ }
++
++ void ChildRenderer::resetForFrame()
++ {
++ }
++
++ class Renderer : public QSSGRenderExtension
++ {
++ public:
++ Renderer();
++ ~Renderer();
++
++ bool prepareData(QSSGFrameData &data) override;
++ void prepareRender(QSSGFrameData &data) override;
++ void render(QSSGFrameData &data) override;
++ void resetForFrame() override;
++ RenderMode mode() const override { return RenderMode::Standalone; }
++ RenderStage stage() const override { return RenderStage::PostColor; }
++
++ QThread *t;
++ };
++
++ MyExtension::MyExtension(QQuick3DObject *parent)
++ : QQuick3DRenderExtension(parent)
++ {
++ new ChildExtension(this);
++ }
++
++ QSSGRenderGraphObject *MyExtension::updateSpatialNode(QSSGRenderGraphObject *node)
++ {
++ if (!node)
++ node = new Renderer;
++
++ return node;
++ }
++
++ void MyExtension::markDirty(Dirty v)
++ {
++ m_dirtyFlag |= v;
++ update();
++ }
++
++ Renderer::Renderer()
++ {
++ t = QThread::currentThread();
++ extensionCtorCount += 1;
++ }
++
++ Renderer::~Renderer()
++ {
++ // There is no ordering guarantee when it comes to the destruction of nodes.
++ // The ChildRenderer (the other extension backend node) may or may not be alive at this point.
++
++ // with the threaded render loop it is important that the dtor is called on the same thread as the ctor
++ // NOTE: We only want to check that the address is the same (the object might have been destroyed already)...
++ QVERIFY(QThread::currentThread() == t);
++
++ extensionDtorCount += 1;
++ }
++
++ bool Renderer::prepareData(QSSGFrameData &data)
++ {
++ auto camera = data.activeCamera();
++ if (camera == QSSGCameraId::Invalid)
++ qWarning("No camera in prepareData");
++ else if (childExtensionFunctional == 0) // the order is undefined for siblings, and bottom-up for parent-child relationships
++ qWarning("Child did not run before parent's prepareData");
++ else
++ extensionFunctional += 1;
++
++ return true;
++ }
++
++ void Renderer::prepareRender(QSSGFrameData &)
++ {
++ QVERIFY(hasGraphicsResources());
++
++ if (childExtensionFunctional == 0)
++ qWarning("Child did not run before parent's prepareRender");
++ else
++ extensionFunctional += 1;
++ }
++
++ void Renderer::render(QSSGFrameData &)
++ {
++ if (childExtensionFunctional == 0)
++ qWarning("Child did not run before parent's render");
++ else
++ extensionFunctional += 1;
++ }
++
++ void Renderer::resetForFrame()
++ {
++ }