1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Quick 3D.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "qquick3dcustommaterial_p.h"
31 #include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h>
32 #include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
33 #include <QtQuick/QQuickWindow>
34 
35 #include "qquick3dobject_p.h"
36 #include "qquick3dviewport_p.h"
37 #include "qquick3dscenemanager_p.h"
38 
39 Q_DECLARE_OPAQUE_POINTER(QQuick3DShaderUtilsTextureInput)
40 
41 QT_BEGIN_NAMESPACE
42 
43 /*!
44     \qmltype CustomMaterial
45     \inherits Material
46     \inqmlmodule QtQuick3D.Materials
47     \brief Base component for creating custom materials used to shade models.
48 
49     The custom material allows the user of QtQuick3D to access its material library and implement
50     own materials. There are two types of custom materials, which differ on how they are using the
51     material library. First one uses the custom material interface provided by the library to
52     implement materials similarly to many of the materials in the material library without
53     implementing it's own main function. This type of material must implement all the required
54     functions of the material. The second type implements it's own main function, but can still
55     use functionality from the material library. See \l {Qt Quick 3D Custom Material Reference}{reference}
56     on how to implement the material using the material interface.
57 
58     \qml
59     CustomMaterial {
60         // These properties names need to match the ones in the shader code!
61         property bool uEnvironmentMappingEnabled: false
62         property bool uShadowMappingEnabled: false
63         property real roughness: 0.0
64         property vector3d metal_color: Qt.vector3d(0.805, 0.395, 0.305)
65 
66         shaderInfo: ShaderInfo {
67             version: "330"
68             type: "GLSL"
69             shaderKey: ShaderInfo.Glossy
70         }
71 
72         property TextureInput uEnvironmentTexture: TextureInput {
73                 enabled: uEnvironmentMappingEnabled
74                 texture: Texture {
75                     id: envImage
76                     source: "maps/spherical_checker.png"
77                 }
78         }
79         property TextureInput uBakedShadowTexture: TextureInput {
80                 enabled: uShadowMappingEnabled
81                 texture: Texture {
82                     id: shadowImage
83                     source: "maps/shadow.png"
84                 }
85         }
86 
87         Shader {
88             id: copperFragShader
89             stage: Shader.Fragment
90             shader: "shaders/copper.frag"
91         }
92 
93         passes: [ Pass {
94                 shaders: copperFragShader
95             }
96         ]
97     }
98     \endqml
99 
100     The example here from CopperMaterial shows how the material is built. First, the shader
101     parameters are specified as properties. The names and types must match the names in the shader
102     code. Textures use TextureInput to assign \l{QtQuick3D::Texture}{texture} into the shader variable.
103     The shaderInfo property specifies more information about the shader and also configures some of
104     its features on or off when the custom material is built by QtQuick3D shader generator.
105     Then the material can use Shader type to specify shader source and shader stage. These are used
106     with \l {Pass}{passes} to create the resulting material. The passes can contain multiple
107     rendering passes and also other commands. Normally only the fragment shader needs to be passed
108     to a pass. The material library generates the vertex shader for the material. The material can
109     also create \l {Buffer}{buffers} to store intermediate rendering results. Here is an example
110     from GlassRefractiveMaterial:
111 
112     \qml
113     Buffer {
114         id: tempBuffer
115         name: "temp_buffer"
116         format: Buffer.Unknown
117         textureFilterOperation: Buffer.Linear
118         textureCoordOperation: Buffer.ClampToEdge
119         sizeMultiplier: 1.0
120         bufferFlags: Buffer.None // aka frame
121     }
122 
123     passes: [ Pass {
124             shaders: simpleGlassRefractiveFragShader
125             commands: [ BufferBlit {
126                     destination: tempBuffer
127                 }, BufferInput {
128                     buffer: tempBuffer
129                     param: "refractiveTexture"
130                 }, Blending {
131                     srcBlending: Blending.SrcAlpha
132                     destBlending: Blending.OneMinusSrcAlpha
133                 }
134             ]
135         }
136     ]
137     \endqml
138 
139     Multiple passes can also be specified to create advanced materials. Here is an example from
140     FrostedGlassMaterial.
141 
142     \qml
143     passes: [ Pass {
144             shaders: noopShader
145             output: dummyBuffer
146             commands: [ BufferBlit {
147                     destination: frameBuffer
148                 }
149             ]
150         }, Pass {
151             shaders: preBlurShader
152             output: tempBuffer
153             commands: [ BufferInput {
154                     buffer: frameBuffer
155                     param: "OriginBuffer"
156                 }
157             ]
158         }, Pass {
159             shaders: blurXShader
160             output: blurXBuffer
161             commands: [ BufferInput {
162                     buffer: tempBuffer
163                     param: "BlurBuffer"
164                 }
165             ]
166         }, Pass {
167             shaders: blurYShader
168             output: blurYBuffer
169             commands: [ BufferInput {
170                     buffer: blurXBuffer
171                     param: "BlurBuffer"
172                 }, BufferInput {
173                     buffer: tempBuffer
174                     param: "OriginBuffer"
175                 }
176             ]
177         }, Pass {
178             shaders: mainShader
179             commands: [BufferInput {
180                     buffer: blurYBuffer
181                     param: "refractiveTexture"
182                 }, Blending {
183                     srcBlending: Blending.SrcAlpha
184                     destBlending: Blending.OneMinusSrcAlpha
185                 }
186             ]
187         }
188     ]
189     \endqml
190 */
191 /*!
192     \qmlproperty bool CustomMaterial::hasTransparency
193     Specifies that the material has transparency.
194 */
195 /*!
196     \qmlproperty bool CustomMaterial::hasRefraction
197     Specifies that the material has refraction.
198 */
199 /*!
200     \qmlproperty bool CustomMaterial::alwaysDirty
201     Specifies that the material state is always dirty, which indicates that the material needs
202     to be refreshed every time it is used by the QtQuick3D.
203 */
204 /*!
205     \qmlproperty ShaderInfo CustomMaterial::shaderInfo
206     Specifies the ShaderInfo of the material.
207 */
208 /*!
209     \qmlproperty list CustomMaterial::passes
210     Contains a list of render \l {Pass}{passes} implemented by the material.
211 */
212 
213 template <QVariant::Type>
214 struct ShaderType
215 {
216 };
217 
218 template<>
219 struct ShaderType<QVariant::Double>
220 {
typeShaderType221     static constexpr QSSGRenderShaderDataType type() { return QSSGRenderShaderDataType::Float; }
nameShaderType222     static QByteArray name() { return QByteArrayLiteral("float"); }
223 };
224 
225 template<>
226 struct ShaderType<QVariant::Bool>
227 {
typeShaderType228     static constexpr QSSGRenderShaderDataType type() { return QSSGRenderShaderDataType::Boolean; }
nameShaderType229     static QByteArray name() { return QByteArrayLiteral("bool"); }
230 };
231 
232 template<>
233 struct ShaderType<QVariant::Int>
234 {
typeShaderType235     static constexpr QSSGRenderShaderDataType type() { return QSSGRenderShaderDataType::Integer; }
nameShaderType236     static QByteArray name() { return QByteArrayLiteral("int"); }
237 };
238 
239 template<>
240 struct ShaderType<QVariant::Vector2D>
241 {
typeShaderType242     static constexpr QSSGRenderShaderDataType type() { return QSSGRenderShaderDataType::Vec2; }
nameShaderType243     static QByteArray name() { return QByteArrayLiteral("vec2"); }
244 };
245 
246 template<>
247 struct ShaderType<QVariant::Vector3D>
248 {
typeShaderType249     static constexpr QSSGRenderShaderDataType type() { return QSSGRenderShaderDataType::Vec3; }
nameShaderType250     static QByteArray name() { return QByteArrayLiteral("vec3"); }
251 };
252 
253 template<>
254 struct ShaderType<QVariant::Vector4D>
255 {
typeShaderType256     static constexpr QSSGRenderShaderDataType type() { return QSSGRenderShaderDataType::Vec4; }
nameShaderType257     static QByteArray name() { return QByteArrayLiteral("vec4"); }
258 };
259 
260 template<>
261 struct ShaderType<QVariant::Color>
262 {
typeShaderType263     static constexpr QSSGRenderShaderDataType type() { return QSSGRenderShaderDataType::Rgba; }
nameShaderType264     static QByteArray name() { return QByteArrayLiteral("vec4"); }
265 };
266 
QQuick3DCustomMaterial(QQuick3DObject * parent)267 QQuick3DCustomMaterial::QQuick3DCustomMaterial(QQuick3DObject *parent)
268     : QQuick3DMaterial(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::CustomMaterial)), parent)
269 {
270 }
271 
~QQuick3DCustomMaterial()272 QQuick3DCustomMaterial::~QQuick3DCustomMaterial() {}
273 
hasTransparency() const274 bool QQuick3DCustomMaterial::hasTransparency() const
275 {
276     return m_hasTransparency;
277 }
278 
hasRefraction() const279 bool QQuick3DCustomMaterial::hasRefraction() const
280 {
281     return m_hasRefraction;
282 }
283 
shaderInfo() const284 QQuick3DShaderUtilsShaderInfo *QQuick3DCustomMaterial::shaderInfo() const
285 {
286     return m_shaderInfo;
287 }
288 
passes()289 QQmlListProperty<QQuick3DShaderUtilsRenderPass> QQuick3DCustomMaterial::passes()
290 {
291     return QQmlListProperty<QQuick3DShaderUtilsRenderPass>(this,
292                                                             nullptr,
293                                                             QQuick3DCustomMaterial::qmlAppendPass,
294                                                             QQuick3DCustomMaterial::qmlPassCount,
295                                                             QQuick3DCustomMaterial::qmlPassAt,
296                                                             QQuick3DCustomMaterial::qmlPassClear);
297 }
298 
markAllDirty()299 void QQuick3DCustomMaterial::markAllDirty()
300 {
301     m_dirtyAttributes = 0xffffffff;
302     QQuick3DMaterial::markAllDirty();
303 }
304 
alwaysDirty() const305 bool QQuick3DCustomMaterial::alwaysDirty() const
306 {
307     return m_alwaysDirty;
308 }
309 
setHasTransparency(bool hasTransparency)310 void QQuick3DCustomMaterial::setHasTransparency(bool hasTransparency)
311 {
312     if (m_hasTransparency == hasTransparency)
313         return;
314 
315     m_hasTransparency = hasTransparency;
316     emit hasTransparencyChanged(m_hasTransparency);
317 }
318 
setHasRefraction(bool hasRefraction)319 void QQuick3DCustomMaterial::setHasRefraction(bool hasRefraction)
320 {
321     if (m_hasRefraction == hasRefraction)
322         return;
323 
324     m_hasRefraction = hasRefraction;
325     emit hasRefractionChanged(m_hasRefraction);
326 }
327 
setShaderInfo(QQuick3DShaderUtilsShaderInfo * shaderInfo)328 void QQuick3DCustomMaterial::setShaderInfo(QQuick3DShaderUtilsShaderInfo *shaderInfo)
329 {
330     m_shaderInfo = shaderInfo;
331 }
332 
setAlwaysDirty(bool alwaysDirty)333 void QQuick3DCustomMaterial::setAlwaysDirty(bool alwaysDirty)
334 {
335     if (m_alwaysDirty == alwaysDirty)
336         return;
337 
338     m_alwaysDirty = alwaysDirty;
339     emit alwaysDirtyChanged(m_alwaysDirty);
340 }
341 
updateSpatialNode(QSSGRenderGraphObject * node)342 QSSGRenderGraphObject *QQuick3DCustomMaterial::updateSpatialNode(QSSGRenderGraphObject *node)
343 {
344     static const auto appendShaderUniform = [](const QByteArray &type, const QByteArray &name, QByteArray *shaderPrefix) {
345         shaderPrefix->append(QByteArrayLiteral("uniform ") + type + " " + name + ";\n");
346     };
347 
348     // Sanity check(s)
349     if (!m_shaderInfo || !m_shaderInfo->isValid()) {
350         qWarning("ShaderInfo is not valid!");
351         return node;
352     }
353 
354     QSSGRenderCustomMaterial *customMaterial = static_cast<QSSGRenderCustomMaterial *>(node);
355     if (!customMaterial) {
356         markAllDirty();
357         customMaterial = new QSSGRenderCustomMaterial;
358         customMaterial->m_shaderKeyValues = static_cast<QSSGRenderCustomMaterial::MaterialShaderKeyFlags>(m_shaderInfo->shaderKey);
359         customMaterial->className = metaObject()->className();
360         customMaterial->m_alwaysDirty = m_alwaysDirty;
361         customMaterial->m_hasTransparency = m_hasTransparency;
362         customMaterial->m_hasRefraction = m_hasRefraction;
363 
364         // Shader info
365         auto &shaderInfo = customMaterial->shaderInfo;
366         shaderInfo.type = m_shaderInfo->type;
367         shaderInfo.version = m_shaderInfo->version;
368         shaderInfo.shaderPrefix = QByteArrayLiteral("#include \"customMaterial.glsllib\"\n");
369 
370         QMetaMethod propertyDirtyMethod;
371         const int idx = metaObject()->indexOfSlot("onPropertyDirty()");
372         if (idx != -1)
373             propertyDirtyMethod = metaObject()->method(idx);
374 
375         // Properties
376         const int propCount = metaObject()->propertyCount();
377         int propOffset = metaObject()->propertyOffset();
378 
379         // Custom materials can have multilayered inheritance structure, so find the actual propOffset
380         const QMetaObject *superClass = metaObject()->superClass();
381         while (superClass && qstrcmp(superClass->className(), "QQuick3DCustomMaterial") != 0)  {
382             propOffset = superClass->propertyOffset();
383             superClass = superClass->superClass();
384         }
385 
386         QVector<QMetaProperty> userProperties;
387         for (int i = propOffset; i != propCount; ++i) {
388             const auto property = metaObject()->property(i);
389             if (Q_UNLIKELY(!property.isValid()))
390                 continue;
391 
392             // Track the property changes
393             if (property.hasNotifySignal() && propertyDirtyMethod.isValid())
394                 connect(this, property.notifySignal(), this, propertyDirtyMethod);
395 
396             QVariant::Type propType = property.type();
397             QVariant propValue = property.read(this);
398             if (static_cast<QMetaType::Type>(propType) == QMetaType::QVariant)
399                 propType = propValue.type();
400 
401             if (propType == QVariant::Double) {
402                 appendShaderUniform(ShaderType<QVariant::Double>::name(), property.name(), &shaderInfo.shaderPrefix);
403                 customMaterial->properties.push_back({ property.name(), propValue, ShaderType<QVariant::Double>::type(), i});
404             } else if (propType == QVariant::Bool) {
405                 appendShaderUniform(ShaderType<QVariant::Bool>::name(), property.name(), &shaderInfo.shaderPrefix);
406                 customMaterial->properties.push_back({ property.name(), propValue, ShaderType<QVariant::Bool>::type(), i});
407             } else if (propType == QVariant::Vector2D) {
408                 appendShaderUniform(ShaderType<QVariant::Vector2D>::name(), property.name(), &shaderInfo.shaderPrefix);
409                 customMaterial->properties.push_back({ property.name(), propValue, ShaderType<QVariant::Vector2D>::type(), i});
410             } else if (propType == QVariant::Vector3D) {
411                 appendShaderUniform(ShaderType<QVariant::Vector3D>::name(), property.name(), &shaderInfo.shaderPrefix);
412                 customMaterial->properties.push_back({ property.name(), propValue, ShaderType<QVariant::Vector3D>::type(), i});
413             } else if (propType == QVariant::Vector4D) {
414                 appendShaderUniform(ShaderType<QVariant::Vector4D>::name(), property.name(), &shaderInfo.shaderPrefix);
415                 customMaterial->properties.push_back({ property.name(), propValue, ShaderType<QVariant::Vector4D>::type(), i});
416             } else if (propType == QVariant::Int) {
417                 appendShaderUniform(ShaderType<QVariant::Int>::name(), property.name(), &shaderInfo.shaderPrefix);
418                 customMaterial->properties.push_back({ property.name(), propValue, ShaderType<QVariant::Int>::type(), i});
419             } else if (propType == QVariant::Color) {
420                 appendShaderUniform(ShaderType<QVariant::Color>::name(), property.name(), &shaderInfo.shaderPrefix);
421                 customMaterial->properties.push_back({ property.name(), propValue, ShaderType<QVariant::Color>::type(), i});
422             } else if (propType == QVariant::UserType) {
423                 if (property.userType() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>())
424                     userProperties.push_back(property);
425             } else if (static_cast<QMetaType::Type>(propType) == QMetaType::QObjectStar) {
426                 QObject *obj = qobject_cast<QQuick3DShaderUtilsTextureInput *>(propValue.value<QObject *>());
427                 if (obj)
428                     userProperties.push_back(property);
429             } else {
430                 qWarning("No know uniform convertion found for property %s. Skipping", property.name());
431             }
432         }
433 
434         // Textures
435         for (const auto &userProperty : qAsConst(userProperties)) {
436             QSSGRenderCustomMaterial::TextureProperty textureData;
437             QQuick3DShaderUtilsTextureInput *texture = userProperty.read(this).value<QQuick3DShaderUtilsTextureInput *>();
438             const QByteArray &name = userProperty.name();
439             if (name.isEmpty()) // Warnings here will just drown in the shader error messages
440                 continue;
441             texture->name = name;
442             QQuick3DTexture *tex = texture->texture(); //
443             connect(texture, &QQuick3DShaderUtilsTextureInput::textureDirty, this, &QQuick3DCustomMaterial::onTextureDirty);
444             textureData.name = name;
445             if (texture->enabled)
446                 textureData.texImage = tex->getRenderImage();
447             textureData.shaderDataType = QSSGRenderShaderDataType::Texture2D;
448             textureData.clampType = tex->horizontalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
449                                                                                      : (tex->horizontalTiling() == QQuick3DTexture::ClampToEdge) ? QSSGRenderTextureCoordOp::ClampToEdge
450                                                                                                                                                : QSSGRenderTextureCoordOp::MirroredRepeat;
451             QSSGShaderUtils::addSnapperSampler(textureData.name, shaderInfo.shaderPrefix);
452             customMaterial->textureProperties.push_back(textureData);
453         }
454 
455         QByteArray &shared = shaderInfo.shaderPrefix;
456         QByteArray vertex, geometry, fragment, shaderCode;
457         if (!m_passes.isEmpty()) {
458             for (const auto &pass : qAsConst(m_passes)) {
459                 QQuick3DShaderUtilsShader *sharedShader = pass->m_shaders.at(int(QQuick3DShaderUtilsShader::Stage::Shared));
460                 QQuick3DShaderUtilsShader *vertShader = pass->m_shaders.at(int(QQuick3DShaderUtilsShader::Stage::Vertex));
461                 QQuick3DShaderUtilsShader *fragShader = pass->m_shaders.at(int(QQuick3DShaderUtilsShader::Stage::Fragment));
462                 QQuick3DShaderUtilsShader *geomShader = pass->m_shaders.at(int(QQuick3DShaderUtilsShader::Stage::Geometry));
463                 if (!sharedShader && !vertShader && !fragShader && !geomShader) {
464                     qWarning("Pass with no shader attatched!");
465                     continue;
466                 }
467 
468                 // Build up shader code
469                 QByteArray shaderPath;
470                 if (sharedShader)
471                     shared += QSSGShaderUtils::resolveShader(sharedShader->shader, shaderPath, this);
472                 if (vertShader)
473                     vertex = QSSGShaderUtils::resolveShader(vertShader->shader, shaderPath, this);
474                 if (fragShader)
475                     fragment = QSSGShaderUtils::resolveShader(fragShader->shader, shaderPath, this);
476                 if (geomShader)
477                     geometry = QSSGShaderUtils::resolveShader(geomShader->shader, shaderPath, this);
478 
479                 shaderCode = QSSGShaderUtils::mergeShaderCode(shared, QByteArray(), QByteArray(), vertex, geometry, fragment);
480 
481                 // Bind shader
482                 customMaterial->commands.push_back(new dynamic::QSSGBindShader(shaderPath));
483                 customMaterial->commands.push_back(new dynamic::QSSGApplyInstanceValue());
484 
485                 // Buffers
486                 QQuick3DShaderUtilsBuffer *outputBuffer = pass->outputBuffer;
487                 if (outputBuffer) {
488                     const QByteArray &outBufferName = outputBuffer->name;
489                     Q_ASSERT(!outBufferName.isEmpty());
490                     // Allocate buffer command
491                     customMaterial->commands.push_back(outputBuffer->getCommand());
492                     // bind buffer
493                     customMaterial->commands.push_back(new dynamic::QSSGBindBuffer(outBufferName, true));
494                 } else {
495                     customMaterial->commands.push_back(new dynamic::QSSGBindTarget(QSSGRenderTextureFormat::RGBA8));
496                 }
497 
498                 // Other commands (BufferInput, Blending ... )
499                 const auto &extraCommands = pass->m_commands;
500                 for (const auto &command : extraCommands) {
501                     const int bufferCount = command->bufferCount();
502                     for (int i = 0; i != bufferCount; ++i)
503                         customMaterial->commands.push_back(command->bufferAt(i)->getCommand());
504                     customMaterial->commands.push_back(command->getCommand());
505                 }
506 
507                 // ... and finaly the render command
508                 customMaterial->commands.push_back(new dynamic::QSSGRender);
509 
510                 customMaterial->shaders.insert(shaderPath, shaderCode);
511             }
512         }
513     }
514 
515     QQuick3DMaterial::updateSpatialNode(customMaterial);
516 
517     if (m_dirtyAttributes & Dirty::PropertyDirty) {
518         for (const auto &prop : qAsConst(customMaterial->properties)) {
519             auto p = metaObject()->property(prop.pid);
520             if (Q_LIKELY(p.isValid()))
521                 prop.value = p.read(this);
522         }
523     }
524 
525     if (m_dirtyAttributes & Dirty::TextureDirty) {
526         // TODO:
527     }
528 
529     m_dirtyAttributes = 0;
530 
531     return customMaterial;
532 }
533 
onPropertyDirty()534 void QQuick3DCustomMaterial::onPropertyDirty()
535 {
536     markDirty(Dirty::PropertyDirty);
537     update();
538 }
539 
onTextureDirty(QQuick3DShaderUtilsTextureInput * texture)540 void QQuick3DCustomMaterial::onTextureDirty(QQuick3DShaderUtilsTextureInput *texture)
541 {
542     Q_UNUSED(texture)
543     markDirty(Dirty::TextureDirty);
544     update();
545 }
546 
qmlAppendPass(QQmlListProperty<QQuick3DShaderUtilsRenderPass> * list,QQuick3DShaderUtilsRenderPass * pass)547 void QQuick3DCustomMaterial::qmlAppendPass(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list, QQuick3DShaderUtilsRenderPass *pass)
548 {
549     if (!pass)
550         return;
551 
552     QQuick3DCustomMaterial *that = qobject_cast<QQuick3DCustomMaterial *>(list->object);
553     that->m_passes.push_back(pass);
554 }
555 
qmlPassAt(QQmlListProperty<QQuick3DShaderUtilsRenderPass> * list,int index)556 QQuick3DShaderUtilsRenderPass *QQuick3DCustomMaterial::qmlPassAt(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list, int index)
557 {
558     QQuick3DCustomMaterial *that = qobject_cast<QQuick3DCustomMaterial *>(list->object);
559     return that->m_passes.at(index);
560 }
561 
qmlPassCount(QQmlListProperty<QQuick3DShaderUtilsRenderPass> * list)562 int QQuick3DCustomMaterial::qmlPassCount(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list)
563 {
564     QQuick3DCustomMaterial *that = qobject_cast<QQuick3DCustomMaterial *>(list->object);
565     return that->m_passes.count();
566 }
567 
qmlPassClear(QQmlListProperty<QQuick3DShaderUtilsRenderPass> * list)568 void QQuick3DCustomMaterial::qmlPassClear(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list)
569 {
570     QQuick3DCustomMaterial *that = qobject_cast<QQuick3DCustomMaterial *>(list->object);
571     that->m_passes.clear();
572 }
573 
574 
575 QT_END_NAMESPACE
576