Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QQuickView>
#include <QtQuick3D/private/qquick3dcontentlayer_p.h>
#include <QtQuick3D/private/qquick3dnode_p.h>
#include "../shared/util.h"
// Content layers at runtime: a node moved onto or off the camera's layers
// after the first frame must appear or disappear without any other change to
// the scene. The node's tag is not part of the transform and opacity update,
// so the layer filter has to be redone when a tag changes.
class tst_Layers : public QQuick3DDataTest
{
Q_OBJECT
private slots:
void initTestCase() override;
void layersChangeAtRuntime();
void sharedSceneChangesAtRuntime();
private:
bool hasRed(const QImage &image) const;
bool hasRedAt(const QImage &image, int x, int y) const;
};
void tst_Layers::initTestCase()
{
QQuick3DDataTest::initTestCase();
if (!initialized())
return;
}
bool tst_Layers::hasRed(const QImage &image) const
{
// The cube fills the middle of the view; sample the centre.
return hasRedAt(image, image.width() / 2, image.height() / 2);
}
bool tst_Layers::hasRedAt(const QImage &image, int x, int y) const
{
const QColor c = image.pixelColor(x, y);
return c.red() > 150 && c.green() < 80 && c.blue() < 80;
}
void tst_Layers::layersChangeAtRuntime()
{
QScopedPointer<QQuickView> view(createView(QStringLiteral("layerschange.qml"), QSize(200, 200)));
QVERIFY(view);
view->show();
QVERIFY(QTest::qWaitForWindowExposed(view.data()));
QVERIFY(waitForFrames(view.data(), 2));
auto *box = view->rootObject()->findChild<QQuick3DNode *>(QStringLiteral("box"));
QVERIFY(box);
QCOMPARE(box->layers(), int(QQuick3DContentLayer::Layer1));
// Outside the camera's mask: not drawn.
QVERIFY(!hasRed(grab(view.data())));
// Onto the camera's layer: drawn on the next frames.
box->setLayers(int(QQuick3DContentLayer::Layer0));
QVERIFY(waitForFrames(view.data(), 2));
QVERIFY(hasRed(grab(view.data())));
// And off again.
box->setLayers(int(QQuick3DContentLayer::Layer2));
QVERIFY(waitForFrames(view.data(), 2));
QVERIFY(!hasRed(grab(view.data())));
}
// Two views share a scene through importScene, so both layers walk the same
// render nodes. The dirty flags on a node are cleared by the first layer that
// prepares, so the change has to be passed on to the other layer, or that layer
// keeps its stale filtered node list (QTBUG-150709). The view that cannot see
// the box is declared first on purpose, so it is the one consuming the flags.
void tst_Layers::sharedSceneChangesAtRuntime()
{
QScopedPointer<QQuickView> view(createView(QStringLiteral("sharedscene.qml"), QSize(400, 200)));
QVERIFY(view);
view->show();
QVERIFY(QTest::qWaitForWindowExposed(view.data()));
QVERIFY(waitForFrames(view.data(), 2));
auto *box = view->rootObject()->findChild<QQuick3DNode *>(QStringLiteral("box"));
QVERIFY(box);
QVERIFY(!box->visible());
QCOMPARE(box->layers(), int(QQuick3DContentLayer::Layer1));
// The left view (x < 200) sees Layer2, the right view (x >= 200) sees Layer1.
const int leftX = 100;
const int rightX = 300;
const int y = 100;
// Hidden: drawn in neither view.
QImage image = grab(view.data());
QVERIFY(!hasRedAt(image, leftX, y));
QVERIFY(!hasRedAt(image, rightX, y));
// Changes to the shared scene reach the views through the scene manager, which
// can take more than one frame, so poll for the expected result. Each grab
// renders a frame, and a view stuck with a stale node list never recovers on
// its own, so a polled check cannot pass by accident.
// Shown: only the view whose camera sees Layer1 draws it, even though the
// other view's layer is the one that consumed the active change.
box->setVisible(true);
QTRY_VERIFY(hasRedAt(grab(view.data()), rightX, y));
QVERIFY(!hasRedAt(grab(view.data()), leftX, y));
// Moved to Layer2: the left view picks it up and the right view drops it. The
// left layer consumes the tag change, so the right view depends on it being
// passed on.
box->setLayers(int(QQuick3DContentLayer::Layer2));
QTRY_VERIFY(hasRedAt(grab(view.data()), leftX, y));
QTRY_VERIFY(!hasRedAt(grab(view.data()), rightX, y));
// Hidden again: gone from the left view too.
box->setVisible(false);
QTRY_VERIFY(!hasRedAt(grab(view.data()), leftX, y));
QVERIFY(!hasRedAt(grab(view.data()), rightX, y));
}
QTEST_MAIN(tst_Layers)
#include "tst_layers.moc"