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