Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qquick3dcustommaterial.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5#include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
8#include <QtQuick/QQuickWindow>
9
10#include "qquick3dobject_p.h"
11#include "qquick3dviewport_p.h"
13
15
17
1265{
1266 switch (mode) {
1297 default:
1299 }
1300}
1301
1304{
1305}
1306
1308
1310{
1311 return m_srcBlend;
1312}
1313
1315{
1316 if (m_srcBlend == mode)
1317 return;
1318
1319 m_srcBlend = mode;
1320 update();
1322}
1323
1325{
1326 return m_dstBlend;
1327}
1328
1330{
1331 if (m_dstBlend == mode)
1332 return;
1333
1334 m_dstBlend = mode;
1335 update();
1337}
1338
1340{
1341 return m_shadingMode;
1342}
1343
1345{
1346 if (m_shadingMode == mode)
1347 return;
1348
1349 m_shadingMode = mode;
1352}
1353
1355{
1356 return m_vertexShader;
1357}
1358
1360{
1361 if (m_vertexShader == url)
1362 return;
1363
1364 m_vertexShader = url;
1367}
1368
1370{
1371 return m_fragmentShader;
1372}
1373
1375{
1376 if (m_fragmentShader == url)
1377 return;
1378
1379 m_fragmentShader = url;
1382}
1383
1385{
1386 return m_lineWidth;
1387}
1388
1390{
1391 if (qFuzzyCompare(m_lineWidth, width))
1392 return;
1393 m_lineWidth = width;
1394 update();
1396}
1397
1399{
1400 m_dirtyAttributes |= Dirty::AllDirty;
1402}
1403
1405{
1406 if (!(that.m_dirtyAttributes & quint32(type))) {
1407 that.m_dirtyAttributes |= quint32(type);
1408 that.update();
1409 }
1410}
1411
1413{
1414 return m_alwaysDirty;
1415}
1416
1418{
1419 if (m_alwaysDirty == alwaysDirty)
1420 return;
1421
1422 m_alwaysDirty = alwaysDirty;
1423 update();
1425}
1426
1428{
1451}
1452
1454{
1455 using namespace QSSGShaderUtils;
1456
1457 const auto &renderContext = QQuick3DObjectPrivate::get(this)->sceneManager->wattached->rci();
1458 if (!renderContext) {
1459 qWarning("QQuick3DCustomMaterial: No render context interface?");
1460 return nullptr;
1461 }
1462
1464 QSSGRenderCustomMaterial *customMaterial = static_cast<QSSGRenderCustomMaterial *>(node);
1465 bool newBackendNode = false;
1466 bool shadersMayChange = false;
1467 if (!customMaterial) {
1468 customMaterial = new QSSGRenderCustomMaterial;
1469 newBackendNode = true;
1470 } else if (m_dirtyAttributes & ShaderSettingsDirty) {
1471 shadersMayChange = true;
1472 }
1473
1474 if (newBackendNode || shadersMayChange) {
1475 markAllDirty();
1476
1477 customMaterial->m_properties.clear();
1478 customMaterial->m_textureProperties.clear();
1479
1480 customMaterial->m_shadingMode = QSSGRenderCustomMaterial::ShadingMode(int(m_shadingMode));
1481
1482 QMetaMethod propertyDirtyMethod;
1483 const int idx = metaObject()->indexOfSlot("onPropertyDirty()");
1484 if (idx != -1)
1485 propertyDirtyMethod = metaObject()->method(idx);
1486
1487 const int propCount = metaObject()->propertyCount();
1488 int propOffset = metaObject()->propertyOffset();
1489
1490 // Custom materials can have multilayered inheritance structure, so find the actual propOffset
1491 const QMetaObject *superClass = metaObject()->superClass();
1492 while (superClass && qstrcmp(superClass->className(), "QQuick3DCustomMaterial") != 0) {
1493 propOffset = superClass->propertyOffset();
1494 superClass = superClass->superClass();
1495 }
1496
1497 using TextureInputProperty = QPair<QQuick3DShaderUtilsTextureInput *, const char *>;
1498 QVector<TextureInputProperty> textureProperties; // We'll deal with these later
1499
1500 for (int i = propOffset; i != propCount; ++i) {
1501 const auto property = metaObject()->property(i);
1502 if (Q_UNLIKELY(!property.isValid()))
1503 continue;
1504
1505 const auto name = property.name();
1506 QMetaType propType = property.metaType();
1507 QVariant propValue = property.read(this);
1508 if (propType == QMetaType(QMetaType::QVariant))
1509 propType = propValue.metaType();
1510
1511 if (propType.id() >= QMetaType::User) {
1512 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
1514 textureProperties.push_back({texture, name});
1515 }
1516 } else if (propType == QMetaType(QMetaType::QObjectStar)) {
1517 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(propValue.value<QObject *>()))
1518 textureProperties.push_back({texture, name});
1519 } else {
1520 const auto type = uniformType(propType);
1522 uniforms.append({ uniformTypeName(propType), name });
1523 customMaterial->m_properties.push_back({ name, propValue, uniformType(propType), i});
1524 if (newBackendNode) {
1525 // Track the property changes
1526 if (property.hasNotifySignal() && propertyDirtyMethod.isValid())
1527 connect(this, property.notifySignal(), this, propertyDirtyMethod);
1528 } // else already connected
1529 } else {
1530 // ### figure out how _not_ to warn when there are no dynamic
1531 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
1532 //qWarning("No known uniform conversion found for effect property %s. Skipping", property.name());
1533 }
1534 }
1535 }
1536
1537 const auto processTextureProperty = [&](QQuick3DShaderUtilsTextureInput &texture, const QByteArray &name) {
1538 texture.name = name;
1539
1541 textureData.texInput = &texture;
1542 textureData.name = name;
1544
1545 if (newBackendNode) {
1546 connect(&texture, &QQuick3DShaderUtilsTextureInput::enabledChanged, this, &QQuick3DCustomMaterial::onTextureDirty);
1547 connect(&texture, &QQuick3DShaderUtilsTextureInput::textureChanged, this, &QQuick3DCustomMaterial::onTextureDirty);
1548 } // else already connected
1549
1550 QQuick3DTexture *tex = texture.texture(); // may be null if the TextureInput has no 'texture' set
1551 if (tex && QQuick3DObjectPrivate::get(tex)->type == QQuick3DObjectPrivate::Type::ImageCube)
1552 uniforms.append({ QByteArrayLiteral("samplerCube"), textureData.name });
1553 else if (tex && tex->textureData() && tex->textureData()->depth() > 0)
1554 uniforms.append({ QByteArrayLiteral("sampler3D"), textureData.name });
1555 else
1556 uniforms.append({ QByteArrayLiteral("sampler2D"), textureData.name });
1557
1558 customMaterial->m_textureProperties.push_back(textureData);
1559 };
1560
1561 for (const auto &textureProperty : std::as_const(textureProperties))
1562 processTextureProperty(*textureProperty.first, textureProperty.second);
1563
1564 if (customMaterial->incompleteBuildTimeObject || (m_dirtyAttributes & DynamicPropertiesDirty)) { // This object came from the shadergen tool
1565 const auto names = dynamicPropertyNames();
1566 for (const auto &name : names) {
1567 QVariant propValue = property(name.constData());
1568 QMetaType propType = propValue.metaType();
1569 if (propType == QMetaType(QMetaType::QVariant))
1570 propType = propValue.metaType();
1571
1572 if (propType.id() >= QMetaType::User) {
1573 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
1575 textureProperties.push_back({texture, name});
1576 }
1577 } else if (propType.id() == QMetaType::QObjectStar) {
1578 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(propValue.value<QObject *>()))
1579 textureProperties.push_back({texture, name});
1580 } else {
1581 const auto type = uniformType(propType);
1583 uniforms.append({ uniformTypeName(propType), name });
1584 customMaterial->m_properties.push_back({ name, propValue,
1585 uniformType(propType), -1 /* aka. dynamic property */});
1586 // We don't need to track property changes
1587 } else {
1588 // ### figure out how _not_ to warn when there are no dynamic
1589 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
1590 qWarning("No known uniform conversion found for custom material property %s. Skipping", name.constData());
1591 }
1592 }
1593 }
1594
1595 for (const auto &property : std::as_const(textureProperties))
1596 processTextureProperty(*property.first, property.second);
1597 }
1598
1599 const QQmlContext *context = qmlContext(this);
1600 QByteArray vertex, fragment;
1601 QSSGCustomShaderMetaData vertexMeta, fragmentMeta;
1602 QByteArray shaderPathKey("custom material --");
1603
1604 customMaterial->m_renderFlags = {};
1605
1606 if (!m_vertexShader.isEmpty()) {
1607 vertex = QSSGShaderUtils::resolveShader(m_vertexShader, context, shaderPathKey);
1608 QByteArray shaderCodeMeta;
1610 vertex,
1612 uniforms);
1613 vertex = result.first;
1614 vertex.append(shaderCodeMeta);
1615 vertexMeta = result.second;
1616
1617 setCustomMaterialFlagsFromShader(customMaterial, vertexMeta);
1618
1619 if (vertexMeta.flags.testFlag(QSSGCustomShaderMetaData::OverridesPosition))
1621 }
1622
1623 if (!m_fragmentShader.isEmpty()) {
1624 fragment = QSSGShaderUtils::resolveShader(m_fragmentShader, context, shaderPathKey);
1625 QByteArray shaderCodeMeta;
1627 fragment,
1629 uniforms);
1630 fragment = result.first;
1631 fragment.append(shaderCodeMeta);
1632 fragmentMeta = result.second;
1633
1634 setCustomMaterialFlagsFromShader(customMaterial, fragmentMeta);
1635
1636 if (fragmentMeta.flags.testFlag(QSSGCustomShaderMetaData::UsesSharedVars))
1637 customMaterial->m_usesSharedVariables = true;
1638 }
1639
1640 // At this point we have snippets that look like this:
1641 // - the original code, with VARYING ... lines removed
1642 // - followed by QQ3D_SHADER_META block for uniforms
1643 // - followed by QQ3D_SHADER_META block for inputs/outputs
1644
1645 customMaterial->m_customShaderPresence = {};
1646 if (!vertex.isEmpty() || !fragment.isEmpty()) {
1647 customMaterial->m_shaderPathKey = shaderPathKey.append(':' + QCryptographicHash::hash(QByteArray(vertex + fragment), QCryptographicHash::Algorithm::Sha1).toHex());
1648
1649 if (!vertex.isEmpty()) {
1651 renderContext->shaderLibraryManager()->setShaderSource(shaderPathKey, QSSGShaderCache::ShaderType::Vertex, vertex, vertexMeta);
1652 }
1653
1654 if (!fragment.isEmpty()) {
1656 renderContext->shaderLibraryManager()->setShaderSource(shaderPathKey, QSSGShaderCache::ShaderType::Fragment, fragment, fragmentMeta);
1657 }
1658 }
1659 }
1660
1661 customMaterial->setAlwaysDirty(m_alwaysDirty);
1662 if (m_srcBlend != BlendMode::NoBlend && m_dstBlend != BlendMode::NoBlend) { // both must be set to something other than NoBlend
1663 customMaterial->m_renderFlags.setFlag(QSSGRenderCustomMaterial::RenderFlag::Blending, true);
1664 customMaterial->m_srcBlend = toRhiBlendFactor(m_srcBlend);
1665 customMaterial->m_dstBlend = toRhiBlendFactor(m_dstBlend);
1666 } else {
1667 customMaterial->m_renderFlags.setFlag(QSSGRenderCustomMaterial::RenderFlag::Blending, false);
1668 }
1669 customMaterial->m_lineWidth = m_lineWidth;
1670
1672
1673 if (m_dirtyAttributes & Dirty::PropertyDirty) {
1674 for (auto &prop : customMaterial->m_properties) {
1675 auto p = metaObject()->property(prop.pid);
1676 if (Q_LIKELY(p.isValid()))
1677 prop.value = p.read(this);
1678 }
1679 }
1680
1681 if (m_dirtyAttributes & Dirty::TextureDirty) {
1682 for (QSSGRenderCustomMaterial::TextureProperty &prop : customMaterial->m_textureProperties) {
1683 QQuick3DTexture *tex = prop.texInput->texture();
1684 if (tex) {
1685 if (prop.texInput->enabled)
1686 prop.texImage = tex->getRenderImage();
1687 else
1688 prop.texImage = nullptr;
1702 } else {
1703 prop.texImage = nullptr;
1704 }
1705
1706 if (tex != prop.lastConnectedTexture) {
1707 prop.lastConnectedTexture = tex;
1708 disconnect(prop.minFilterChangedConn);
1709 disconnect(prop.magFilterChangedConn);
1710 disconnect(prop.mipFilterChangedConn);
1711 disconnect(prop.horizontalTilingChangedConn);
1712 disconnect(prop.verticalTilingChangedConn);
1713 if (tex) {
1714 prop.minFilterChangedConn = connect(tex, &QQuick3DTexture::minFilterChanged, this, &QQuick3DCustomMaterial::onTextureDirty);
1715 prop.magFilterChangedConn = connect(tex, &QQuick3DTexture::magFilterChanged, this, &QQuick3DCustomMaterial::onTextureDirty);
1716 prop.mipFilterChangedConn = connect(tex, &QQuick3DTexture::mipFilterChanged, this, &QQuick3DCustomMaterial::onTextureDirty);
1717 prop.horizontalTilingChangedConn = connect(tex, &QQuick3DTexture::horizontalTilingChanged, this, &QQuick3DCustomMaterial::onTextureDirty);
1718 prop.verticalTilingChangedConn = connect(tex, &QQuick3DTexture::verticalTilingChanged, this, &QQuick3DCustomMaterial::onTextureDirty);
1719 }
1720 }
1721 }
1722 }
1723
1724 m_dirtyAttributes = 0;
1725
1726 return customMaterial;
1727}
1728
1729void QQuick3DCustomMaterial::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
1730{
1732 if (change == QQuick3DObject::ItemSceneChange) {
1733 if (auto sceneManager = value.sceneManager) {
1734 for (const auto &it : std::as_const(m_dynamicTextureMaps)) {
1735 if (auto tex = it->texture())
1736 QQuick3DObjectPrivate::refSceneManager(tex, *sceneManager);
1737 }
1738 } else {
1739 for (const auto &it : std::as_const(m_dynamicTextureMaps)) {
1740 if (auto tex = it->texture())
1742 }
1743 }
1744 }
1745}
1746
1747void QQuick3DCustomMaterial::onPropertyDirty()
1748{
1750 update();
1751}
1752
1753void QQuick3DCustomMaterial::onTextureDirty()
1754{
1756 update();
1757}
1758
1759void QQuick3DCustomMaterial::setDynamicTextureMap(QQuick3DShaderUtilsTextureInput *textureMap)
1760{
1761 // There can only be one texture input per property, as the texture input is a combination
1762 // of the texture used and the uniform name!
1763 auto it = m_dynamicTextureMaps.constFind(textureMap);
1764
1765 if (it == m_dynamicTextureMaps.constEnd()) {
1766 // Track the object, if it's destroyed we need to remove it from our table.
1767 connect(textureMap, &QQuick3DShaderUtilsTextureInput::destroyed, this, [this, textureMap]() {
1768 auto it = m_dynamicTextureMaps.constFind(textureMap);
1769 if (it != m_dynamicTextureMaps.constEnd())
1770 m_dynamicTextureMaps.erase(it);
1771 });
1772 m_dynamicTextureMaps.insert(textureMap);
1773
1774 update();
1775 }
1776}
1777
\inmodule QtCore
Definition qbytearray.h:57
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
QByteArray first(qsizetype n) const
Definition qbytearray.h:159
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QByteArray hash(QByteArrayView data, Algorithm method)
Returns the hash of data using method.
Definition qlist.h:74
void push_back(parameter_type t)
Definition qlist.h:672
void clear()
Definition qlist.h:417
\inmodule QtCore
Definition qmetaobject.h:18
bool isValid() const
\inmodule QtCore
Definition qmetatype.h:320
int id(int=0) const
Definition qmetatype.h:454
friend class QVariant
Definition qmetatype.h:775
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
QList< QByteArray > dynamicPropertyNames() const
Definition qobject.cpp:4216
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
The QQmlContext class defines a context within a QML engine.
Definition qqmlcontext.h:25
void setAlwaysDirty(bool alwaysDirty)
QQuick3DCustomMaterial(QQuick3DObject *parent=nullptr)
void setFragmentShader(const QUrl &url)
void setSrcBlend(QQuick3DCustomMaterial::BlendMode mode)
static void markDirty(QQuick3DCustomMaterial &that, QQuick3DCustomMaterial::Dirty type)
void itemChange(ItemChange, const ItemChangeData &) override
void setVertexShader(const QUrl &url)
QSSGRenderGraphObject * updateSpatialNode(QSSGRenderGraphObject *node) override
void setDstBlend(QQuick3DCustomMaterial::BlendMode mode)
void setShadingMode(QQuick3DCustomMaterial::ShadingMode mode)
QSSGRenderGraphObject * updateSpatialNode(QSSGRenderGraphObject *node) override
void itemChange(ItemChange, const ItemChangeData &) override
QPointer< QQuick3DSceneManager > sceneManager
void refSceneManager(QQuick3DSceneManager &)
static QQuick3DObjectPrivate * get(QQuick3DObject *item)
\qmltype Object3D \inqmlmodule QtQuick3D \instantiates QQuick3DObject \inherits QtObject
virtual void markAllDirty()
QPointer< QQuick3DWindowAttachment > wattached
int depth() const
Returns the depth of the texture data in pixels.
void minFilterChanged()
void horizontalTilingChanged()
void verticalTilingChanged()
void magFilterChanged()
QQuick3DTextureData * textureData
void mipFilterChanged()
QSSGRenderImage * getRenderImage()
TilingMode verticalTiling() const
\qmlproperty enumeration QtQuick3D::Texture::tilingModeVertical
TilingMode horizontalTiling() const
\qmlproperty enumeration QtQuick3D::Texture::tilingModeHorizontal
const std::shared_ptr< QSSGRenderContextInterface > & rci() const
BlendFactor
Specifies the blend factor.
Definition qrhi.h:1280
const_iterator constEnd() const noexcept
Definition qset.h:143
iterator erase(const_iterator i)
Definition qset.h:145
const_iterator constFind(const T &value) const
Definition qset.h:161
iterator insert(const T &value)
Definition qset.h:155
\inmodule QtCore
Definition qurl.h:94
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1888
void append(const T &t)
\inmodule QtCore
Definition qvariant.h:64
T value() const &
Definition qvariant.h:511
QMetaType metaType() const
QSet< QString >::iterator it
\qmltype Shader \inherits Object \inqmlmodule QtQuick3D
QByteArray resolveShader(const QUrl &fileUrl, const QQmlContext *context, QByteArray &shaderPathKey)
Combined button and popup list for selecting options.
static void * context
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
Q_CORE_EXPORT int qstrcmp(const char *str1, const char *str2)
#define Q_UNLIKELY(x)
#define Q_LIKELY(x)
std::pair< T1, T2 > QPair
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
#define qWarning
Definition qlogging.h:162
#define Q_DECLARE_OPAQUE_POINTER(POINTER)
Definition qmetatype.h:1496
GLenum mode
GLint GLsizei width
GLenum type
GLenum GLuint texture
GLenum GLuint GLsizei propCount
GLuint name
GLuint GLuint * names
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:71
static QT_BEGIN_NAMESPACE QRhiGraphicsPipeline::BlendFactor toRhiBlendFactor(QQuick3DCustomMaterial::BlendMode mode)
\qmlproperty url CustomMaterial::vertexShader
static void setCustomMaterialFlagsFromShader(QSSGRenderCustomMaterial *material, const QSSGCustomShaderMetaData &meta)
QSSGRenderTextureCoordOp
QSSGRenderTextureFilterOp
#define emit
unsigned int quint32
Definition qtypes.h:45
const char property[13]
Definition qwizard.cpp:101
QUrl url("example.com")
[constructor-url-reference]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
obj metaObject() -> className()
myObject disconnect()
[26]
\inmodule QtCore
const char * className() const
Returns the class name.
const QMetaObject * superClass() const
Returns the meta-object of the superclass, or \nullptr if there is no such object.
int propertyOffset() const
Returns the property offset for this class; i.e.
CustomShaderPresence m_customShaderPresence
static ShaderCodeAndMetaData prepareCustomShader(QByteArray &dst, const QByteArray &shaderCode, QSSGShaderCache::ShaderType type, const StringPairList &baseUniforms, const StringPairList &baseInputs=StringPairList(), const StringPairList &baseOutputs=StringPairList())
Definition moc.h:24
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent