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 | |
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 | |
186 | namespace QtPrivate { |
187 | class MappedSlotObject: public QtPrivate::QSlotObjectBase |
188 | { |
189 | public: |
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 | |
199 | private: |
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 | |
222 | QQuickOpenGLShaderEffectCommon::~QQuickOpenGLShaderEffectCommon() |
223 | { |
224 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) |
225 | clearSignalMappers(shader: shaderType); |
226 | } |
227 | |
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(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 | |
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, 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 | |
294 | void 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 | |
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, 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 | |
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(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 | |
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(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 | |
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 *>(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 | |
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 *>(v: d.value) == object) |
562 | d.value = QVariant(); |
563 | } |
564 | } |
565 | } |
566 | } |
567 | |
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 *>(v: d.value) == source) |
576 | return false; |
577 | } |
578 | } |
579 | return true; |
580 | } |
581 | |
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 *>(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 | |
624 | void 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 | |
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 | |
657 | QQuickOpenGLShaderEffect::~QQuickOpenGLShaderEffect() |
658 | { |
659 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) |
660 | m_common.disconnectPropertySignals(item: m_item, shaderType: Key::ShaderType(shaderType)); |
661 | } |
662 | |
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 | |
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 | |
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 | |
715 | QVariant QQuickOpenGLShaderEffect::mesh() const |
716 | { |
717 | return m_mesh ? QVariant::fromValue(value: static_cast<QObject *>(m_mesh)) |
718 | : QVariant::fromValue(value: m_meshResolution); |
719 | } |
720 | |
721 | void 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 | |
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 | |
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 | |
775 | QString 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 | |
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(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 | |
805 | void QQuickOpenGLShaderEffect::updateGeometry() |
806 | { |
807 | m_dirtyGeometry = true; |
808 | m_item->update(); |
809 | } |
810 | |
811 | void QQuickOpenGLShaderEffect::updateGeometryIfAtlased() |
812 | { |
813 | if (m_supportsAtlasTextures) |
814 | updateGeometry(); |
815 | } |
816 | |
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 | |
825 | void QQuickOpenGLShaderEffect::sourceDestroyed(QObject *object) |
826 | { |
827 | m_common.sourceDestroyed(object); |
828 | } |
829 | |
830 | void 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 | |
839 | void QQuickOpenGLShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &) |
840 | { |
841 | m_dirtyGeometry = true; |
842 | } |
843 | |
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(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 | |
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(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 | |
987 | void QQuickOpenGLShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
988 | { |
989 | if (change == QQuickItem::ItemSceneChange) |
990 | m_common.updateWindow(window: value.window); |
991 | } |
992 | |
993 | QT_END_NAMESPACE |
994 | |
995 | #include "moc_qquickopenglshadereffect_p.cpp" |
996 | |