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