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
58QT_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
64namespace {
65
66 enum VariableQualifier {
67 AttributeQualifier,
68 UniformQualifier
69 };
70
71 inline bool qt_isalpha(char c)
72 {
73 char ch = c | 0x20;
74 return (ch >= 'a' && ch <= 'z') || c == '_';
75 }
76
77 inline bool qt_isalnum(char c)
78 {
79 return qt_isalpha(c) || (c >= '0' && c <= '9');
80 }
81
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.
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(c: s[index])) {
105 compilerDirectiveExpected |= s[index] == '\n';
106 ++index;
107 }
108
109 if (qt_isalpha(c: s[index])) {
110 // Read identifier.
111 int idIndex = index;
112 ++index;
113 while (qt_isalnum(c: 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(str1: "attribute", str2: s + idIndex, len: attrLen) == 0) {
127 decl = AttributeQualifier;
128 expected = PrecisionIdentifier;
129 } else if (shaderType == QQuickOpenGLShaderEffectCommon::Key::VertexShader
130 && idLength == inLen && qstrncmp(str1: "in", str2: s + idIndex, len: inLen) == 0) {
131 decl = AttributeQualifier;
132 expected = PrecisionIdentifier;
133 } else if (idLength == uniLen && qstrncmp(str1: "uniform", str2: s + idIndex, len: uniLen) == 0) {
134 decl = UniformQualifier;
135 expected = PrecisionIdentifier;
136 }
137 break;
138 case PrecisionIdentifier:
139 if ((idLength == loLen && qstrncmp(str1: "lowp", str2: s + idIndex, len: loLen) == 0)
140 || (idLength == medLen && qstrncmp(str1: "mediump", str2: s + idIndex, len: medLen) == 0)
141 || (idLength == hiLen && qstrncmp(str1: "highp", str2: s + idIndex, len: 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
186namespace QtPrivate {
187class MappedSlotObject: public QtPrivate::QSlotObjectBase
188{
189public:
190 typedef std::function<void()> PropChangedFunc;
191
192 explicit MappedSlotObject(PropChangedFunc f)
193 : QSlotObjectBase(&impl), _signalIndex(-1), func(std::move(f))
194 { ref(); }
195
196 void setSignalIndex(int idx) { _signalIndex = idx; }
197 int signalIndex() const { return _signalIndex; }
198
199private:
200 int _signalIndex;
201 PropChangedFunc func;
202
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
222QQuickOpenGLShaderEffectCommon::~QQuickOpenGLShaderEffectCommon()
223{
224 for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType)
225 clearSignalMappers(shader: shaderType);
226}
227
228void 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(sender: item, signal_index: mapper->signalIndex(), slot: &a);
237 if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
238 QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value));
239 if (source) {
240 if (item->window())
241 QQuickItemPrivate::get(item: source)->derefWindow();
242 QObject::disconnect(sender: source, SIGNAL(destroyed(QObject*)), receiver: host, SLOT(sourceDestroyed(QObject*)));
243 }
244 }
245 }
246}
247
248void 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, object: 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(key: QString::fromUtf8(str: d.name), object: nullptr, context: nullptr);
262 if (pd && !pd->isFunction()) {
263 if (pd->notifyIndex() == -1) {
264 qWarning(msg: "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(index: d.propertyIndex).notifySignal().methodIndex());
268 Q_ASSERT(item->metaObject() == itemMetaObject);
269 bool ok = QObjectPrivate::connectImpl(sender: item, signal_index: pd->notifyIndex(), receiver: item, slot: nullptr, slotObj: mapper,
270 type: Qt::AutoConnection, types: nullptr, senderMetaObject: itemMetaObject);
271 if (!ok)
272 qWarning() << "Failed to connect to property" << itemMetaObject->property(index: 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(name: d.name).isValid())
280 qWarning(msg: "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 *>(object: qvariant_cast<QObject *>(v: d.value));
285 if (source) {
286 if (item->window())
287 QQuickItemPrivate::get(item: source)->refWindow(item->window());
288 QObject::connect(sender: source, SIGNAL(destroyed(QObject*)), receiver: host, SLOT(sourceDestroyed(QObject*)));
289 }
290 }
291 }
292}
293
294void QQuickOpenGLShaderEffectCommon::updateParseLog(bool ignoreAttributes)
295{
296 parseLog.clear();
297 if (!ignoreAttributes) {
298 if (!attributes.contains(t: qtPositionAttributeName())) {
299 parseLog += QLatin1String("Warning: Missing reference to \'")
300 + QLatin1String(qtPositionAttributeName())
301 + QLatin1String("\'.\n");
302 }
303 if (!attributes.contains(t: 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
323void 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, object: 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, length: code.size(), index, decl, typeIndex, typeLength,
338 nameIndex, nameLength, shaderType)) != -1)
339 {
340 if (decl == AttributeQualifier) {
341 if (shaderType == Key::VertexShader)
342 attributes.append(t: 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(str1: "qt_Opacity", str2: s + nameIndex, len: opLen) == 0) {
356 d.specialType = UniformData::Opacity;
357 } else if (nameLength == matLen && qstrncmp(str1: "qt_Matrix", str2: s + nameIndex, len: matLen) == 0) {
358 d.specialType = UniformData::Matrix;
359 } else if (nameLength > srLen && qstrncmp(str1: "qt_SubRect_", str2: s + nameIndex, len: srLen) == 0) {
360 d.specialType = UniformData::SubRect;
361 } else {
362 if (propCache) {
363 if (QQmlPropertyData *pd = propCache->property(key: QString::fromUtf8(str: d.name), object: nullptr, context: 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(str1: "sampler2D", str2: s + typeIndex, len: sampLen) == 0)
373 d.specialType = UniformData::Sampler;
374 else if (typeLength == sampExtLen && qstrncmp(str1: "samplerExternalOES", str2: s + typeIndex, len: sampExtLen) == 0)
375 d.specialType = UniformData::SamplerExternal;
376 else
377 d.specialType = UniformData::None;
378 d.setValueFromProperty(item, itemMetaObject);
379 }
380 uniformData[shaderType].append(t: d);
381 signalMappers[shaderType].append(t: mapper);
382 }
383 }
384}
385
386void QQuickOpenGLShaderEffectCommon::updateShader(QQuickItem *item,
387 const QMetaObject *itemMetaObject,
388 Key::ShaderType shaderType)
389{
390 disconnectPropertySignals(item, shaderType);
391 uniformData[shaderType].clear();
392 clearSignalMappers(shader: 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(str: source.sourceCode[shaderType]));
398 if (!srcUrl.scheme().compare(other: QLatin1String("qrc"), cs: 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(filePath: QQmlFile::urlToLocalFileOrQrc(srcUrl));
409 QFile f(fn);
410 if (f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
411 source.sourceCode[shaderType] = f.readAll();
412 f.close();
413 } else {
414 qWarning(msg: "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(t: QByteArray(qtPositionAttributeName()));
424 attributes.append(t: QByteArray(qtTexCoordAttributeName()));
425 UniformData d;
426 d.name = "qt_Matrix";
427 d.specialType = UniformData::Matrix;
428 uniformData[Key::VertexShader].append(t: d);
429 signalMappers[Key::VertexShader].append(t: 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(t: d);
435 signalMappers[Key::FragmentShader].append(t: 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(t: d);
444 signalMappers[Key::FragmentShader].append(t: mapper);
445 }
446 } else {
447 lookThroughShaderCode(item, itemMetaObject, shaderType, code);
448 }
449
450 connectPropertySignals(item, itemMetaObject, shaderType);
451}
452
453void 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(sender: t, SIGNAL(textureChanged()), receiver: node, SLOT(markDirtyTexture()));
463 QObject::disconnect(sender: t, SIGNAL(destroyed(QObject*)), receiver: 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(t: 0, size: 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(i: index);
498 QSGTextureProvider *newProvider = nullptr;
499 QQuickItem *source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: d.value));
500 if (source && source->isTextureProvider())
501 newProvider = source->textureProvider();
502 if (newProvider != oldProvider) {
503 if (oldProvider) {
504 QObject::disconnect(sender: oldProvider, SIGNAL(textureChanged()), receiver: node, SLOT(markDirtyTexture()));
505 QObject::disconnect(sender: oldProvider, SIGNAL(destroyed(QObject*)), receiver: 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(sender: newProvider, SIGNAL(textureChanged()), receiver: node, SLOT(markDirtyTexture()));
512 QObject::connect(sender: newProvider, SIGNAL(destroyed(QObject*)), receiver: node, SLOT(textureProviderDestroyed(QObject*)));
513 } else {
514 const char *typeName = source ? source->metaObject()->className() : d.value.typeName();
515 qWarning(msg: "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
527void 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 *>(object: qvariant_cast<QObject *>(v: d.value));
536 if (source)
537 QQuickItemPrivate::get(item: 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 *>(object: qvariant_cast<QObject *>(v: d.value));
547 if (source)
548 QQuickItemPrivate::get(item: source)->derefWindow();
549 }
550 }
551 }
552 }
553}
554
555void 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 *>(v: d.value) == object)
562 d.value = QVariant();
563 }
564 }
565 }
566}
567
568static 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 *>(v: d.value) == source)
576 return false;
577 }
578 }
579 return true;
580}
581
582void 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 *>(object: qvariant_cast<QObject *>(v: d.value));
591 if (source) {
592 if (item->window())
593 QQuickItemPrivate::get(item: 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, typeToSkip: shaderType, indexToSkip: index))
600 QObject::disconnect(sender: source, SIGNAL(destroyed(QObject*)), receiver: host, SLOT(sourceDestroyed(QObject*)));
601 }
602
603 d.setValueFromProperty(item, itemMetaObject);
604
605 source = qobject_cast<QQuickItem *>(object: qvariant_cast<QObject *>(v: 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(item: source)->refWindow(item->window());
613 QObject::connect(sender: source, SIGNAL(destroyed(QObject*)), receiver: 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
624void QQuickOpenGLShaderEffectCommon::clearSignalMappers(int shader)
625{
626 for (auto mapper : qAsConst(t&: signalMappers[shader])) {
627 if (mapper)
628 mapper->destroyIfLastRef();
629 }
630 signalMappers[shader].clear();
631}
632
633QQuickOpenGLShaderEffect::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
657QQuickOpenGLShaderEffect::~QQuickOpenGLShaderEffect()
658{
659 for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType)
660 m_common.disconnectPropertySignals(item: m_item, shaderType: Key::ShaderType(shaderType));
661}
662
663void 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
683void 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
704void 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
715QVariant QQuickOpenGLShaderEffect::mesh() const
716{
717 return m_mesh ? QVariant::fromValue(value: static_cast<QObject *>(m_mesh))
718 : QVariant::fromValue(value: m_meshResolution);
719}
720
721void QQuickOpenGLShaderEffect::setMesh(const QVariant &mesh)
722{
723 QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(object: qvariant_cast<QObject *>(v: mesh));
724 if (newMesh && newMesh == m_mesh)
725 return;
726 if (m_mesh)
727 disconnect(sender: m_mesh, SIGNAL(geometryChanged()), receiver: this, member: nullptr);
728 m_mesh = newMesh;
729 if (m_mesh) {
730 connect(sender: m_mesh, SIGNAL(geometryChanged()), receiver: this, SLOT(updateGeometry()));
731 } else {
732 if (mesh.canConvert<QSize>()) {
733 m_meshResolution = mesh.toSize();
734 } else {
735 QList<QByteArray> res = mesh.toByteArray().split(sep: 'x');
736 bool ok = res.size() == 2;
737 if (ok) {
738 int w = res.at(i: 0).toInt(ok: &ok);
739 if (ok) {
740 int h = res.at(i: 1).toInt(ok: &ok);
741 if (ok)
742 m_meshResolution = QSize(w, h);
743 }
744 }
745 if (!ok)
746 qWarning(msg: "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
757void 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
766void 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
775QString QQuickOpenGLShaderEffect::parseLog()
776{
777 maybeUpdateShaders(force: true);
778
779 if (m_dirtyParseLog) {
780 m_common.updateParseLog(ignoreAttributes: m_mesh != nullptr);
781 m_dirtyParseLog = false;
782 }
783 return m_common.parseLog;
784}
785
786void 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(item: m_item, itemMetaObject: m_itemMetaObject,
795 mappedId: (shaderType << 16) | i, textureProviderChanged: &textureProviderChanged);
796 m_dirtyTextureProviders |= textureProviderChanged;
797 m_dirtyUniformValues = true;
798 m_item->update();
799 }
800 }
801 }
802 }
803}
804
805void QQuickOpenGLShaderEffect::updateGeometry()
806{
807 m_dirtyGeometry = true;
808 m_item->update();
809}
810
811void QQuickOpenGLShaderEffect::updateGeometryIfAtlased()
812{
813 if (m_supportsAtlasTextures)
814 updateGeometry();
815}
816
817void 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
825void QQuickOpenGLShaderEffect::sourceDestroyed(QObject *object)
826{
827 m_common.sourceDestroyed(object);
828}
829
830void QQuickOpenGLShaderEffect::propertyChanged(int mappedId)
831{
832 bool textureProviderChanged;
833 m_common.propertyChanged(item: m_item, itemMetaObject: m_itemMetaObject, mappedId, textureProviderChanged: &textureProviderChanged);
834 m_dirtyTextureProviders |= textureProviderChanged;
835 m_dirtyUniformValues = true;
836 m_item->update();
837}
838
839void QQuickOpenGLShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &)
840{
841 m_dirtyGeometry = true;
842}
843
844QSGNode *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(sender: node, SIGNAL(logAndStatusChanged(QString,int)), receiver: this, SLOT(updateLogAndStatus(QString,int)));
863 connect(sender: node, signal: &QQuickOpenGLShaderEffectNode::dirtyTexture,
864 receiver: this, slot: &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(flags: QSGMaterial::Blending, on: m_blending);
872 node->markDirty(bits: QSGNode::DirtyMaterial);
873 }
874
875 if (int(material->cullMode) != int(m_cullMode)) {
876 material->cullMode = QQuickShaderEffect::CullMode(m_cullMode);
877 node->markDirty(bits: 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(bits: 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, updateUniforms: m_dirtyUniforms, updateUniformValues: m_dirtyUniformValues,
902 updateTextureProviders: m_dirtyTextureProviders);
903 node->markDirty(bits: 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(i: 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(flags: QSGMaterial::RequiresFullMatrix, on: m_customVertexShader);
919 node->markDirty(bits: QSGNode::DirtyMaterial);
920 }
921
922 if (material->geometryUsesTextureSubRect != geometryUsesTextureSubRect) {
923 material->geometryUsesTextureSubRect = geometryUsesTextureSubRect;
924 node->markDirty(bits: 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(attributes: m_common.attributes, posIndex: &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, attrCount: 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
963void 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(item: m_item, itemMetaObject: m_itemMetaObject, shaderType: Key::VertexShader);
979 }
980
981 if (m_fragNeedsUpdate) {
982 m_fragNeedsUpdate = false;
983 m_common.updateShader(item: m_item, itemMetaObject: m_itemMetaObject, shaderType: Key::FragmentShader);
984 }
985}
986
987void QQuickOpenGLShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
988{
989 if (change == QQuickItem::ItemSceneChange)
990 m_common.updateWindow(window: value.window);
991}
992
993QT_END_NAMESPACE
994
995#include "moc_qquickopenglshadereffect_p.cpp"
996

source code of qtdeclarative/src/quick/items/qquickopenglshadereffect.cpp