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 <QtQml/QQmlEngine>
#include <QtQml/QQmlComponent>
#include <QtQml/qqmlextensionplugin.h>
#include <QtQuick3D/private/qquick3dcontentlayer_p.h>
#ifdef QT_STATIC
// Static builds need the QML plugins explicitly imported, since the test loads
// "import QtQuick" and "import QtQuick3D" via QQmlComponent::setData() at
// runtime and the import scanner cannot discover them from the C++ sources.
Q_IMPORT_QML_PLUGIN(QtQuick2Plugin)
Q_IMPORT_QML_PLUGIN(QQuick3DPlugin)
#endif
class tst_QQuick3DRenderPass : public QObject
{
Q_OBJECT
private slots:
void testContentLayer();
void testRenderPassQMLCreation();
void testAddDefine();
void testPipelineStateOverrideProperties();
void testRenderTargetBlend();
};
void tst_QQuick3DRenderPass::testContentLayer()
{
// Test that all layer constants are available and have expected values
QCOMPARE(QQuick3DContentLayer::Layer0, 1u << 0);
QCOMPARE(QQuick3DContentLayer::Layer1, 1u << 1);
QCOMPARE(QQuick3DContentLayer::Layer2, 1u << 2);
QCOMPARE(QQuick3DContentLayer::Layer23, 1u << 23);
QCOMPARE(QQuick3DContentLayer::Layer24, 1u << 24);
QCOMPARE(QQuick3DContentLayer::Layer31, 1u << 31);
QCOMPARE(QQuick3DContentLayer::LayerNone, 0u);
QCOMPARE(QQuick3DContentLayer::LayerAll, 0xFFFFFFu); // Only user layers (0-23)
// Test bitwise operations
quint32 layerMask = QQuick3DContentLayer::Layer0 | QQuick3DContentLayer::Layer1;
QCOMPARE(layerMask, 3u);
layerMask = QQuick3DContentLayer::Layer0 | QQuick3DContentLayer::Layer2;
QCOMPARE(layerMask, 5u);
// Test that layers are powers of 2
for (int i = 0; i < 32; ++i) {
quint32 layer = 1u << i;
// Verify it's a power of 2 (only one bit set)
QVERIFY((layer & (layer - 1)) == 0);
}
}
void tst_QQuick3DRenderPass::testRenderPassQMLCreation()
{
// Test that user render pass types can be created via QML
QQmlEngine engine;
// Test RenderPass creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; RenderPass { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
// Test RenderPassTexture creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; RenderPassTexture { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
// Test ColorAttachment creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; ColorAttachment { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
// Test DepthStencilAttachment creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; DepthStencilAttachment { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
// Test DepthTextureAttachment creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; DepthTextureAttachment { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
// Test RenderablesFilter creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; RenderablesFilter { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
// Test PipelineStateOverride creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; PipelineStateOverride { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
// Test SubRenderPass creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; SubRenderPass { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QObject *object = component.create();
QVERIFY(object != nullptr);
delete object;
}
}
void tst_QQuick3DRenderPass::testAddDefine()
{
QQmlEngine engine;
// Basic creation
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; AddDefine { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
// Default property values
QByteArray name = object->property("name").toByteArray();
int value = object->property("value").toInt();
QCOMPARE(name, QByteArray());
QCOMPARE(value, 0);
}
// Set and read back name and value
{
QQmlComponent component(&engine);
component.setData(R"(import QtQuick3D; AddDefine { name: "MY_DEFINE"; value: 42 })", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
QCOMPARE(object->property("name").toByteArray(), QByteArray("MY_DEFINE"));
QCOMPARE(object->property("value").toInt(), 42);
}
// Dynamic update of name and value
{
QQmlComponent component(&engine);
component.setData(R"(import QtQuick3D; AddDefine { name: "INITIAL"; value: 1 })", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
QCOMPARE(object->property("name").toByteArray(), QByteArray("INITIAL"));
QCOMPARE(object->property("value").toInt(), 1);
object->setProperty("name", QByteArray("UPDATED"));
object->setProperty("value", 99);
QCOMPARE(object->property("name").toByteArray(), QByteArray("UPDATED"));
QCOMPARE(object->property("value").toInt(), 99);
}
// Empty name (no-op — the renderer guards against empty names)
{
QQmlComponent component(&engine);
component.setData(R"(import QtQuick3D; AddDefine { name: ""; value: 0 })", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
QCOMPARE(object->property("name").toByteArray(), QByteArray(""));
}
}
void tst_QQuick3DRenderPass::testPipelineStateOverrideProperties()
{
QQmlEngine engine;
// Basic creation and default state
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; PipelineStateOverride { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
// Verify properties exist and are accessible
QVERIFY(object->metaObject()->indexOfProperty("depthTestEnabled") != -1);
QVERIFY(object->metaObject()->indexOfProperty("depthWriteEnabled") != -1);
QVERIFY(object->metaObject()->indexOfProperty("blendEnabled") != -1);
QVERIFY(object->metaObject()->indexOfProperty("cullMode") != -1);
QVERIFY(object->metaObject()->indexOfProperty("polygonMode") != -1);
QVERIFY(object->metaObject()->indexOfProperty("scissor") != -1);
QVERIFY(object->metaObject()->indexOfProperty("viewport") != -1);
}
// Set and read back boolean pipeline state properties
{
QQmlComponent component(&engine);
component.setData(R"(
import QtQuick3D
PipelineStateOverride {
depthTestEnabled: true
depthWriteEnabled: false
blendEnabled: true
})",
QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
QCOMPARE(object->property("depthTestEnabled").toBool(), true);
QCOMPARE(object->property("depthWriteEnabled").toBool(), false);
QCOMPARE(object->property("blendEnabled").toBool(), true);
}
// Dynamic property update
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; PipelineStateOverride { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
object->setProperty("depthTestEnabled", true);
object->setProperty("depthWriteEnabled", true);
object->setProperty("blendEnabled", false);
QCOMPARE(object->property("depthTestEnabled").toBool(), true);
QCOMPARE(object->property("depthWriteEnabled").toBool(), true);
QCOMPARE(object->property("blendEnabled").toBool(), false);
// Change again to verify updates propagate
object->setProperty("depthTestEnabled", false);
QCOMPARE(object->property("depthTestEnabled").toBool(), false);
}
// Scissor and viewport rect properties
{
QQmlComponent component(&engine);
component.setData(R"(
import QtQuick
import QtQuick3D
PipelineStateOverride {
scissor: Qt.rect(10, 20, 300, 200)
viewport: Qt.rect(0, 0, 400, 400)
})",
QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
QRectF scissor = object->property("scissor").toRectF();
QCOMPARE(scissor.x(), 10.0);
QCOMPARE(scissor.y(), 20.0);
QCOMPARE(scissor.width(), 300.0);
QCOMPARE(scissor.height(), 200.0);
QRectF viewport = object->property("viewport").toRectF();
QCOMPARE(viewport.x(), 0.0);
QCOMPARE(viewport.width(), 400.0);
}
}
void tst_QQuick3DRenderPass::testRenderTargetBlend()
{
QQmlEngine engine;
// PipelineStateOverride should expose targetBlend0 through targetBlend7
{
QQmlComponent component(&engine);
component.setData("import QtQuick3D; PipelineStateOverride { }", QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
// targetBlend0 through targetBlend7 should be accessible properties
for (int i = 0; i <= 7; ++i) {
const QByteArray propName = QByteArrayLiteral("targetBlend") + QByteArray::number(i);
const int propIndex = object->metaObject()->indexOfProperty(propName.constData());
QVERIFY2(propIndex != -1,
qPrintable(QString("PipelineStateOverride should have property '%1'").arg(QString::fromLatin1(propName))));
}
}
// Set targetBlend0.enable
{
QQmlComponent component(&engine);
component.setData(R"(
import QtQuick3D
PipelineStateOverride {
targetBlend0.enable: true
})",
QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
QVariant blend0 = object->property("targetBlend0");
QVERIFY(blend0.isValid());
// Extract the enable sub-property via the value type gadget
const QMetaObject *mo = QMetaType::fromName("QQuick3DRenderPassTargetBlend").metaObject();
if (mo) {
int enableIdx = mo->indexOfProperty("enable");
if (enableIdx != -1) {
void *gadget = blend0.data();
QVERIFY(mo->property(enableIdx).readOnGadget(gadget).toBool());
}
}
}
// targetBlend0 enum values via RenderTargetBlend namespace in QML
{
QQmlComponent component(&engine);
component.setData(R"(
import QtQuick3D
PipelineStateOverride {
targetBlend0.enable: true
targetBlend0.srcColor: RenderTargetBlend.SrcAlpha
targetBlend0.dstColor: RenderTargetBlend.OneMinusSrcAlpha
targetBlend0.opColor: RenderTargetBlend.Add
})",
QUrl());
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> object(component.create());
QVERIFY(object != nullptr);
QVariant blend0 = object->property("targetBlend0");
QVERIFY(blend0.isValid());
}
}
QTEST_MAIN(tst_QQuick3DRenderPass)
#include "tst_qquick3drenderpass.moc"