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/qquickopenglshadereffect_p.h>
41 
42 #include <QtQuick/qsgmaterial.h>
43 #include <QtQuick/private/qsgshadersourcebuilder_p.h>
44 #include "qquickitem_p.h"
45 
46 #include <QtQuick/private/qsgcontext_p.h>
47 #include <QtQuick/qsgtextureprovider.h>
48 #include "qquickwindow.h"
49 
50 #include "qquickimage_p.h"
51 #include "qquickshadereffectsource_p.h"
52 #include "qquickshadereffectmesh_p.h"
53 
54 #include <QtQml/qqmlfile.h>
55 #include <QtCore/qsignalmapper.h>
56 #include <QtCore/qfileselector.h>
57 
58 QT_BEGIN_NAMESPACE
59 
60 // Note: this legacy ShaderEffect implementation is used only when running
61 // directly with OpenGL. This is going to go away in the future (Qt 6?), since
62 // the RHI path uses QQuickGenericShaderEffect always.
63 
64 namespace {
65 
66     enum VariableQualifier {
67         AttributeQualifier,
68         UniformQualifier
69     };
70 
qt_isalpha(char c)71     inline bool qt_isalpha(char c)
72     {
73         char ch = c | 0x20;
74         return (ch >= 'a' && ch <= 'z') || c == '_';
75     }
76 
qt_isalnum(char c)77     inline bool qt_isalnum(char c)
78     {
79         return qt_isalpha(c) || (c >= '0' && c <= '9');
80     }
81 
qt_isspace(char c)82     inline bool qt_isspace(char c)
83     {
84         return c == ' ' || (c >= 0x09 && c <= 0x0d);
85     }
86 
87     // Returns -1 if not found, returns index to first character after the name if found.
qt_search_for_variable(const char * s,int length,int index,VariableQualifier & decl,int & typeIndex,int & typeLength,int & nameIndex,int & nameLength,QQuickOpenGLShaderEffectCommon::Key::ShaderType shaderType)88     int qt_search_for_variable(const char *s, int length, int index, VariableQualifier &decl,
89                                int &typeIndex, int &typeLength,
90                                int &nameIndex, int &nameLength,
91                                QQuickOpenGLShaderEffectCommon::Key::ShaderType shaderType)
92     {
93         enum Identifier {
94             QualifierIdentifier, // Base state
95             PrecisionIdentifier,
96             TypeIdentifier,
97             NameIdentifier
98         };
99         Identifier expected = QualifierIdentifier;
100         bool compilerDirectiveExpected = index == 0;
101 
102         while (index < length) {
103             // Skip whitespace.
104             while (qt_isspace(s[index])) {
105                 compilerDirectiveExpected |= s[index] == '\n';
106                 ++index;
107             }
108 
109             if (qt_isalpha(s[index])) {
110                 // Read identifier.
111                 int idIndex = index;
112                 ++index;
113                 while (qt_isalnum(s[index]))
114                     ++index;
115                 int idLength = index - idIndex;
116 
117                 const int attrLen = sizeof("attribute") - 1;
118                 const int inLen = sizeof("in") - 1;
119                 const int uniLen = sizeof("uniform") - 1;
120                 const int loLen = sizeof("lowp") - 1;
121                 const int medLen = sizeof("mediump") - 1;
122                 const int hiLen = sizeof("highp") - 1;
123 
124                 switch (expected) {
125                 case QualifierIdentifier:
126                     if (idLength == attrLen && qstrncmp("attribute", s + idIndex, attrLen) == 0) {
127                         decl = AttributeQualifier;
128                         expected = PrecisionIdentifier;
129                     } else if (shaderType == QQuickOpenGLShaderEffectCommon::Key::VertexShader
130                                && idLength == inLen && qstrncmp("in", s + idIndex, inLen) == 0) {
131                         decl = AttributeQualifier;
132                         expected = PrecisionIdentifier;
133                     } else if (idLength == uniLen && qstrncmp("uniform", s + idIndex, uniLen) == 0) {
134                         decl = UniformQualifier;
135                         expected = PrecisionIdentifier;
136                     }
137                     break;
138                 case PrecisionIdentifier:
139                     if ((idLength == loLen && qstrncmp("lowp", s + idIndex, loLen) == 0)
140                             || (idLength == medLen && qstrncmp("mediump", s + idIndex, medLen) == 0)
141                             || (idLength == hiLen && qstrncmp("highp", s + idIndex, hiLen) == 0))
142                     {
143                         expected = TypeIdentifier;
144                         break;
145                     }
146                     Q_FALLTHROUGH();
147                 case TypeIdentifier:
148                     typeIndex = idIndex;
149                     typeLength = idLength;
150                     expected = NameIdentifier;
151                     break;
152                 case NameIdentifier:
153                     nameIndex = idIndex;
154                     nameLength = idLength;
155                     return index; // Attribute or uniform declaration found. Return result.
156                 default:
157                     break;
158                 }
159             } else if (s[index] == '#' && compilerDirectiveExpected) {
160                 // Skip compiler directives.
161                 ++index;
162                 while (index < length && (s[index] != '\n' || s[index - 1] == '\\'))
163                     ++index;
164             } else if (s[index] == '/' && s[index + 1] == '/') {
165                 // Skip comments.
166                 index += 2;
167                 while (index < length && s[index] != '\n')
168                     ++index;
169             } else if (s[index] == '/' && s[index + 1] == '*') {
170                 // Skip comments.
171                 index += 2;
172                 while (index < length && (s[index] != '*' || s[index + 1] != '/'))
173                     ++index;
174                 if (index < length)
175                     index += 2; // Skip star-slash.
176             } else {
177                 expected = QualifierIdentifier;
178                 ++index;
179             }
180             compilerDirectiveExpected = false;
181         }
182         return -1;
183     }
184 }
185 
186 namespace QtPrivate {
187 class MappedSlotObject: public QtPrivate::QSlotObjectBase
188 {
189 public:
190     typedef std::function<void()> PropChangedFunc;
191 
MappedSlotObject(PropChangedFunc f)192     explicit MappedSlotObject(PropChangedFunc f)
193         : QSlotObjectBase(&impl), _signalIndex(-1), func(std::move(f))
194     { ref(); }
195 
setSignalIndex(int idx)196     void setSignalIndex(int idx) { _signalIndex = idx; }
signalIndex() const197     int signalIndex() const { return _signalIndex; }
198 
199 private:
200     int _signalIndex;
201     PropChangedFunc func;
202 
impl(int which,QSlotObjectBase * this_,QObject *,void ** a,bool * ret)203     static void impl(int which, QSlotObjectBase *this_, QObject *, void **a, bool *ret)
204     {
205         auto thiz = static_cast<MappedSlotObject*>(this_);
206         switch (which) {
207         case Destroy:
208             delete thiz;
209             break;
210         case Call:
211             thiz->func();
212             break;
213         case Compare:
214             *ret = thiz == reinterpret_cast<MappedSlotObject *>(a[0]);
215             break;
216         case NumOperations: ;
217         }
218     }
219 };
220 }
221 
~QQuickOpenGLShaderEffectCommon()222 QQuickOpenGLShaderEffectCommon::~QQuickOpenGLShaderEffectCommon()
223 {
224     for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType)
225         clearSignalMappers(shaderType);
226 }
227 
disconnectPropertySignals(QQuickItem * item,Key::ShaderType shaderType)228 void QQuickOpenGLShaderEffectCommon::disconnectPropertySignals(QQuickItem *item, Key::ShaderType shaderType)
229 {
230     for (int i = 0; i < uniformData[shaderType].size(); ++i) {
231         if (signalMappers[shaderType].at(i) == 0)
232             continue;
233         const UniformData &d = uniformData[shaderType].at(i);
234         auto mapper = signalMappers[shaderType].at(i);
235         void *a = mapper;
236         QObjectPrivate::disconnect(item, mapper->signalIndex(), &a);
237         if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
238             QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
239             if (source) {
240                 if (item->window())
241                     QQuickItemPrivate::get(source)->derefWindow();
242                 QObject::disconnect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
243             }
244         }
245     }
246 }
247 
connectPropertySignals(QQuickItem * item,const QMetaObject * itemMetaObject,Key::ShaderType shaderType)248 void QQuickOpenGLShaderEffectCommon::connectPropertySignals(QQuickItem *item,
249                                                             const QMetaObject *itemMetaObject,
250                                                             Key::ShaderType shaderType)
251 {
252     auto engine = qmlEngine(item);
253     if (!engine)
254         return;
255 
256     QQmlPropertyCache *propCache = QQmlData::ensurePropertyCache(engine, item);
257     for (int i = 0; i < uniformData[shaderType].size(); ++i) {
258         if (signalMappers[shaderType].at(i) == 0)
259             continue;
260         const UniformData &d = uniformData[shaderType].at(i);
261         QQmlPropertyData *pd = propCache->property(QString::fromUtf8(d.name), nullptr, nullptr);
262         if (pd && !pd->isFunction()) {
263             if (pd->notifyIndex() == -1) {
264                 qWarning("QQuickOpenGLShaderEffect: property '%s' does not have notification method!", d.name.constData());
265             } else {
266                 auto *mapper = signalMappers[shaderType].at(i);
267                 mapper->setSignalIndex(itemMetaObject->property(d.propertyIndex).notifySignal().methodIndex());
268                 Q_ASSERT(item->metaObject() == itemMetaObject);
269                 bool ok = QObjectPrivate::connectImpl(item, pd->notifyIndex(), item, nullptr, mapper,
270                                                       Qt::AutoConnection, nullptr, itemMetaObject);
271                 if (!ok)
272                     qWarning() << "Failed to connect to property" << itemMetaObject->property(d.propertyIndex).name()
273                                << "(" << d.propertyIndex << ", signal index" << pd->notifyIndex()
274                                << ") of item" << item;
275             }
276         } else {
277             // If the source is set via a dynamic property, like the layer is, then we need this
278             // check to disable the warning.
279             if (!item->property(d.name).isValid())
280                 qWarning("QQuickOpenGLShaderEffect: '%s' does not have a matching property!", d.name.constData());
281         }
282 
283         if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
284             QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
285             if (source) {
286                 if (item->window())
287                     QQuickItemPrivate::get(source)->refWindow(item->window());
288                 QObject::connect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
289             }
290         }
291     }
292 }
293 
updateParseLog(bool ignoreAttributes)294 void QQuickOpenGLShaderEffectCommon::updateParseLog(bool ignoreAttributes)
295 {
296     parseLog.clear();
297     if (!ignoreAttributes) {
298         if (!attributes.contains(qtPositionAttributeName())) {
299             parseLog += QLatin1String("Warning: Missing reference to \'")
300                         + QLatin1String(qtPositionAttributeName())
301                         + QLatin1String("\'.\n");
302         }
303         if (!attributes.contains(qtTexCoordAttributeName())) {
304             parseLog += QLatin1String("Warning: Missing reference to \'")
305                         + QLatin1String(qtTexCoordAttributeName())
306                         + QLatin1String("\'.\n");
307         }
308     }
309     bool respectsMatrix = false;
310     bool respectsOpacity = false;
311     for (int i = 0; i < uniformData[Key::VertexShader].size(); ++i)
312         respectsMatrix |= uniformData[Key::VertexShader].at(i).specialType == UniformData::Matrix;
313     for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
314         for (int i = 0; i < uniformData[shaderType].size(); ++i)
315             respectsOpacity |= uniformData[shaderType].at(i).specialType == UniformData::Opacity;
316     }
317     if (!respectsMatrix)
318         parseLog += QLatin1String("Warning: Vertex shader is missing reference to \'qt_Matrix\'.\n");
319     if (!respectsOpacity)
320         parseLog += QLatin1String("Warning: Shaders are missing reference to \'qt_Opacity\'.\n");
321 }
322 
lookThroughShaderCode(QQuickItem * item,const QMetaObject * itemMetaObject,Key::ShaderType shaderType,const QByteArray & code)323 void QQuickOpenGLShaderEffectCommon::lookThroughShaderCode(QQuickItem *item,
324                                                            const QMetaObject *itemMetaObject,
325                                                            Key::ShaderType shaderType,
326                                                            const QByteArray &code)
327 {
328     auto engine = qmlEngine(item);
329     QQmlPropertyCache *propCache = (engine) ? QQmlData::ensurePropertyCache(engine, item) : nullptr;
330     int index = 0;
331     int typeIndex = -1;
332     int typeLength = 0;
333     int nameIndex = -1;
334     int nameLength = 0;
335     const char *s = code.constData();
336     VariableQualifier decl = AttributeQualifier;
337     while ((index = qt_search_for_variable(s, code.size(), index, decl, typeIndex, typeLength,
338                                            nameIndex, nameLength, shaderType)) != -1)
339     {
340         if (decl == AttributeQualifier) {
341             if (shaderType == Key::VertexShader)
342                 attributes.append(QByteArray(s + nameIndex, nameLength));
343         } else {
344             Q_ASSERT(decl == UniformQualifier);
345 
346             const int sampLen = sizeof("sampler2D") - 1;
347             const int sampExtLen = sizeof("samplerExternalOES") - 1;
348             const int opLen = sizeof("qt_Opacity") - 1;
349             const int matLen = sizeof("qt_Matrix") - 1;
350             const int srLen = sizeof("qt_SubRect_") - 1;
351 
352             UniformData d;
353             QtPrivate::MappedSlotObject *mapper = nullptr;
354             d.name = QByteArray(s + nameIndex, nameLength);
355             if (nameLength == opLen && qstrncmp("qt_Opacity", s + nameIndex, opLen) == 0) {
356                 d.specialType = UniformData::Opacity;
357             } else if (nameLength == matLen && qstrncmp("qt_Matrix", s + nameIndex, matLen) == 0) {
358                 d.specialType = UniformData::Matrix;
359             } else if (nameLength > srLen && qstrncmp("qt_SubRect_", s + nameIndex, srLen) == 0) {
360                 d.specialType = UniformData::SubRect;
361             } else {
362                 if (propCache) {
363                     if (QQmlPropertyData *pd = propCache->property(QString::fromUtf8(d.name), nullptr, nullptr)) {
364                         if (!pd->isFunction())
365                             d.propertyIndex = pd->coreIndex();
366                     }
367                 }
368                 const int mappedId = uniformData[shaderType].size() | (shaderType << 16);
369                 mapper = new QtPrivate::MappedSlotObject([this, mappedId](){
370                     this->mappedPropertyChanged(mappedId);
371                 });
372                 if (typeLength == sampLen && qstrncmp("sampler2D", s + typeIndex, sampLen) == 0)
373                     d.specialType = UniformData::Sampler;
374                 else if (typeLength == sampExtLen && qstrncmp("samplerExternalOES", s + typeIndex, sampExtLen) == 0)
375                     d.specialType = UniformData::SamplerExternal;
376                 else
377                     d.specialType = UniformData::None;
378                 d.setValueFromProperty(item, itemMetaObject);
379             }
380             uniformData[shaderType].append(d);
381             signalMappers[shaderType].append(mapper);
382         }
383     }
384 }
385 
updateShader(QQuickItem * item,const QMetaObject * itemMetaObject,Key::ShaderType shaderType)386 void QQuickOpenGLShaderEffectCommon::updateShader(QQuickItem *item,
387                                                   const QMetaObject *itemMetaObject,
388                                                   Key::ShaderType shaderType)
389 {
390     disconnectPropertySignals(item, shaderType);
391     uniformData[shaderType].clear();
392     clearSignalMappers(shaderType);
393     if (shaderType == Key::VertexShader)
394         attributes.clear();
395 
396     // A qrc or file URL means the shader source is to be read from the specified file.
397     QUrl srcUrl(QString::fromUtf8(source.sourceCode[shaderType]));
398     if (!srcUrl.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || srcUrl.isLocalFile()) {
399         if (!fileSelector) {
400             fileSelector = new QFileSelector(item);
401             // There may not be an OpenGL context accessible here. So rely on
402             // the window's requestedFormat().
403             if (item->window()
404                     && item->window()->requestedFormat().profile() == QSurfaceFormat::CoreProfile) {
405                 fileSelector->setExtraSelectors(QStringList() << QStringLiteral("glslcore"));
406             }
407         }
408         const QString fn = fileSelector->select(QQmlFile::urlToLocalFileOrQrc(srcUrl));
409         QFile f(fn);
410         if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
411             source.sourceCode[shaderType] = f.readAll();
412             f.close();
413         } else {
414             qWarning("ShaderEffect: Failed to read %s", qPrintable(fn));
415             source.sourceCode[shaderType] = QByteArray();
416         }
417     }
418 
419     const QByteArray &code = source.sourceCode[shaderType];
420     if (code.isEmpty()) {
421         // Optimize for default code.
422         if (shaderType == Key::VertexShader) {
423             attributes.append(QByteArray(qtPositionAttributeName()));
424             attributes.append(QByteArray(qtTexCoordAttributeName()));
425             UniformData d;
426             d.name = "qt_Matrix";
427             d.specialType = UniformData::Matrix;
428             uniformData[Key::VertexShader].append(d);
429             signalMappers[Key::VertexShader].append(0);
430         } else if (shaderType == Key::FragmentShader) {
431             UniformData d;
432             d.name = "qt_Opacity";
433             d.specialType = UniformData::Opacity;
434             uniformData[Key::FragmentShader].append(d);
435             signalMappers[Key::FragmentShader].append(0);
436             auto mapper = new QtPrivate::MappedSlotObject([this](){
437                 mappedPropertyChanged(1 | (Key::FragmentShader << 16));
438             });
439             const char *sourceName = "source";
440             d.name = sourceName;
441             d.setValueFromProperty(item, itemMetaObject);
442             d.specialType = UniformData::Sampler;
443             uniformData[Key::FragmentShader].append(d);
444             signalMappers[Key::FragmentShader].append(mapper);
445         }
446     } else {
447         lookThroughShaderCode(item, itemMetaObject, shaderType, code);
448     }
449 
450     connectPropertySignals(item, itemMetaObject, shaderType);
451 }
452 
updateMaterial(QQuickOpenGLShaderEffectNode * node,QQuickOpenGLShaderEffectMaterial * material,bool updateUniforms,bool updateUniformValues,bool updateTextureProviders)453 void QQuickOpenGLShaderEffectCommon::updateMaterial(QQuickOpenGLShaderEffectNode *node,
454                                               QQuickOpenGLShaderEffectMaterial *material,
455                                               bool updateUniforms, bool updateUniformValues,
456                                               bool updateTextureProviders)
457 {
458     if (updateUniforms) {
459         for (int i = 0; i < material->textureProviders.size(); ++i) {
460             QSGTextureProvider *t = material->textureProviders.at(i);
461             if (t) {
462                 QObject::disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
463                 QObject::disconnect(t, SIGNAL(destroyed(QObject*)), node, SLOT(textureProviderDestroyed(QObject*)));
464             }
465         }
466 
467         // First make room in the textureProviders array. Set to proper value further down.
468         int textureProviderCount = 0;
469         for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
470             for (int i = 0; i < uniformData[shaderType].size(); ++i) {
471                 if (uniformData[shaderType].at(i).specialType == UniformData::Sampler ||
472                         uniformData[shaderType].at(i).specialType == UniformData::SamplerExternal)
473                     ++textureProviderCount;
474             }
475             material->uniforms[shaderType] = uniformData[shaderType];
476         }
477         material->textureProviders.fill(0, textureProviderCount);
478         updateUniformValues = false;
479         updateTextureProviders = true;
480     }
481 
482     if (updateUniformValues) {
483         for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
484             Q_ASSERT(uniformData[shaderType].size() == material->uniforms[shaderType].size());
485             for (int i = 0; i < uniformData[shaderType].size(); ++i)
486                 material->uniforms[shaderType][i].value = uniformData[shaderType].at(i).value;
487         }
488     }
489 
490     if (updateTextureProviders) {
491         int index = 0;
492         for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
493             for (int i = 0; i < uniformData[shaderType].size(); ++i) {
494                 const UniformData &d = uniformData[shaderType].at(i);
495                 if (d.specialType != UniformData::Sampler && d.specialType != UniformData::SamplerExternal)
496                     continue;
497                 QSGTextureProvider *oldProvider = material->textureProviders.at(index);
498                 QSGTextureProvider *newProvider = nullptr;
499                 QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
500                 if (source && source->isTextureProvider())
501                     newProvider = source->textureProvider();
502                 if (newProvider != oldProvider) {
503                     if (oldProvider) {
504                         QObject::disconnect(oldProvider, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
505                         QObject::disconnect(oldProvider, SIGNAL(destroyed(QObject*)), node, SLOT(textureProviderDestroyed(QObject*)));
506                     }
507                     if (newProvider) {
508                         Q_ASSERT_X(newProvider->thread() == QThread::currentThread(),
509                                    "QQuickOpenGLShaderEffect::updatePaintNode",
510                                    "Texture provider must belong to the rendering thread");
511                         QObject::connect(newProvider, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
512                         QObject::connect(newProvider, SIGNAL(destroyed(QObject*)), node, SLOT(textureProviderDestroyed(QObject*)));
513                     } else {
514                         const char *typeName = source ? source->metaObject()->className() : d.value.typeName();
515                         qWarning("ShaderEffect: Property '%s' is not assigned a valid texture provider (%s).",
516                                  d.name.constData(), typeName);
517                     }
518                     material->textureProviders[index] = newProvider;
519                 }
520                 ++index;
521             }
522         }
523         Q_ASSERT(index == material->textureProviders.size());
524     }
525 }
526 
updateWindow(QQuickWindow * window)527 void QQuickOpenGLShaderEffectCommon::updateWindow(QQuickWindow *window)
528 {
529     // See comment in QQuickOpenGLShaderEffectCommon::propertyChanged().
530     if (window) {
531         for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
532             for (int i = 0; i < uniformData[shaderType].size(); ++i) {
533                 const UniformData &d = uniformData[shaderType].at(i);
534                 if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
535                     QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
536                     if (source)
537                         QQuickItemPrivate::get(source)->refWindow(window);
538                 }
539             }
540         }
541     } else {
542         for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
543             for (int i = 0; i < uniformData[shaderType].size(); ++i) {
544                 const UniformData &d = uniformData[shaderType].at(i);
545                 if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
546                     QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
547                     if (source)
548                         QQuickItemPrivate::get(source)->derefWindow();
549                 }
550             }
551         }
552     }
553 }
554 
sourceDestroyed(QObject * object)555 void QQuickOpenGLShaderEffectCommon::sourceDestroyed(QObject *object)
556 {
557     for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
558         for (int i = 0; i < uniformData[shaderType].size(); ++i) {
559             UniformData &d = uniformData[shaderType][i];
560             if ((d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) && d.value.canConvert<QObject *>()) {
561                 if (qvariant_cast<QObject *>(d.value) == object)
562                     d.value = QVariant();
563             }
564         }
565     }
566 }
567 
qquick_uniqueInUniformData(QQuickItem * source,const QVector<QQuickOpenGLShaderEffectMaterial::UniformData> * uniformData,int typeToSkip,int indexToSkip)568 static bool qquick_uniqueInUniformData(QQuickItem *source, const QVector<QQuickOpenGLShaderEffectMaterial::UniformData> *uniformData, int typeToSkip, int indexToSkip)
569 {
570     for (int s=0; s<QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++s) {
571         for (int i=0; i<uniformData[s].size(); ++i) {
572             if (s == typeToSkip && i == indexToSkip)
573                 continue;
574             const QQuickOpenGLShaderEffectMaterial::UniformData &d = uniformData[s][i];
575             if ((d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::Sampler || d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::SamplerExternal) && qvariant_cast<QObject *>(d.value) == source)
576                 return false;
577         }
578     }
579     return true;
580 }
581 
propertyChanged(QQuickItem * item,const QMetaObject * itemMetaObject,int mappedId,bool * textureProviderChanged)582 void QQuickOpenGLShaderEffectCommon::propertyChanged(QQuickItem *item,
583                                                      const QMetaObject *itemMetaObject,
584                                                      int mappedId, bool *textureProviderChanged)
585 {
586     Key::ShaderType shaderType = Key::ShaderType(mappedId >> 16);
587     int index = mappedId & 0xffff;
588     UniformData &d = uniformData[shaderType][index];
589     if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
590         QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
591         if (source) {
592             if (item->window())
593                 QQuickItemPrivate::get(source)->derefWindow();
594 
595             // QObject::disconnect() will disconnect all matching connections. If the same
596             // source has been attached to two separate samplers, then changing one of them
597             // would trigger both to be disconnected. Without the connection we'll end up
598             // with a dangling pointer in the uniformData.
599             if (qquick_uniqueInUniformData(source, uniformData, shaderType, index))
600                 QObject::disconnect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
601         }
602 
603         d.setValueFromProperty(item, itemMetaObject);
604 
605         source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
606         if (source) {
607             // 'source' needs a window to get a scene graph node. It usually gets one through its
608             // parent, but if the source item is "inline" rather than a reference -- i.e.
609             // "property variant source: Image { }" instead of "property variant source: foo" -- it
610             // will not get a parent. In those cases, 'source' should get the window from 'item'.
611             if (item->window())
612                 QQuickItemPrivate::get(source)->refWindow(item->window());
613             QObject::connect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
614         }
615         if (textureProviderChanged)
616             *textureProviderChanged = true;
617     } else {
618         d.setValueFromProperty(item, itemMetaObject);
619         if (textureProviderChanged)
620             *textureProviderChanged = false;
621     }
622 }
623 
clearSignalMappers(int shader)624 void QQuickOpenGLShaderEffectCommon::clearSignalMappers(int shader)
625 {
626     for (auto mapper : qAsConst(signalMappers[shader])) {
627         if (mapper)
628             mapper->destroyIfLastRef();
629     }
630     signalMappers[shader].clear();
631 }
632 
QQuickOpenGLShaderEffect(QQuickShaderEffect * item,QObject * parent)633 QQuickOpenGLShaderEffect::QQuickOpenGLShaderEffect(QQuickShaderEffect *item, QObject *parent)
634     : QObject(parent)
635     , m_item(item)
636     , m_itemMetaObject(nullptr)
637     , m_meshResolution(1, 1)
638     , m_mesh(nullptr)
639     , m_cullMode(QQuickShaderEffect::NoCulling)
640     , m_status(QQuickShaderEffect::Uncompiled)
641     , m_common(this, [this](int mappedId){this->propertyChanged(mappedId);})
642     , m_blending(true)
643     , m_dirtyUniforms(true)
644     , m_dirtyUniformValues(true)
645     , m_dirtyTextureProviders(true)
646     , m_dirtyProgram(true)
647     , m_dirtyParseLog(true)
648     , m_dirtyMesh(true)
649     , m_dirtyGeometry(true)
650     , m_customVertexShader(false)
651     , m_supportsAtlasTextures(false)
652     , m_vertNeedsUpdate(true)
653     , m_fragNeedsUpdate(true)
654 {
655 }
656 
~QQuickOpenGLShaderEffect()657 QQuickOpenGLShaderEffect::~QQuickOpenGLShaderEffect()
658 {
659     for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType)
660         m_common.disconnectPropertySignals(m_item, Key::ShaderType(shaderType));
661 }
662 
setFragmentShader(const QByteArray & code)663 void QQuickOpenGLShaderEffect::setFragmentShader(const QByteArray &code)
664 {
665     if (m_common.source.sourceCode[Key::FragmentShader].constData() == code.constData())
666         return;
667     m_common.source.sourceCode[Key::FragmentShader] = code;
668     m_dirtyProgram = true;
669     m_dirtyParseLog = true;
670 
671     m_fragNeedsUpdate = true;
672     if (m_item->isComponentComplete())
673         maybeUpdateShaders();
674 
675     m_item->update();
676     if (m_status != QQuickShaderEffect::Uncompiled) {
677         m_status = QQuickShaderEffect::Uncompiled;
678         emit m_item->statusChanged();
679     }
680     emit m_item->fragmentShaderChanged();
681 }
682 
setVertexShader(const QByteArray & code)683 void QQuickOpenGLShaderEffect::setVertexShader(const QByteArray &code)
684 {
685     if (m_common.source.sourceCode[Key::VertexShader].constData() == code.constData())
686         return;
687     m_common.source.sourceCode[Key::VertexShader] = code;
688     m_dirtyProgram = true;
689     m_dirtyParseLog = true;
690     m_customVertexShader = true;
691 
692     m_vertNeedsUpdate = true;
693     if (m_item->isComponentComplete())
694         maybeUpdateShaders();
695 
696     m_item->update();
697     if (m_status != QQuickShaderEffect::Uncompiled) {
698         m_status = QQuickShaderEffect::Uncompiled;
699         emit m_item->statusChanged();
700     }
701     emit m_item->vertexShaderChanged();
702 }
703 
setBlending(bool enable)704 void QQuickOpenGLShaderEffect::setBlending(bool enable)
705 {
706     if (blending() == enable)
707         return;
708 
709     m_blending = enable;
710     m_item->update();
711 
712     emit m_item->blendingChanged();
713 }
714 
mesh() const715 QVariant QQuickOpenGLShaderEffect::mesh() const
716 {
717     return m_mesh ? QVariant::fromValue(static_cast<QObject *>(m_mesh))
718                   : QVariant::fromValue(m_meshResolution);
719 }
720 
setMesh(const QVariant & mesh)721 void QQuickOpenGLShaderEffect::setMesh(const QVariant &mesh)
722 {
723     QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qvariant_cast<QObject *>(mesh));
724     if (newMesh && newMesh == m_mesh)
725         return;
726     if (m_mesh)
727         disconnect(m_mesh, SIGNAL(geometryChanged()), this, nullptr);
728     m_mesh = newMesh;
729     if (m_mesh) {
730         connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry()));
731     } else {
732         if (mesh.canConvert<QSize>()) {
733             m_meshResolution = mesh.toSize();
734         } else {
735             QList<QByteArray> res = mesh.toByteArray().split('x');
736             bool ok = res.size() == 2;
737             if (ok) {
738                 int w = res.at(0).toInt(&ok);
739                 if (ok) {
740                     int h = res.at(1).toInt(&ok);
741                     if (ok)
742                         m_meshResolution = QSize(w, h);
743                 }
744             }
745             if (!ok)
746                 qWarning("ShaderEffect: mesh property must be size or object deriving from QQuickShaderEffectMesh.");
747         }
748         m_defaultMesh.setResolution(m_meshResolution);
749     }
750 
751     m_dirtyMesh = true;
752     m_dirtyParseLog = true;
753     m_item->update();
754     emit m_item->meshChanged();
755 }
756 
setCullMode(QQuickShaderEffect::CullMode face)757 void QQuickOpenGLShaderEffect::setCullMode(QQuickShaderEffect::CullMode face)
758 {
759     if (face == m_cullMode)
760         return;
761     m_cullMode = face;
762     m_item->update();
763     emit m_item->cullModeChanged();
764 }
765 
setSupportsAtlasTextures(bool supports)766 void QQuickOpenGLShaderEffect::setSupportsAtlasTextures(bool supports)
767 {
768     if (supports == m_supportsAtlasTextures)
769         return;
770     m_supportsAtlasTextures = supports;
771     updateGeometry();
772     emit m_item->supportsAtlasTexturesChanged();
773 }
774 
parseLog()775 QString QQuickOpenGLShaderEffect::parseLog()
776 {
777     maybeUpdateShaders(true);
778 
779     if (m_dirtyParseLog) {
780         m_common.updateParseLog(m_mesh != nullptr);
781         m_dirtyParseLog = false;
782     }
783     return m_common.parseLog;
784 }
785 
handleEvent(QEvent * event)786 void QQuickOpenGLShaderEffect::handleEvent(QEvent *event)
787 {
788     if (event->type() == QEvent::DynamicPropertyChange) {
789         QDynamicPropertyChangeEvent *e = static_cast<QDynamicPropertyChangeEvent *>(event);
790         for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
791             for (int i = 0; i < m_common.uniformData[shaderType].size(); ++i) {
792                 if (m_common.uniformData[shaderType].at(i).name == e->propertyName()) {
793                     bool textureProviderChanged;
794                     m_common.propertyChanged(m_item, m_itemMetaObject,
795                                              (shaderType << 16) | i, &textureProviderChanged);
796                     m_dirtyTextureProviders |= textureProviderChanged;
797                     m_dirtyUniformValues = true;
798                     m_item->update();
799                 }
800             }
801         }
802     }
803 }
804 
updateGeometry()805 void QQuickOpenGLShaderEffect::updateGeometry()
806 {
807     m_dirtyGeometry = true;
808     m_item->update();
809 }
810 
updateGeometryIfAtlased()811 void QQuickOpenGLShaderEffect::updateGeometryIfAtlased()
812 {
813     if (m_supportsAtlasTextures)
814         updateGeometry();
815 }
816 
updateLogAndStatus(const QString & log,int status)817 void QQuickOpenGLShaderEffect::updateLogAndStatus(const QString &log, int status)
818 {
819     m_log = parseLog() + log;
820     m_status = QQuickShaderEffect::Status(status);
821     emit m_item->logChanged();
822     emit m_item->statusChanged();
823 }
824 
sourceDestroyed(QObject * object)825 void QQuickOpenGLShaderEffect::sourceDestroyed(QObject *object)
826 {
827     m_common.sourceDestroyed(object);
828 }
829 
propertyChanged(int mappedId)830 void QQuickOpenGLShaderEffect::propertyChanged(int mappedId)
831 {
832     bool textureProviderChanged;
833     m_common.propertyChanged(m_item, m_itemMetaObject, mappedId, &textureProviderChanged);
834     m_dirtyTextureProviders |= textureProviderChanged;
835     m_dirtyUniformValues = true;
836     m_item->update();
837 }
838 
handleGeometryChanged(const QRectF &,const QRectF &)839 void QQuickOpenGLShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &)
840 {
841     m_dirtyGeometry = true;
842 }
843 
handleUpdatePaintNode(QSGNode * oldNode,QQuickItem::UpdatePaintNodeData *)844 QSGNode *QQuickOpenGLShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
845 {
846     QQuickOpenGLShaderEffectNode *node = static_cast<QQuickOpenGLShaderEffectNode *>(oldNode);
847 
848     // In the case of zero-size or a bad vertex shader, don't try to create a node...
849     if (m_common.attributes.isEmpty() || m_item->width() <= 0 || m_item->height() <= 0) {
850         if (node)
851             delete node;
852         return nullptr;
853     }
854 
855     if (!node) {
856         node = new QQuickOpenGLShaderEffectNode;
857         node->setMaterial(new QQuickOpenGLShaderEffectMaterial(node));
858         node->setFlag(QSGNode::OwnsMaterial, true);
859         m_dirtyProgram = true;
860         m_dirtyUniforms = true;
861         m_dirtyGeometry = true;
862         connect(node, SIGNAL(logAndStatusChanged(QString,int)), this, SLOT(updateLogAndStatus(QString,int)));
863         connect(node, &QQuickOpenGLShaderEffectNode::dirtyTexture,
864                 this, &QQuickOpenGLShaderEffect::updateGeometryIfAtlased);
865     }
866 
867     QQuickOpenGLShaderEffectMaterial *material = static_cast<QQuickOpenGLShaderEffectMaterial *>(node->material());
868 
869     // Update blending
870     if (bool(material->flags() & QSGMaterial::Blending) != m_blending) {
871         material->setFlag(QSGMaterial::Blending, m_blending);
872         node->markDirty(QSGNode::DirtyMaterial);
873     }
874 
875     if (int(material->cullMode) != int(m_cullMode)) {
876         material->cullMode = QQuickShaderEffect::CullMode(m_cullMode);
877         node->markDirty(QSGNode::DirtyMaterial);
878     }
879 
880     if (m_dirtyProgram) {
881         Key s = m_common.source;
882         QSGShaderSourceBuilder builder;
883         if (s.sourceCode[Key::FragmentShader].isEmpty()) {
884             builder.appendSourceFile(QStringLiteral(":/qt-project.org/items/shaders/shadereffect.frag"));
885             s.sourceCode[Key::FragmentShader] = builder.source();
886             builder.clear();
887         }
888         if (s.sourceCode[Key::VertexShader].isEmpty()) {
889             builder.appendSourceFile(QStringLiteral(":/qt-project.org/items/shaders/shadereffect.vert"));
890             s.sourceCode[Key::VertexShader] = builder.source();
891         }
892 
893         material->setProgramSource(s);
894         material->attributes = m_common.attributes;
895         node->markDirty(QSGNode::DirtyMaterial);
896         m_dirtyProgram = false;
897         m_dirtyUniforms = true;
898     }
899 
900     if (m_dirtyUniforms || m_dirtyUniformValues || m_dirtyTextureProviders) {
901         m_common.updateMaterial(node, material, m_dirtyUniforms, m_dirtyUniformValues,
902                                 m_dirtyTextureProviders);
903         node->markDirty(QSGNode::DirtyMaterial);
904         m_dirtyUniforms = m_dirtyUniformValues = m_dirtyTextureProviders = false;
905     }
906 
907     QRectF srcRect(0, 0, 1, 1);
908     bool geometryUsesTextureSubRect = false;
909     if (m_supportsAtlasTextures && material->textureProviders.size() == 1) {
910         QSGTextureProvider *provider = material->textureProviders.at(0);
911         if (provider && provider->texture()) {
912             srcRect = provider->texture()->normalizedTextureSubRect();
913             geometryUsesTextureSubRect = true;
914         }
915     }
916 
917     if (bool(material->flags() & QSGMaterial::RequiresFullMatrix) != m_customVertexShader) {
918         material->setFlag(QSGMaterial::RequiresFullMatrix, m_customVertexShader);
919         node->markDirty(QSGNode::DirtyMaterial);
920     }
921 
922     if (material->geometryUsesTextureSubRect != geometryUsesTextureSubRect) {
923         material->geometryUsesTextureSubRect = geometryUsesTextureSubRect;
924         node->markDirty(QSGNode::DirtyMaterial);
925     }
926 
927     if (m_dirtyMesh) {
928         node->setGeometry(nullptr);
929         m_dirtyMesh = false;
930         m_dirtyGeometry = true;
931     }
932 
933     if (m_dirtyGeometry) {
934         node->setFlag(QSGNode::OwnsGeometry, false);
935         QSGGeometry *geometry = node->geometry();
936         QRectF rect(0, 0, m_item->width(), m_item->height());
937         QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
938 
939         int posIndex = 0;
940         if (!mesh->validateAttributes(m_common.attributes, &posIndex)) {
941             QString log = mesh->log();
942             if (!log.isNull()) {
943                 m_log = parseLog() + QLatin1String("*** Mesh ***\n") + log;
944                 m_status = QQuickShaderEffect::Error;
945                 emit m_item->logChanged();
946                 emit m_item->statusChanged();
947             }
948             delete node;
949             return nullptr;
950         }
951 
952         geometry = mesh->updateGeometry(geometry, m_common.attributes.count(), posIndex, srcRect, rect);
953 
954         node->setGeometry(geometry);
955         node->setFlag(QSGNode::OwnsGeometry, true);
956 
957         m_dirtyGeometry = false;
958     }
959 
960     return node;
961 }
962 
maybeUpdateShaders(bool force)963 void QQuickOpenGLShaderEffect::maybeUpdateShaders(bool force)
964 {
965     if (!m_itemMetaObject)
966         m_itemMetaObject = m_item->metaObject();
967 
968     // Defer processing if a window is not yet associated with the item. This
969     // is because the actual scenegraph backend is not known so conditions
970     // based on GraphicsInfo.shaderType and similar evaluate to wrong results.
971     if (!m_item->window() && !force) {
972         m_item->polish();
973         return;
974     }
975 
976     if (m_vertNeedsUpdate) {
977         m_vertNeedsUpdate = false;
978         m_common.updateShader(m_item, m_itemMetaObject, Key::VertexShader);
979     }
980 
981     if (m_fragNeedsUpdate) {
982         m_fragNeedsUpdate = false;
983         m_common.updateShader(m_item, m_itemMetaObject, Key::FragmentShader);
984     }
985 }
986 
handleItemChange(QQuickItem::ItemChange change,const QQuickItem::ItemChangeData & value)987 void QQuickOpenGLShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
988 {
989     if (change == QQuickItem::ItemSceneChange)
990         m_common.updateWindow(value.window);
991 }
992 
993 QT_END_NAMESPACE
994 
995 #include "moc_qquickopenglshadereffect_p.cpp"
996