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