1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include <private/qquickgenericshadereffect_p.h>
41 #include <private/qquickwindow_p.h>
42 #include <private/qquickitem_p.h>
43 
44 QT_BEGIN_NAMESPACE
45 
46 namespace {
47 class IntSignalMapper : public QObject
48 {
49     Q_OBJECT
50 
51     int value;
52 public:
IntSignalMapper(int v)53     explicit IntSignalMapper(int v)
54         : QObject(nullptr), value(v) {}
55 
56 public Q_SLOTS:
map()57     void map() { emit mapped(value); }
58 
59 Q_SIGNALS:
60     void mapped(int);
61 };
62 } // unnamed namespace
63 
64 // The generic shader effect is used whenever on the RHI code path, or when the
65 // scenegraph backend indicates SupportsShaderEffectNode. This, unlike the
66 // monolithic and interconnected (e.g. with particles) OpenGL variant, passes
67 // most of the work to a scenegraph node created via the adaptation layer, thus
68 // allowing different implementation in the backends.
69 
QQuickGenericShaderEffect(QQuickShaderEffect * item,QObject * parent)70 QQuickGenericShaderEffect::QQuickGenericShaderEffect(QQuickShaderEffect *item, QObject *parent)
71     : QObject(parent)
72     , m_item(item)
73     , m_meshResolution(1, 1)
74     , m_mesh(nullptr)
75     , m_cullMode(QQuickShaderEffect::NoCulling)
76     , m_blending(true)
77     , m_supportsAtlasTextures(false)
78     , m_mgr(nullptr)
79     , m_fragNeedsUpdate(true)
80     , m_vertNeedsUpdate(true)
81 {
82     qRegisterMetaType<QSGGuiThreadShaderEffectManager::ShaderInfo::Type>("ShaderInfo::Type");
83     for (int i = 0; i < NShader; ++i)
84         m_inProgress[i] = nullptr;
85 }
86 
~QQuickGenericShaderEffect()87 QQuickGenericShaderEffect::~QQuickGenericShaderEffect()
88 {
89     for (int i = 0; i < NShader; ++i) {
90         disconnectSignals(Shader(i));
91         for (const auto &sm : qAsConst(m_signalMappers[i]))
92             delete sm.mapper;
93     }
94 
95     delete m_mgr;
96 }
97 
setFragmentShader(const QByteArray & src)98 void QQuickGenericShaderEffect::setFragmentShader(const QByteArray &src)
99 {
100     // Compare the actual values since they are often just filenames.
101     // Optimizing by comparing constData() is a bad idea since seemingly static
102     // strings in QML may in fact have different addresses when a binding
103     // triggers assigning the "same" value to the property.
104     if (m_fragShader == src)
105         return;
106 
107     m_fragShader = src;
108 
109     m_fragNeedsUpdate = true;
110     if (m_item->isComponentComplete())
111         maybeUpdateShaders();
112 
113     emit m_item->fragmentShaderChanged();
114 }
115 
setVertexShader(const QByteArray & src)116 void QQuickGenericShaderEffect::setVertexShader(const QByteArray &src)
117 {
118     if (m_vertShader == src)
119         return;
120 
121     m_vertShader = src;
122 
123     m_vertNeedsUpdate = true;
124     if (m_item->isComponentComplete())
125         maybeUpdateShaders();
126 
127     emit m_item->vertexShaderChanged();
128 }
129 
setBlending(bool enable)130 void QQuickGenericShaderEffect::setBlending(bool enable)
131 {
132     if (m_blending == enable)
133         return;
134 
135     m_blending = enable;
136     m_item->update();
137     emit m_item->blendingChanged();
138 }
139 
mesh() const140 QVariant QQuickGenericShaderEffect::mesh() const
141 {
142     return m_mesh ? QVariant::fromValue(static_cast<QObject *>(m_mesh))
143                   : QVariant::fromValue(m_meshResolution);
144 }
145 
setMesh(const QVariant & mesh)146 void QQuickGenericShaderEffect::setMesh(const QVariant &mesh)
147 {
148     QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qvariant_cast<QObject *>(mesh));
149     if (newMesh && newMesh == m_mesh)
150         return;
151 
152     if (m_mesh)
153         disconnect(m_mesh, SIGNAL(geometryChanged()), this, nullptr);
154 
155     m_mesh = newMesh;
156 
157     if (m_mesh) {
158         connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(markGeometryDirtyAndUpdate()));
159     } else {
160         if (mesh.canConvert<QSize>()) {
161             m_meshResolution = mesh.toSize();
162         } else {
163             QList<QByteArray> res = mesh.toByteArray().split('x');
164             bool ok = res.size() == 2;
165             if (ok) {
166                 int w = res.at(0).toInt(&ok);
167                 if (ok) {
168                     int h = res.at(1).toInt(&ok);
169                     if (ok)
170                         m_meshResolution = QSize(w, h);
171                 }
172             }
173             if (!ok)
174                 qWarning("ShaderEffect: mesh property must be a size or an object deriving from QQuickShaderEffectMesh");
175         }
176         m_defaultMesh.setResolution(m_meshResolution);
177     }
178 
179     m_dirty |= QSGShaderEffectNode::DirtyShaderMesh;
180     m_item->update();
181 
182     emit m_item->meshChanged();
183 }
184 
setCullMode(QQuickShaderEffect::CullMode face)185 void QQuickGenericShaderEffect::setCullMode(QQuickShaderEffect::CullMode face)
186 {
187     if (m_cullMode == face)
188         return;
189 
190     m_cullMode = face;
191     m_item->update();
192     emit m_item->cullModeChanged();
193 }
194 
setSupportsAtlasTextures(bool supports)195 void QQuickGenericShaderEffect::setSupportsAtlasTextures(bool supports)
196 {
197     if (m_supportsAtlasTextures == supports)
198         return;
199 
200     m_supportsAtlasTextures = supports;
201     markGeometryDirtyAndUpdate();
202     emit m_item->supportsAtlasTexturesChanged();
203 }
204 
parseLog()205 QString QQuickGenericShaderEffect::parseLog()
206 {
207     maybeUpdateShaders();
208     return log();
209 }
210 
log() const211 QString QQuickGenericShaderEffect::log() const
212 {
213     QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
214     if (!mgr)
215         return QString();
216 
217     return mgr->log();
218 }
219 
status() const220 QQuickShaderEffect::Status QQuickGenericShaderEffect::status() const
221 {
222     QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
223     if (!mgr)
224         return QQuickShaderEffect::Uncompiled;
225 
226     return QQuickShaderEffect::Status(mgr->status());
227 }
228 
handleEvent(QEvent * event)229 void QQuickGenericShaderEffect::handleEvent(QEvent *event)
230 {
231     if (event->type() == QEvent::DynamicPropertyChange) {
232         QDynamicPropertyChangeEvent *e = static_cast<QDynamicPropertyChangeEvent *>(event);
233         for (int shaderType = 0; shaderType < NShader; ++shaderType) {
234             const auto &vars(m_shaders[shaderType].shaderInfo.variables);
235             for (int idx = 0; idx < vars.count(); ++idx) {
236                 if (vars[idx].name == e->propertyName()) {
237                     propertyChanged((shaderType << 16) | idx);
238                     break;
239                 }
240             }
241         }
242     }
243 }
244 
handleGeometryChanged(const QRectF &,const QRectF &)245 void QQuickGenericShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &)
246 {
247     m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
248 }
249 
handleUpdatePaintNode(QSGNode * oldNode,QQuickItem::UpdatePaintNodeData *)250 QSGNode *QQuickGenericShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
251 {
252     QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode);
253 
254     if (m_item->width() <= 0 || m_item->height() <= 0) {
255         delete node;
256         return nullptr;
257     }
258 
259     // Do not change anything while a new shader is being reflected or compiled.
260     if (m_inProgress[Vertex] || m_inProgress[Fragment])
261         return node;
262 
263     // The manager should be already created on the gui thread. Just take that instance.
264     QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
265     if (!mgr) {
266         delete node;
267         return nullptr;
268     }
269 
270     if (!node) {
271         QSGRenderContext *rc = QQuickWindowPrivate::get(m_item->window())->context;
272         node = rc->sceneGraphContext()->createShaderEffectNode(rc, mgr);
273         if (!node) {
274             qWarning("No shader effect node");
275             return nullptr;
276         }
277         m_dirty = QSGShaderEffectNode::DirtyShaderAll;
278     }
279 
280     QSGShaderEffectNode::SyncData sd;
281     sd.dirty = m_dirty;
282     sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode);
283     sd.blending = m_blending;
284     sd.vertex.shader = &m_shaders[Vertex];
285     sd.vertex.dirtyConstants = &m_dirtyConstants[Vertex];
286     sd.vertex.dirtyTextures = &m_dirtyTextures[Vertex];
287     sd.fragment.shader = &m_shaders[Fragment];
288     sd.fragment.dirtyConstants = &m_dirtyConstants[Fragment];
289     sd.fragment.dirtyTextures = &m_dirtyTextures[Fragment];
290     node->syncMaterial(&sd);
291 
292     if (m_dirty & QSGShaderEffectNode::DirtyShaderMesh) {
293         node->setGeometry(nullptr);
294         m_dirty &= ~QSGShaderEffectNode::DirtyShaderMesh;
295         m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
296     }
297 
298     if (m_dirty & QSGShaderEffectNode::DirtyShaderGeometry) {
299         const QRectF rect(0, 0, m_item->width(), m_item->height());
300         QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
301         QSGGeometry *geometry = node->geometry();
302 
303         const QRectF srcRect = node->updateNormalizedTextureSubRect(m_supportsAtlasTextures);
304         geometry = mesh->updateGeometry(geometry, 2, 0, srcRect, rect);
305 
306         node->setFlag(QSGNode::OwnsGeometry, false);
307         node->setGeometry(geometry);
308         node->setFlag(QSGNode::OwnsGeometry, true);
309 
310         m_dirty &= ~QSGShaderEffectNode::DirtyShaderGeometry;
311     }
312 
313     m_dirty = {};
314     for (int i = 0; i < NShader; ++i) {
315         m_dirtyConstants[i].clear();
316         m_dirtyTextures[i].clear();
317     }
318 
319     return node;
320 }
321 
maybeUpdateShaders()322 void QQuickGenericShaderEffect::maybeUpdateShaders()
323 {
324     if (m_vertNeedsUpdate)
325         m_vertNeedsUpdate = !updateShader(Vertex, m_vertShader);
326     if (m_fragNeedsUpdate)
327         m_fragNeedsUpdate = !updateShader(Fragment, m_fragShader);
328     if (m_vertNeedsUpdate || m_fragNeedsUpdate) {
329         // This function is invoked either from componentComplete or in a
330         // response to a previous invocation's polish() request. If this is
331         // case #1 then updateShader can fail due to not having a window or
332         // scenegraph ready. Schedule the polish to try again later. In case #2
333         // the backend probably does not have shadereffect support so there is
334         // nothing to do for us here.
335         if (!m_item->window() || !m_item->window()->isSceneGraphInitialized())
336             m_item->polish();
337     }
338 }
339 
handleItemChange(QQuickItem::ItemChange change,const QQuickItem::ItemChangeData & value)340 void QQuickGenericShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
341 {
342     // Move the window ref.
343     if (change == QQuickItem::ItemSceneChange) {
344         for (int shaderType = 0; shaderType < NShader; ++shaderType) {
345             for (const auto &vd : qAsConst(m_shaders[shaderType].varData)) {
346                 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
347                     QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
348                     if (source) {
349                         if (value.window)
350                             QQuickItemPrivate::get(source)->refWindow(value.window);
351                         else
352                             QQuickItemPrivate::get(source)->derefWindow();
353                     }
354                 }
355             }
356         }
357     }
358 }
359 
shaderEffectManager() const360 QSGGuiThreadShaderEffectManager *QQuickGenericShaderEffect::shaderEffectManager() const
361 {
362     if (!m_mgr) {
363         // return null if this is not the gui thread and not already created
364         if (QThread::currentThread() != m_item->thread())
365             return m_mgr;
366         QQuickWindow *w = m_item->window();
367         if (w) { // note: just the window, don't care about isSceneGraphInitialized() here
368             m_mgr = QQuickWindowPrivate::get(w)->context->sceneGraphContext()->createGuiThreadShaderEffectManager();
369             if (m_mgr) {
370                 connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(logChanged()));
371                 connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(statusChanged()));
372                 connect(m_mgr, SIGNAL(textureChanged()), this, SLOT(markGeometryDirtyAndUpdateIfSupportsAtlas()));
373                 connect(m_mgr, &QSGGuiThreadShaderEffectManager::shaderCodePrepared, this, &QQuickGenericShaderEffect::shaderCodePrepared);
374             }
375         }
376     }
377 
378     return m_mgr;
379 }
380 
disconnectSignals(Shader shaderType)381 void QQuickGenericShaderEffect::disconnectSignals(Shader shaderType)
382 {
383     for (auto &sm : m_signalMappers[shaderType]) {
384         if (sm.active) {
385             sm.active = false;
386             QObject::disconnect(m_item, nullptr, sm.mapper, SLOT(map()));
387             QObject::disconnect(sm.mapper, SIGNAL(mapped(int)), this, SLOT(propertyChanged(int)));
388         }
389     }
390     for (const auto &vd : qAsConst(m_shaders[shaderType].varData)) {
391         if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
392             QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
393             if (source) {
394                 if (m_item->window())
395                     QQuickItemPrivate::get(source)->derefWindow();
396                 QObject::disconnect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*)));
397             }
398         }
399     }
400 }
401 
402 struct ShaderInfoCache
403 {
containsShaderInfoCache404     bool contains(const QByteArray &key) const
405     {
406         return m_shaderInfoCache.contains(key);
407     }
408 
valueShaderInfoCache409     QSGGuiThreadShaderEffectManager::ShaderInfo value(const QByteArray &key) const
410     {
411         return m_shaderInfoCache.value(key);
412     }
413 
insertShaderInfoCache414     void insert(const QByteArray &key, const QSGGuiThreadShaderEffectManager::ShaderInfo &value)
415     {
416         m_shaderInfoCache.insert(key, value);
417     }
418 
419     QHash<QByteArray, QSGGuiThreadShaderEffectManager::ShaderInfo> m_shaderInfoCache;
420 };
421 
Q_GLOBAL_STATIC(ShaderInfoCache,shaderInfoCache)422 Q_GLOBAL_STATIC(ShaderInfoCache, shaderInfoCache)
423 
424 bool QQuickGenericShaderEffect::updateShader(Shader shaderType, const QByteArray &src)
425 {
426     QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
427     if (!mgr)
428         return false;
429 
430     const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects();
431 
432     disconnectSignals(shaderType);
433 
434     m_shaders[shaderType].shaderInfo = QSGGuiThreadShaderEffectManager::ShaderInfo();
435     m_shaders[shaderType].varData.clear();
436 
437     if (!src.isEmpty()) {
438         if (shaderInfoCache()->contains(src)) {
439             m_shaders[shaderType].shaderInfo = shaderInfoCache()->value(src);
440             m_shaders[shaderType].hasShaderCode = true;
441         } else {
442             // Each prepareShaderCode call needs its own work area, hence the
443             // dynamic alloc. If there are calls in progress, let those run to
444             // finish, their results can then simply be ignored because
445             // m_inProgress indicates what we care about.
446             m_inProgress[shaderType] = new QSGGuiThreadShaderEffectManager::ShaderInfo;
447             const QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint =
448                     shaderType == Vertex ? QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex
449                                          : QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
450             // Figure out what input parameters and variables are used in the
451             // shader. For file-based shader source/bytecode this is where the data
452             // is pulled in from the file. Some backends may choose to do
453             // source->bytecode compilation as well in this step.
454             mgr->prepareShaderCode(typeHint, src, m_inProgress[shaderType]);
455             // the rest is handled in shaderCodePrepared()
456             return true;
457         }
458     } else {
459         m_shaders[shaderType].hasShaderCode = false;
460         if (shaderType == Fragment) {
461             // With built-in shaders hasShaderCode is set to false and all
462             // metadata is empty, as it is left up to the node to provide a
463             // built-in default shader and its metadata. However, in case of
464             // the built-in fragment shader the value for 'source' has to be
465             // provided and monitored like with an application-provided shader.
466             QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
467             v.name = QByteArrayLiteral("source");
468             v.bindPoint = 0; // fake
469             v.type = texturesSeparate ? QSGGuiThreadShaderEffectManager::ShaderInfo::Texture
470                                       : QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
471             m_shaders[shaderType].shaderInfo.variables.append(v);
472         }
473     }
474 
475     updateShaderVars(shaderType);
476     m_dirty |= QSGShaderEffectNode::DirtyShaders;
477     m_item->update();
478     return true;
479 }
480 
shaderCodePrepared(bool ok,QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint,const QByteArray & src,QSGGuiThreadShaderEffectManager::ShaderInfo * result)481 void QQuickGenericShaderEffect::shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint,
482                                                    const QByteArray &src, QSGGuiThreadShaderEffectManager::ShaderInfo *result)
483 {
484     const Shader shaderType = typeHint == QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? Vertex : Fragment;
485 
486     // If another call was made to updateShader() for the same shader type in
487     // the meantime then our results are useless, just drop them.
488     if (result != m_inProgress[shaderType]) {
489         delete result;
490         return;
491     }
492 
493     m_shaders[shaderType].shaderInfo = *result;
494     delete result;
495     m_inProgress[shaderType] = nullptr;
496 
497     if (!ok) {
498         qWarning("ShaderEffect: shader preparation failed for %s\n%s\n", src.constData(), qPrintable(log()));
499         m_shaders[shaderType].hasShaderCode = false;
500         return;
501     }
502 
503     m_shaders[shaderType].hasShaderCode = true;
504     shaderInfoCache()->insert(src, m_shaders[shaderType].shaderInfo);
505     updateShaderVars(shaderType);
506     m_dirty |= QSGShaderEffectNode::DirtyShaders;
507     m_item->update();
508 }
509 
updateShaderVars(Shader shaderType)510 void QQuickGenericShaderEffect::updateShaderVars(Shader shaderType)
511 {
512     QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
513     if (!mgr)
514         return;
515 
516     const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects();
517 
518     const int varCount = m_shaders[shaderType].shaderInfo.variables.count();
519     m_shaders[shaderType].varData.resize(varCount);
520 
521     // Reuse signal mappers as much as possible since the mapping is based on
522     // the index and shader type which are both constant.
523     if (m_signalMappers[shaderType].count() < varCount)
524         m_signalMappers[shaderType].resize(varCount);
525 
526     // Hook up the signals to get notified about changes for properties that
527     // correspond to variables in the shader. Store also the values.
528     for (int i = 0; i < varCount; ++i) {
529         const auto &v(m_shaders[shaderType].shaderInfo.variables.at(i));
530         QSGShaderEffectNode::VariableData &vd(m_shaders[shaderType].varData[i]);
531         const bool isSpecial = v.name.startsWith("qt_"); // special names not mapped to properties
532         if (isSpecial) {
533             if (v.name == "qt_Opacity")
534                 vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
535             else if (v.name == "qt_Matrix")
536                 vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
537             else if (v.name.startsWith("qt_SubRect_"))
538                 vd.specialType = QSGShaderEffectNode::VariableData::SubRect;
539             continue;
540         }
541 
542         // The value of a property corresponding to a sampler is the source
543         // item ref, unless there are separate texture objects in which case
544         // the sampler is ignored (here).
545         if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
546             if (texturesSeparate) {
547                 vd.specialType = QSGShaderEffectNode::VariableData::Unused;
548                 continue;
549             } else {
550                 vd.specialType = QSGShaderEffectNode::VariableData::Source;
551             }
552         } else if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) {
553             Q_ASSERT(texturesSeparate);
554             vd.specialType = QSGShaderEffectNode::VariableData::Source;
555         } else {
556             vd.specialType = QSGShaderEffectNode::VariableData::None;
557         }
558 
559         // Find the property on the ShaderEffect item.
560         const int propIdx = m_item->metaObject()->indexOfProperty(v.name.constData());
561         if (propIdx >= 0) {
562             QMetaProperty mp = m_item->metaObject()->property(propIdx);
563             if (!mp.hasNotifySignal())
564                 qWarning("ShaderEffect: property '%s' does not have notification method", v.name.constData());
565 
566             // Have a IntSignalMapper that emits mapped() with an index+type on each property change notify signal.
567             auto &sm(m_signalMappers[shaderType][i]);
568             if (!sm.mapper)
569                 sm.mapper = new IntSignalMapper(i | (shaderType << 16));
570             sm.active = true;
571             const QByteArray signalName = '2' + mp.notifySignal().methodSignature();
572             QObject::connect(m_item, signalName, sm.mapper, SLOT(map()));
573             QObject::connect(sm.mapper, SIGNAL(mapped(int)), this, SLOT(propertyChanged(int)));
574         } else {
575             // Do not warn for dynamic properties.
576             if (!m_item->property(v.name.constData()).isValid())
577                 qWarning("ShaderEffect: '%s' does not have a matching property", v.name.constData());
578         }
579 
580         vd.value = m_item->property(v.name.constData());
581 
582         if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
583             QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
584             if (source) {
585                 if (m_item->window())
586                     QQuickItemPrivate::get(source)->refWindow(m_item->window());
587                 QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*)));
588             }
589         }
590     }
591 }
592 
sourceIsUnique(QQuickItem * source,Shader typeToSkip,int indexToSkip) const593 bool QQuickGenericShaderEffect::sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const
594 {
595     for (int shaderType = 0; shaderType < NShader; ++shaderType) {
596         for (int idx = 0; idx < m_shaders[shaderType].varData.count(); ++idx) {
597             if (shaderType != typeToSkip || idx != indexToSkip) {
598                 const auto &vd(m_shaders[shaderType].varData[idx]);
599                 if (vd.specialType == QSGShaderEffectNode::VariableData::Source && qvariant_cast<QObject *>(vd.value) == source)
600                     return false;
601             }
602         }
603     }
604     return true;
605 }
606 
propertyChanged(int mappedId)607 void QQuickGenericShaderEffect::propertyChanged(int mappedId)
608 {
609     const Shader type = Shader(mappedId >> 16);
610     const int idx = mappedId & 0xFFFF;
611     const auto &v(m_shaders[type].shaderInfo.variables[idx]);
612     auto &vd(m_shaders[type].varData[idx]);
613 
614     if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
615         QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
616         if (source) {
617             if (m_item->window())
618                 QQuickItemPrivate::get(source)->derefWindow();
619             // QObject::disconnect() will disconnect all matching connections.
620             // If the same source has been attached to two separate
621             // textures/samplers, then changing one of them would trigger both
622             // to be disconnected. So check first.
623             if (sourceIsUnique(source, type, idx))
624                 QObject::disconnect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*)));
625         }
626 
627         vd.value = m_item->property(v.name.constData());
628 
629         source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
630         if (source) {
631             // 'source' needs a window to get a scene graph node. It usually gets one through its
632             // parent, but if the source item is "inline" rather than a reference -- i.e.
633             // "property variant source: Image { }" instead of "property variant source: foo" -- it
634             // will not get a parent. In those cases, 'source' should get the window from 'item'.
635             if (m_item->window())
636                 QQuickItemPrivate::get(source)->refWindow(m_item->window());
637             QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*)));
638         }
639 
640         m_dirty |= QSGShaderEffectNode::DirtyShaderTexture;
641         m_dirtyTextures[type].insert(idx);
642 
643      } else {
644         vd.value = m_item->property(v.name.constData());
645         m_dirty |= QSGShaderEffectNode::DirtyShaderConstant;
646         m_dirtyConstants[type].insert(idx);
647     }
648 
649     m_item->update();
650 }
651 
sourceDestroyed(QObject * object)652 void QQuickGenericShaderEffect::sourceDestroyed(QObject *object)
653 {
654     for (int shaderType = 0; shaderType < NShader; ++shaderType) {
655         for (auto &vd : m_shaders[shaderType].varData) {
656             if (vd.specialType == QSGShaderEffectNode::VariableData::Source && vd.value.canConvert<QObject *>()) {
657                 if (qvariant_cast<QObject *>(vd.value) == object)
658                     vd.value = QVariant();
659             }
660         }
661     }
662 }
663 
markGeometryDirtyAndUpdate()664 void QQuickGenericShaderEffect::markGeometryDirtyAndUpdate()
665 {
666     m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
667     m_item->update();
668 }
669 
markGeometryDirtyAndUpdateIfSupportsAtlas()670 void QQuickGenericShaderEffect::markGeometryDirtyAndUpdateIfSupportsAtlas()
671 {
672     if (m_supportsAtlasTextures)
673         markGeometryDirtyAndUpdate();
674 }
675 
676 QT_END_NAMESPACE
677 
678 #include "moc_qquickgenericshadereffect_p.cpp"
679 #include "qquickgenericshadereffect.moc"
680