Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QTest>
++ #include <QSignalSpy>
++ #include <QtCore/qmath.h>
++
++ #include <QtQuick3D/private/qquick3dscenemanager_p.h>
++ #include <QtQuick3D/private/qquick3ddefaultmaterial_p.h>
++ #include <QtQuick3D/private/qquick3dcustommaterial_p.h>
++ #include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
++ #include <QtQuick3D/private/qquick3dspecularglossymaterial_p.h>
++ #include <QtQuick3D/private/qquick3dviewport_p.h>
++
++ #include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterial_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
++ #include <QtQuick3DUtils/private/qssgutils_p.h>
++
++ class tst_QQuick3DMaterials : public QObject
++ {
++ Q_OBJECT
++
++ // Work-around to get access to updateSpatialNode
++ class Material : public QQuick3DDefaultMaterial
++ {
++ public:
++ using QQuick3DMaterial::updateSpatialNode;
++ };
++
++ class CustomMaterial : public QQuick3DCustomMaterial
++ {
++ public:
++ using QQuick3DCustomMaterial::updateSpatialNode;
++ };
++
++ class PrincipledMaterial : public QQuick3DPrincipledMaterial
++ {
++ public:
++ using QQuick3DPrincipledMaterial::updateSpatialNode;
++ };
++
++ class SpecularGlossyMaterial : public QQuick3DSpecularGlossyMaterial
++ {
++ public:
++ using QQuick3DSpecularGlossyMaterial::updateSpatialNode;
++ };
++
++ class Texture : public QQuick3DTexture
++ {
++ public:
++ using QQuick3DTexture::updateSpatialNode;
++ };
++
++ private slots:
++ void testDefaultProperties();
++ void testDefaultTextures();
++ void testDefaultEnums();
++ void testPrincipledProperties();
++ void testPrincipledTextures();
++ void testPrincipledEnums();
++ void testSpecularGlossyProperties();
++ void testSpecularGlossyTextures();
++ void testSpecularGlossyEnums();
++ void testCustomMaterials();
++ };
++
++ void tst_QQuick3DMaterials::testDefaultProperties()
++ {
++ Material material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ const auto originalNode = node; // for comparisons later...
++ QVERIFY(node);
++
++ const float opacity = 0.4f;
++ material.setOpacity(opacity);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QCOMPARE(material.opacity(), node->opacity);
++
++ const float specularAmount = 10.0f;
++ material.setSpecularAmount(specularAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QCOMPARE(material.specularAmount(), node->specularAmount);
++
++ const float emissiveFactor = 0.5f;
++ material.setEmissiveFactor(QVector3D(emissiveFactor, emissiveFactor, emissiveFactor));
++ material.setLighting(QQuick3DDefaultMaterial::FragmentLighting);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ auto expectedEmissiceColor = material.emissiveFactor();
++ QCOMPARE(expectedEmissiceColor, node->emissiveColor);
++
++ const float bumpAmount = 0.3f;
++ material.setBumpAmount(bumpAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QCOMPARE(material.bumpAmount(), node->bumpAmount);
++
++ material.setVertexColorsEnabled(true);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(material.vertexColorsEnabled(), node->vertexColorsEnabled);
++ material.setVertexColorsEnabled(false);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(material.vertexColorsEnabled(), node->vertexColorsEnabled);
++
++ QColor color1("#12345678");
++ QVector4D color1Vec4 = QSSGUtils::color::sRGBToLinear(color1);
++ material.setDiffuseColor(color1);
++ material.setEmissiveFactor(color1Vec4.toVector3D());
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QCOMPARE(color1Vec4, node->color);
++ // Note: emissiveColor doesn't contain alpha
++ QCOMPARE(QVector3D(color1Vec4), node->emissiveColor);
++
++ }
++
++ void tst_QQuick3DMaterials::testDefaultTextures()
++ {
++ Material material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ const auto originalNode = node; // for comparisons later...
++ QVERIFY(node);
++
++ Texture texture1;
++ texture1.setSource(QUrl(":/Built_with_Qt.png"));
++ Texture texture2;
++ texture2.setSource(QUrl(":/Built_with_Qt_2.png"));
++
++ QQuick3DSceneManager sceneManager;
++ sceneManager.updateDirtyResource(&texture1);
++ sceneManager.updateDirtyResource(&texture2);
++
++ // Diffusemaps
++ material.setDiffuseMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QVERIFY(texture1.getRenderImage());
++ QCOMPARE(texture1.getRenderImage(), node->colorMap);
++
++ // Specularmaps
++ QVERIFY(!node->specularMap);
++ QVERIFY(!node->specularReflection);
++ QVERIFY(!material.specularMap());
++ QVERIFY(!material.specularReflectionMap());
++ material.setSpecularMap(&texture2);
++ material.setSpecularReflectionMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.specularMap());
++ QCOMPARE(material.specularMap()->getRenderImage(), node->specularMap);
++ QVERIFY(material.specularReflectionMap());
++ QCOMPARE(material.specularReflectionMap()->getRenderImage(), node->specularReflection);
++
++ // Bumpmap
++ QVERIFY(!node->bumpMap);
++ QVERIFY(!material.bumpMap());
++ material.setBumpMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.bumpMap());
++ QCOMPARE(material.bumpMap()->getRenderImage(), node->bumpMap);
++
++ // Opacitymap
++ QVERIFY(!node->opacityMap);
++ QVERIFY(!material.opacityMap());
++ material.setOpacityMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.opacityMap());
++ QCOMPARE(material.opacityMap()->getRenderImage(), node->opacityMap);
++ QCOMPARE(material.opacityMap()->getRenderImage(), material.bumpMap()->getRenderImage());
++ }
++
++ void tst_QQuick3DMaterials::testDefaultEnums()
++ {
++ Material material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QVERIFY(node);
++
++ // These test rely on the different enums having the same values
++ auto lightModes = { QQuick3DDefaultMaterial::Lighting::NoLighting,
++ QQuick3DDefaultMaterial::Lighting::FragmentLighting };
++ for (const auto lightMode : lightModes)
++ {
++ material.setLighting(lightMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QCOMPARE(int(material.lighting()), int(node->lighting));
++ }
++
++ auto blendModes = { QQuick3DDefaultMaterial::BlendMode::SourceOver,
++ QQuick3DDefaultMaterial::BlendMode::Screen,
++ QQuick3DDefaultMaterial::BlendMode::Multiply };
++ for (const auto blendMode : blendModes)
++ {
++ material.setBlendMode(blendMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QCOMPARE(int(material.blendMode()), int(node->blendMode));
++ }
++
++ auto specularModes = { QQuick3DDefaultMaterial::SpecularModel::Default,
++ QQuick3DDefaultMaterial::SpecularModel::KGGX};
++ for (const auto specularMode : specularModes)
++ {
++ material.setSpecularModel(specularMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QCOMPARE(int(material.specularModel()), int(node->specularModel));
++ }
++
++ auto depthDrawModes = {
++ QQuick3DMaterial::DepthDrawMode::OpaqueOnlyDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::AlwaysDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::NeverDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::OpaquePrePassDepthDraw
++ };
++
++ for (const auto depthDrawMode : depthDrawModes) {
++ material.setDepthDrawMode(depthDrawMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QCOMPARE(int(material.depthDrawMode()), int(node->depthDrawMode));
++ }
++ }
++
++ void tst_QQuick3DMaterials::testPrincipledProperties()
++ {
++ PrincipledMaterial material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ const auto originalNode = node; // for comparisons later...
++ QVERIFY(node);
++
++ const float metalness = 0.1f;
++ material.setMetalness(metalness);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QCOMPARE(metalness, material.metalness());
++ QCOMPARE(metalness, node->metalnessAmount);
++
++ // Note: metalness needs to be disabled for specularAmount and specularTint
++ // to affect backend.
++ material.setMetalness(0.0f);
++
++ const float specularAmount = 0.2f;
++ material.setSpecularAmount(specularAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(specularAmount, material.specularAmount());
++ QCOMPARE(specularAmount, node->specularAmount);
++
++ const float specularTint = 0.3f;
++ material.setSpecularTint(specularTint);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(specularTint, material.specularTint());
++ // Note: Backend uses vector3D for specularTint
++ QCOMPARE(specularTint, node->specularTint.y());
++
++ const float roughness = 0.4f;
++ material.setRoughness(roughness);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(roughness, material.roughness());
++ QCOMPARE(roughness, node->specularRoughness);
++
++ const float opacity = 0.7f;
++ material.setOpacity(opacity);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(opacity, material.opacity());
++ QCOMPARE(opacity, node->opacity);
++
++ const float normalStrength = 0.8f;
++ material.setNormalStrength(normalStrength);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(normalStrength, material.normalStrength());
++ QCOMPARE(normalStrength, node->bumpAmount);
++
++ const float occlusionAmount = 0.9f;
++ material.setOcclusionAmount(occlusionAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(occlusionAmount, material.occlusionAmount());
++ QCOMPARE(occlusionAmount, node->occlusionAmount);
++
++ const float alphaCutoff = 0.1f;
++ material.setAlphaCutoff(alphaCutoff);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(alphaCutoff, material.alphaCutoff());
++ QCOMPARE(alphaCutoff, node->alphaCutoff);
++
++ QColor color1("#12345678");
++ QVector4D color1Vec4 = QSSGUtils::color::sRGBToLinear(color1);
++ QColor color2("#cccccccc");
++ QVector3D color2Vec3 = QSSGUtils::color::sRGBToLinear(color2).toVector3D();
++ material.setBaseColor(color1);
++ material.setEmissiveFactor(color2Vec3);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QCOMPARE(color1Vec4, node->color);
++ // Note: emissiveColor doesn't contain alpha
++ QCOMPARE(color2Vec3, node->emissiveColor);
++
++ const float heightAmount = 0.2f;
++ material.setHeightAmount(heightAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(heightAmount, material.heightAmount());
++ QCOMPARE(heightAmount, node->heightAmount);
++
++ const int minHeightMapSamples = 11;
++ material.setMinHeightMapSamples(minHeightMapSamples);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(minHeightMapSamples, material.minHeightMapSamples());
++ QCOMPARE(minHeightMapSamples, node->minHeightSamples);
++
++ const int maxHeightMapSamples = 33;
++ material.setMaxHeightMapSamples(maxHeightMapSamples);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(maxHeightMapSamples, material.maxHeightMapSamples());
++ QCOMPARE(maxHeightMapSamples, node->maxHeightSamples);
++
++ QVERIFY(!node->isClearcoatEnabled());
++ float clearcoatAmount = 0.3f;
++ material.setClearcoatAmount(clearcoatAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(clearcoatAmount, material.clearcoatAmount());
++ QCOMPARE(clearcoatAmount, node->clearcoatAmount);
++ QVERIFY(node->isClearcoatEnabled());
++ clearcoatAmount = 0.0f;
++ material.setClearcoatAmount(clearcoatAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(!node->isClearcoatEnabled());
++
++ const float clearcoatRoughnessAmount = 0.4f;
++ material.setClearcoatRoughnessAmount(clearcoatRoughnessAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(clearcoatRoughnessAmount, material.clearcoatRoughnessAmount());
++ QCOMPARE(clearcoatRoughnessAmount, node->clearcoatRoughnessAmount);
++
++ // Sheen is disabled while the color is black, since the color acts as
++ // a multiplier for the sheen color map
++ QVERIFY(!node->isSheenEnabled());
++ material.setSheenColor(QColor(Qt::white));
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(QColor(Qt::white), material.sheenColor());
++ QVERIFY(node->isSheenEnabled());
++ material.setSheenColor(QColor(Qt::black));
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(!node->isSheenEnabled());
++
++ const float sheenRoughness = 0.6f;
++ material.setSheenRoughness(sheenRoughness);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(sheenRoughness, material.sheenRoughness());
++ QCOMPARE(sheenRoughness, node->sheenRoughness);
++
++ // Out of range values clamp, and a value that clamps to what the property
++ // already holds is not a change, so it must not emit
++ material.setSheenRoughness(2.0f);
++ QCOMPARE(material.sheenRoughness(), 1.0f);
++ QSignalSpy sheenRoughnessSpy(&material, &QQuick3DPrincipledMaterial::sheenRoughnessChanged);
++ material.setSheenRoughness(3.0f);
++ QCOMPARE(material.sheenRoughness(), 1.0f);
++ QCOMPARE(sheenRoughnessSpy.size(), 0);
++ material.setSheenRoughness(sheenRoughness);
++
++ QVERIFY(!node->isAnisotropyEnabled());
++ float anisotropyStrength = 0.7f;
++ material.setAnisotropyStrength(anisotropyStrength);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(anisotropyStrength, material.anisotropyStrength());
++ QCOMPARE(anisotropyStrength, node->anisotropyStrength);
++ QVERIFY(node->isAnisotropyEnabled());
++ anisotropyStrength = 0.0f;
++ material.setAnisotropyStrength(anisotropyStrength);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(!node->isAnisotropyEnabled());
++
++ // The property is in degrees to match the rest of the Qt Quick 3D API,
++ // while the render graph works in radians
++ const float anisotropyRotation = 90.0f;
++ material.setAnisotropyRotation(anisotropyRotation);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(anisotropyRotation, material.anisotropyRotation());
++ QCOMPARE(qDegreesToRadians(anisotropyRotation), node->anisotropyRotation);
++
++ QVERIFY(!node->isIridescenceEnabled());
++ float iridescenceFactor = 0.8f;
++ material.setIridescenceFactor(iridescenceFactor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(iridescenceFactor, material.iridescenceFactor());
++ QCOMPARE(iridescenceFactor, node->iridescenceFactor);
++ QVERIFY(node->isIridescenceEnabled());
++ iridescenceFactor = 0.0f;
++ material.setIridescenceFactor(iridescenceFactor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(!node->isIridescenceEnabled());
++
++ // The film has its own index of refraction, separate from the material's
++ QCOMPARE(1.3f, material.iridescenceIndexOfRefraction());
++ const float iridescenceIor = 1.8f;
++ material.setIridescenceIndexOfRefraction(iridescenceIor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(iridescenceIor, material.iridescenceIndexOfRefraction());
++ QCOMPARE(iridescenceIor, node->iridescenceIndexOfRefraction);
++
++ // Thickness is in nanometers, and defaults to the range the extension uses
++ QCOMPARE(100.0f, material.iridescenceThicknessMinimum());
++ QCOMPARE(400.0f, material.iridescenceThicknessMaximum());
++ const float thicknessMinimum = 250.0f;
++ const float thicknessMaximum = 900.0f;
++ material.setIridescenceThicknessMinimum(thicknessMinimum);
++ material.setIridescenceThicknessMaximum(thicknessMaximum);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(thicknessMinimum, node->iridescenceThicknessMinimum);
++ QCOMPARE(thicknessMaximum, node->iridescenceThicknessMaximum);
++
++ QVERIFY(material.metalness() == 0.0f);
++ QVERIFY(node->metalnessAmount == 0.0f);
++ // Dispersion only means something when there is transmission to refract,
++ // so the predicate has to account for both
++ QVERIFY(!node->isDispersionEnabled());
++ const float dispersion = 0.4f;
++ material.setDispersion(dispersion);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(dispersion, material.dispersion());
++ QCOMPARE(dispersion, node->dispersion);
++ QVERIFY(!node->isDispersionEnabled()); // no transmission yet
++
++ QVERIFY(!node->isTransmissionEnabled());
++ const float transmissionFactor = 0.5f;
++ material.setTransmissionFactor(transmissionFactor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(transmissionFactor, material.transmissionFactor());
++ QCOMPARE(transmissionFactor, node->transmissionFactor);
++ QVERIFY(node->isTransmissionEnabled());
++ QVERIFY(node->isDispersionEnabled()); // now both are set
++ material.setTransmissionFactor(0.0f);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(!node->isTransmissionEnabled());
++
++ const float thicknessFactor = 0.6f;
++ material.setThicknessFactor(thicknessFactor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(thicknessFactor, material.thicknessFactor());
++ QCOMPARE(thicknessFactor, node->thicknessFactor);
++ const float attenuationDistance = 0.7f;
++ material.setAttenuationDistance(attenuationDistance);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(attenuationDistance, material.attenuationDistance());
++ QCOMPARE(attenuationDistance, node->attenuationDistance);
++ const QColor attenuationColor = QColor(Qt::green);
++ const QVector3D attenuationColorVector = QSSGUtils::color::sRGBToLinear(attenuationColor).toVector3D();
++ material.setAttenuationColor(attenuationColor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(attenuationColor, material.attenuationColor());
++ QCOMPARE(attenuationColorVector, node->attenuationColor);
++
++ const float ior = 1.45f;
++ material.setIndexOfRefraction(ior);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(ior, material.indexOfRefraction());
++ QCOMPARE(ior, node->ior);
++ }
++
++ void tst_QQuick3DMaterials::testPrincipledTextures()
++ {
++ PrincipledMaterial material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ const auto originalNode = node; // for comparisons later...
++ QVERIFY(node);
++
++ Texture texture1;
++ texture1.setSource(QUrl(":/Built_with_Qt.png"));
++ Texture texture2;
++ texture2.setSource(QUrl(":/Built_with_Qt_2.png"));
++
++ QQuick3DSceneManager sceneManager;
++ sceneManager.updateDirtyResource(&texture1);
++ sceneManager.updateDirtyResource(&texture2);
++
++ // BasecolorMap
++ QVERIFY(!material.baseColorMap());
++ material.setBaseColorMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QVERIFY(material.baseColorMap());
++ QVERIFY(texture1.getRenderImage());
++ QCOMPARE(texture1.getRenderImage(), node->colorMap);
++
++ // MetalnessMap
++ material.setMetalnessMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QVERIFY(texture2.getRenderImage());
++ QCOMPARE(texture2.getRenderImage(), node->metalnessMap);
++
++ // SpecularMap
++ // Note: metalness needs to be disabled for setSpecularMap to affect backend.
++ material.setMetalness(0.0f);
++ QVERIFY(!material.specularMap());
++ material.setSpecularMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.specularMap());
++ QCOMPARE(texture1.getRenderImage(), node->specularMap);
++
++ // RoughnessMap
++ QVERIFY(!material.roughnessMap());
++ material.setRoughnessMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.roughnessMap());
++ QCOMPARE(texture2.getRenderImage(), node->roughnessMap);
++
++ // EmissiveMap
++ QVERIFY(!material.emissiveMap());
++ material.setEmissiveMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.emissiveMap());
++ QCOMPARE(texture2.getRenderImage(), node->emissiveMap);
++
++ // OpacityMap
++ QVERIFY(!material.opacityMap());
++ material.setOpacityMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.opacityMap());
++ QCOMPARE(texture2.getRenderImage(), node->opacityMap);
++
++ // NormalMap
++ QVERIFY(!material.normalMap());
++ material.setNormalMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.normalMap());
++ QCOMPARE(texture2.getRenderImage(), node->normalMap);
++
++ // SpecularReflectionMap
++ QVERIFY(!material.specularReflectionMap());
++ material.setSpecularReflectionMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.specularReflectionMap());
++ QCOMPARE(texture1.getRenderImage(), node->specularReflection);
++
++ // OcclusionMap
++ QVERIFY(!material.occlusionMap());
++ material.setOcclusionMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.occlusionMap());
++ QCOMPARE(texture1.getRenderImage(), node->occlusionMap);
++
++ // HeightMap
++ QVERIFY(!material.heightMap());
++ material.setHeightMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.heightMap());
++ QCOMPARE(texture1.getRenderImage(), node->heightMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping1 = QQuick3DMaterial::A;
++ material.setHeightChannel(channelMapping1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping1, material.heightChannel());
++ QCOMPARE(qToUnderlying(channelMapping1), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->heightChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // ClearcoatMap
++ QVERIFY(!material.clearcoatMap());
++ material.setClearcoatMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.clearcoatMap());
++ QCOMPARE(texture1.getRenderImage(), node->clearcoatMap);
++ QVERIFY(material.clearcoatChannel() == QQuick3DMaterial::R);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping = QQuick3DMaterial::B;
++ material.setClearcoatChannel(channelMapping);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping, material.clearcoatChannel());
++ QCOMPARE(qToUnderlying(channelMapping), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->clearcoatChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // ClearcoatRoughnessMap
++ QVERIFY(!material.clearcoatRoughnessMap());
++ material.setClearcoatRoughnessMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.clearcoatRoughnessMap());
++ QCOMPARE(texture1.getRenderImage(), node->clearcoatRoughnessMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping2 = QQuick3DMaterial::A;
++ material.setClearcoatRoughnessChannel(channelMapping2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping2, material.clearcoatRoughnessChannel());
++ QCOMPARE(qToUnderlying(channelMapping2), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->clearcoatRoughnessChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // SheenColorMap (RGB, so no channel selection)
++ QVERIFY(!material.sheenColorMap());
++ material.setSheenColorMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.sheenColorMap());
++ QCOMPARE(texture1.getRenderImage(), node->sheenColorMap);
++
++ // SheenRoughnessMap; the glTF extension packs sheen roughness into the
++ // alpha channel, so that is our default too
++ QVERIFY(!material.sheenRoughnessMap());
++ QVERIFY(material.sheenRoughnessChannel() == QQuick3DMaterial::A);
++ material.setSheenRoughnessMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.sheenRoughnessMap());
++ QCOMPARE(texture1.getRenderImage(), node->sheenRoughnessMap);
++ const QQuick3DMaterial::TextureChannelMapping sheenChannel = QQuick3DMaterial::G;
++ material.setSheenRoughnessChannel(sheenChannel);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(sheenChannel, material.sheenRoughnessChannel());
++ QCOMPARE(qToUnderlying(sheenChannel), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->sheenRoughnessChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // AnisotropyMap has a fixed RG=direction, B=strength layout, so unlike the
++ // other maps it has no channel property
++ QVERIFY(!material.anisotropyMap());
++ material.setAnisotropyMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.anisotropyMap());
++ QCOMPARE(texture1.getRenderImage(), node->anisotropyMap);
++
++ // IridescenceMap
++ QVERIFY(!material.iridescenceMap());
++ QVERIFY(material.iridescenceChannel() == QQuick3DMaterial::R);
++ material.setIridescenceMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.iridescenceMap());
++ QCOMPARE(texture1.getRenderImage(), node->iridescenceMap);
++ const QQuick3DMaterial::TextureChannelMapping iridescenceChannel = QQuick3DMaterial::B;
++ material.setIridescenceChannel(iridescenceChannel);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(iridescenceChannel, material.iridescenceChannel());
++ QCOMPARE(qToUnderlying(iridescenceChannel), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->iridescenceChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // IridescenceThicknessMap; the glTF extension packs thickness into green
++ QVERIFY(!material.iridescenceThicknessMap());
++ QVERIFY(material.iridescenceThicknessChannel() == QQuick3DMaterial::G);
++ material.setIridescenceThicknessMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.iridescenceThicknessMap());
++ QCOMPARE(texture1.getRenderImage(), node->iridescenceThicknessMap);
++ const QQuick3DMaterial::TextureChannelMapping thicknessChannel = QQuick3DMaterial::A;
++ material.setIridescenceThicknessChannel(thicknessChannel);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(thicknessChannel, material.iridescenceThicknessChannel());
++ QCOMPARE(qToUnderlying(thicknessChannel), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->iridescenceThicknessChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // ClearcoatNormalMap
++ QVERIFY(!material.clearcoatNormalMap());
++ material.setClearcoatNormalMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.clearcoatNormalMap());
++ QCOMPARE(texture2.getRenderImage(), node->clearcoatNormalMap);
++
++ // TransmissionMap
++ QVERIFY(!material.transmissionMap());
++ material.setTransmissionMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.transmissionMap());
++ QCOMPARE(texture1.getRenderImage(), node->transmissionMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping3 = QQuick3DMaterial::B;
++ material.setTransmissionChannel(channelMapping3);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping3, material.transmissionChannel());
++ QCOMPARE(qToUnderlying(channelMapping3), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->transmissionChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // Thickness Map
++ QVERIFY(!material.thicknessMap());
++ material.setThicknessMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.thicknessMap());
++ QCOMPARE(texture1.getRenderImage(), node->thicknessMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping4 = QQuick3DMaterial::A;
++ material.setThicknessChannel(channelMapping4);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping4, material.thicknessChannel());
++ QCOMPARE(qToUnderlying(channelMapping4), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->thicknessChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++ }
++
++ void tst_QQuick3DMaterials::testPrincipledEnums()
++ {
++ PrincipledMaterial material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QVERIFY(node);
++
++ // These test rely on the different enums having the same values
++ auto lightModes = { QQuick3DPrincipledMaterial::Lighting::NoLighting,
++ QQuick3DPrincipledMaterial::Lighting::FragmentLighting };
++ for (const auto lightMode : lightModes) {
++ material.setLighting(lightMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(int(material.lighting()), int(node->lighting));
++ }
++
++ auto blendModes = { QQuick3DPrincipledMaterial::BlendMode::SourceOver,
++ QQuick3DPrincipledMaterial::BlendMode::Screen,
++ QQuick3DPrincipledMaterial::BlendMode::Multiply };
++ for (const auto blendMode : blendModes) {
++ material.setBlendMode(blendMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(int(material.blendMode()), int(node->blendMode));
++ }
++
++ auto alphaModes = { QQuick3DPrincipledMaterial::AlphaMode::Default,
++ QQuick3DPrincipledMaterial::AlphaMode::Mask,
++ QQuick3DPrincipledMaterial::AlphaMode::Blend };
++ for (const auto alphaMode : alphaModes) {
++ material.setAlphaMode(alphaMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(int(material.alphaMode()), int(node->alphaMode));
++ }
++
++ auto depthDrawModes = {
++ QQuick3DMaterial::DepthDrawMode::OpaqueOnlyDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::AlwaysDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::NeverDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::OpaquePrePassDepthDraw
++ };
++
++ for (const auto depthDrawMode : depthDrawModes) {
++ material.setDepthDrawMode(depthDrawMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QCOMPARE(int(material.depthDrawMode()), int(node->depthDrawMode));
++ }
++ }
++
++ void tst_QQuick3DMaterials::testSpecularGlossyProperties()
++ {
++ SpecularGlossyMaterial material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ const auto originalNode = node; // for comparisons later...
++ QVERIFY(node);
++
++ const QColor specularColor = QColor::fromRgbF(0.3f, 0.3f, 0.3f, 1.0f);
++ const QVector3D specular = QSSGUtils::color::sRGBToLinear(specularColor).toVector3D();
++ material.setSpecularColor(specularColor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(specularColor, material.specularColor());
++ QCOMPARE(specular.y(), node->specularTint.y()); // yes, stored in specularTint
++
++ const float glossiness = 0.4f;
++ material.setGlossiness(glossiness);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(glossiness, material.glossiness());
++ QCOMPARE(glossiness, node->specularRoughness); // yes that is correct
++
++ const float opacity = 0.7f;
++ material.setOpacity(opacity);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(opacity, material.opacity());
++ QCOMPARE(opacity, node->opacity);
++
++ const float normalStrength = 0.8f;
++ material.setNormalStrength(normalStrength);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(normalStrength, material.normalStrength());
++ QCOMPARE(normalStrength, node->bumpAmount);
++
++ const float occlusionAmount = 0.9f;
++ material.setOcclusionAmount(occlusionAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(occlusionAmount, material.occlusionAmount());
++ QCOMPARE(occlusionAmount, node->occlusionAmount);
++
++ const float alphaCutoff = 0.1f;
++ material.setAlphaCutoff(alphaCutoff);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(alphaCutoff, material.alphaCutoff());
++ QCOMPARE(alphaCutoff, node->alphaCutoff);
++
++ QColor color1("#12345678");
++ QVector4D color1Vec4 = QSSGUtils::color::sRGBToLinear(color1);
++ QColor color2("#cccccccc");
++ QVector3D color2Vec3 = QSSGUtils::color::sRGBToLinear(color2).toVector3D();
++ material.setAlbedoColor(color1);
++ material.setEmissiveFactor(color2Vec3);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QCOMPARE(color1Vec4, node->color);
++ // Note: emissiveColor doesn't contain alpha
++ QCOMPARE(color2Vec3, node->emissiveColor);
++
++ const float heightAmount = 0.2f;
++ material.setHeightAmount(heightAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(heightAmount, material.heightAmount());
++ QCOMPARE(heightAmount, node->heightAmount);
++
++ const int minHeightMapSamples = 11;
++ material.setMinHeightMapSamples(minHeightMapSamples);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(minHeightMapSamples, material.minHeightMapSamples());
++ QCOMPARE(minHeightMapSamples, node->minHeightSamples);
++
++ const int maxHeightMapSamples = 33;
++ material.setMaxHeightMapSamples(maxHeightMapSamples);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(maxHeightMapSamples, material.maxHeightMapSamples());
++ QCOMPARE(maxHeightMapSamples, node->maxHeightSamples);
++
++ QVERIFY(!node->isClearcoatEnabled());
++ float clearcoatAmount = 0.3f;
++ material.setClearcoatAmount(clearcoatAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(clearcoatAmount, material.clearcoatAmount());
++ QCOMPARE(clearcoatAmount, node->clearcoatAmount);
++ QVERIFY(node->isClearcoatEnabled());
++ clearcoatAmount = 0.0f;
++ material.setClearcoatAmount(clearcoatAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(!node->isClearcoatEnabled());
++
++ const float clearcoatRoughnessAmount = 0.4f;
++ material.setClearcoatRoughnessAmount(clearcoatRoughnessAmount);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(clearcoatRoughnessAmount, material.clearcoatRoughnessAmount());
++ QCOMPARE(clearcoatRoughnessAmount, node->clearcoatRoughnessAmount);
++
++ QVERIFY(!node->isTransmissionEnabled());
++ const float transmissionFactor = 0.5f;
++ material.setTransmissionFactor(transmissionFactor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(transmissionFactor, material.transmissionFactor());
++ QCOMPARE(transmissionFactor, node->transmissionFactor);
++ QVERIFY(node->isTransmissionEnabled());
++ material.setTransmissionFactor(0.0f);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(!node->isTransmissionEnabled());
++
++ const float thicknessFactor = 0.6f;
++ material.setThicknessFactor(thicknessFactor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(thicknessFactor, material.thicknessFactor());
++ QCOMPARE(thicknessFactor, node->thicknessFactor);
++ const float attenuationDistance = 0.7f;
++ material.setAttenuationDistance(attenuationDistance);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(attenuationDistance, material.attenuationDistance());
++ QCOMPARE(attenuationDistance, node->attenuationDistance);
++ const QColor attenuationColor = QColor(Qt::green);
++ const QVector3D attenuationColorVector = QSSGUtils::color::sRGBToLinear(attenuationColor).toVector3D();
++ material.setAttenuationColor(attenuationColor);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(attenuationColor, material.attenuationColor());
++ QCOMPARE(attenuationColorVector, node->attenuationColor);
++ }
++
++ void tst_QQuick3DMaterials::testSpecularGlossyTextures()
++ {
++ SpecularGlossyMaterial material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ const auto originalNode = node; // for comparisons later...
++ QVERIFY(node);
++
++ Texture texture1;
++ texture1.setSource(QUrl(":/Built_with_Qt.png"));
++ Texture texture2;
++ texture2.setSource(QUrl(":/Built_with_Qt_2.png"));
++
++ QQuick3DSceneManager sceneManager;
++ sceneManager.updateDirtyResource(&texture1);
++ sceneManager.updateDirtyResource(&texture2);
++
++ // Albedo
++ QVERIFY(!material.albedoMap());
++ material.setAlbedoMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QVERIFY(material.albedoMap());
++ QVERIFY(texture1.getRenderImage());
++ QCOMPARE(texture1.getRenderImage(), node->colorMap);
++
++ // SpecularMap
++ material.setSpecularMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(originalNode, node);
++ QVERIFY(texture2.getRenderImage());
++ QCOMPARE(texture2.getRenderImage(), node->specularMap);
++
++ // GlossinessMap
++ QVERIFY(!material.glossinessMap());
++ material.setGlossinessMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.glossinessMap());
++ QCOMPARE(texture2.getRenderImage(), node->roughnessMap); // stored as roughness in backend
++
++ // EmissiveMap
++ QVERIFY(!material.emissiveMap());
++ material.setEmissiveMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.emissiveMap());
++ QCOMPARE(texture2.getRenderImage(), node->emissiveMap);
++
++ // OpacityMap
++ QVERIFY(!material.opacityMap());
++ material.setOpacityMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.opacityMap());
++ QCOMPARE(texture2.getRenderImage(), node->opacityMap);
++
++ // NormalMap
++ QVERIFY(!material.normalMap());
++ material.setNormalMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.normalMap());
++ QCOMPARE(texture2.getRenderImage(), node->normalMap);
++
++ // OcclusionMap
++ QVERIFY(!material.occlusionMap());
++ material.setOcclusionMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.occlusionMap());
++ QCOMPARE(texture1.getRenderImage(), node->occlusionMap);
++
++ // HeightMap
++ QVERIFY(!material.heightMap());
++ material.setHeightMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.heightMap());
++ QCOMPARE(texture1.getRenderImage(), node->heightMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping1 = QQuick3DMaterial::A;
++ material.setHeightChannel(channelMapping1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping1, material.heightChannel());
++ QCOMPARE(qToUnderlying(channelMapping1), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->heightChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // ClearcoatMap
++ QVERIFY(!material.clearcoatMap());
++ material.setClearcoatMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.clearcoatMap());
++ QCOMPARE(texture1.getRenderImage(), node->clearcoatMap);
++ QVERIFY(material.clearcoatChannel() == QQuick3DMaterial::R);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping = QQuick3DMaterial::B;
++ material.setClearcoatChannel(channelMapping);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping, material.clearcoatChannel());
++ QCOMPARE(qToUnderlying(channelMapping), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->clearcoatChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // ClearcoatRoughnessMap
++ QVERIFY(!material.clearcoatRoughnessMap());
++ material.setClearcoatRoughnessMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.clearcoatRoughnessMap());
++ QCOMPARE(texture1.getRenderImage(), node->clearcoatRoughnessMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping2 = QQuick3DMaterial::A;
++ material.setClearcoatRoughnessChannel(channelMapping2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping2, material.clearcoatRoughnessChannel());
++ QCOMPARE(qToUnderlying(channelMapping2), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->clearcoatRoughnessChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // ClearcoatNormalMap
++ QVERIFY(!material.clearcoatNormalMap());
++ material.setClearcoatNormalMap(&texture2);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.clearcoatNormalMap());
++ QCOMPARE(texture2.getRenderImage(), node->clearcoatNormalMap);
++
++ // TransmissionMap
++ QVERIFY(!material.transmissionMap());
++ material.setTransmissionMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.transmissionMap());
++ QCOMPARE(texture1.getRenderImage(), node->transmissionMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping3 = QQuick3DMaterial::B;
++ material.setTransmissionChannel(channelMapping3);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping3, material.transmissionChannel());
++ QCOMPARE(qToUnderlying(channelMapping3), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->transmissionChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++
++ // Thickness Map
++ QVERIFY(!material.thicknessMap());
++ material.setThicknessMap(&texture1);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QVERIFY(material.thicknessMap());
++ QCOMPARE(texture1.getRenderImage(), node->thicknessMap);
++ const QQuick3DMaterial::TextureChannelMapping channelMapping4 = QQuick3DMaterial::A;
++ material.setThicknessChannel(channelMapping4);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(channelMapping4, material.thicknessChannel());
++ QCOMPARE(qToUnderlying(channelMapping4), // QQuick3DMaterial::TextureChannelMapping
++ qToUnderlying(node->thicknessChannel)); // QSSGRenderDefaultMaterial::TextureChannelMapping
++ }
++
++ void tst_QQuick3DMaterials::testSpecularGlossyEnums()
++ {
++ SpecularGlossyMaterial material;
++ auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QVERIFY(node);
++
++ // These test rely on the different enums having the same values
++ auto lightModes = { QQuick3DSpecularGlossyMaterial::Lighting::NoLighting,
++ QQuick3DSpecularGlossyMaterial::Lighting::FragmentLighting };
++ for (const auto lightMode : lightModes) {
++ material.setLighting(lightMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(int(material.lighting()), int(node->lighting));
++ }
++
++ auto blendModes = { QQuick3DSpecularGlossyMaterial::BlendMode::SourceOver,
++ QQuick3DSpecularGlossyMaterial::BlendMode::Screen,
++ QQuick3DSpecularGlossyMaterial::BlendMode::Multiply };
++ for (const auto blendMode : blendModes) {
++ material.setBlendMode(blendMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(int(material.blendMode()), int(node->blendMode));
++ }
++
++ auto alphaModes = { QQuick3DSpecularGlossyMaterial::AlphaMode::Default,
++ QQuick3DSpecularGlossyMaterial::AlphaMode::Mask,
++ QQuick3DSpecularGlossyMaterial::AlphaMode::Blend };
++ for (const auto alphaMode : alphaModes) {
++ material.setAlphaMode(alphaMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node));
++ QCOMPARE(int(material.alphaMode()), int(node->alphaMode));
++ }
++
++ auto depthDrawModes = {
++ QQuick3DMaterial::DepthDrawMode::OpaqueOnlyDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::AlwaysDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::NeverDepthDraw,
++ QQuick3DMaterial::DepthDrawMode::OpaquePrePassDepthDraw
++ };
++
++ for (const auto depthDrawMode : depthDrawModes) {
++ material.setDepthDrawMode(depthDrawMode);
++ node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr));
++ QCOMPARE(int(material.depthDrawMode()), int(node->depthDrawMode));
++ }
++ }
++
++
++ void tst_QQuick3DMaterials::testCustomMaterials()
++ {
++ QSKIP("Test can not work as implemented without crashing");
++ CustomMaterial material;
++ QQuick3DViewport *view3D = new QQuick3DViewport;
++ material.setParent(view3D);
++ auto node = static_cast<QSSGRenderCustomMaterial *>(material.updateSpatialNode(nullptr));
++ QVERIFY(node);
++
++ QQuick3DShaderUtilsTextureInput mTexture;
++ QQuick3DTexture qTexture;
++ QSignalSpy spy(&mTexture, SIGNAL(textureChanged()));
++ mTexture.setTexture(&qTexture);
++ QCOMPARE(spy.size(), 1);
++ QVERIFY(&qTexture == mTexture.texture());
++
++ QQuick3DShaderUtilsBuffer mBuffer;
++ auto format = QQuick3DShaderUtilsBuffer::TextureFormat::RGBA8;
++ mBuffer.setFormat(format);
++ auto filterOp = QQuick3DShaderUtilsBuffer::TextureFilterOperation::Nearest;
++ mBuffer.setTextureFilterOperation(filterOp);
++ QCOMPARE(format, mBuffer.format());
++ QCOMPARE(filterOp, mBuffer.textureFilterOperation());
++
++ // TODO: Extend custom material testing with proper shaders
++
++ view3D->deleteLater();
++ }
++
++ QTEST_APPLESS_MAIN(tst_QQuick3DMaterials)
++ #include "tst_qquick3dmaterials.moc"