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/qquickopenglshadereffectnode_p.h> |
41 | |
42 | #include "qquickopenglshadereffect_p.h" |
43 | #include <QtQuick/qsgtextureprovider.h> |
44 | #include <QtQuick/private/qsgrenderer_p.h> |
45 | #include <QtQuick/private/qsgshadersourcebuilder_p.h> |
46 | #include <QtQuick/private/qsgtexture_p.h> |
47 | #include <QtCore/qmutex.h> |
48 | #include <QtGui/qopenglfunctions.h> |
49 | |
50 | #ifndef GL_TEXTURE_EXTERNAL_OES |
51 | #define GL_TEXTURE_EXTERNAL_OES 0x8D65 |
52 | #endif |
53 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | static bool hasAtlasTexture(const QVector<QSGTextureProvider *> &textureProviders) |
57 | { |
58 | for (int i = 0; i < textureProviders.size(); ++i) { |
59 | QSGTextureProvider *t = textureProviders.at(i); |
60 | if (t && t->texture() && t->texture()->isAtlasTexture()) |
61 | return true; |
62 | } |
63 | return false; |
64 | } |
65 | |
66 | class QQuickCustomMaterialShader : public QSGMaterialShader |
67 | { |
68 | public: |
69 | QQuickCustomMaterialShader(const QQuickOpenGLShaderEffectMaterialKey &key, const QVector<QByteArray> &attributes); |
70 | void deactivate() override; |
71 | void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; |
72 | char const *const *attributeNames() const override; |
73 | |
74 | protected: |
75 | friend class QQuickOpenGLShaderEffectNode; |
76 | |
77 | void compile() override; |
78 | const char *vertexShader() const override; |
79 | const char *fragmentShader() const override; |
80 | |
81 | const QQuickOpenGLShaderEffectMaterialKey m_key; |
82 | QVector<QByteArray> m_attributes; |
83 | QVector<const char *> m_attributeNames; |
84 | QString m_log; |
85 | bool m_compiled; |
86 | |
87 | QVector<int> m_uniformLocs[QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount]; |
88 | uint m_initialized : 1; |
89 | }; |
90 | |
91 | QQuickCustomMaterialShader::QQuickCustomMaterialShader(const QQuickOpenGLShaderEffectMaterialKey &key, const QVector<QByteArray> &attributes) |
92 | : m_key(key) |
93 | , m_attributes(attributes) |
94 | , m_compiled(false) |
95 | , m_initialized(false) |
96 | { |
97 | const int attributesCount = m_attributes.count(); |
98 | m_attributeNames.reserve(asize: attributesCount + 1); |
99 | for (int i = 0; i < attributesCount; ++i) |
100 | m_attributeNames.append(t: m_attributes.at(i).constData()); |
101 | m_attributeNames.append(t: 0); |
102 | } |
103 | |
104 | void QQuickCustomMaterialShader::deactivate() |
105 | { |
106 | QSGMaterialShader::deactivate(); |
107 | QOpenGLContext::currentContext()->functions()->glDisable(GL_CULL_FACE); |
108 | } |
109 | |
110 | void QQuickCustomMaterialShader::updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) |
111 | { |
112 | typedef QQuickOpenGLShaderEffectMaterial::UniformData UniformData; |
113 | |
114 | Q_ASSERT(newEffect != nullptr); |
115 | |
116 | QQuickOpenGLShaderEffectMaterial *material = static_cast<QQuickOpenGLShaderEffectMaterial *>(newEffect); |
117 | if (!material->m_emittedLogChanged && material->m_node) { |
118 | material->m_emittedLogChanged = true; |
119 | emit material->m_node->logAndStatusChanged(m_log, status: m_compiled ? QQuickShaderEffect::Compiled |
120 | : QQuickShaderEffect::Error); |
121 | } |
122 | |
123 | if (newEffect != oldEffect) |
124 | m_initialized = false; |
125 | |
126 | int textureProviderIndex = 0; |
127 | if (!m_initialized) { |
128 | for (int shaderType = 0; shaderType < QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++shaderType) { |
129 | m_uniformLocs[shaderType].clear(); |
130 | m_uniformLocs[shaderType].reserve(asize: material->uniforms[shaderType].size()); |
131 | for (int i = 0; i < material->uniforms[shaderType].size(); ++i) { |
132 | const UniformData &d = material->uniforms[shaderType].at(i); |
133 | QByteArray name = d.name; |
134 | if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { |
135 | program()->setUniformValue(name: d.name.constData(), value: textureProviderIndex++); |
136 | // We don't need to store the sampler uniform locations, since their values |
137 | // only need to be set once. Look for the "qt_SubRect_" uniforms instead. |
138 | // These locations are used when binding the textures later. |
139 | name = "qt_SubRect_" + name; |
140 | } |
141 | m_uniformLocs[shaderType].append(t: program()->uniformLocation(name: name.constData())); |
142 | } |
143 | } |
144 | m_initialized = true; |
145 | textureProviderIndex = 0; |
146 | } |
147 | |
148 | QOpenGLFunctions *functions = state.context()->functions(); |
149 | for (int shaderType = 0; shaderType < QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++shaderType) { |
150 | for (int i = 0; i < material->uniforms[shaderType].size(); ++i) { |
151 | const UniformData &d = material->uniforms[shaderType].at(i); |
152 | int loc = m_uniformLocs[shaderType].at(i); |
153 | if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { |
154 | int idx = textureProviderIndex++; |
155 | functions->glActiveTexture(GL_TEXTURE0 + idx); |
156 | if (QSGTextureProvider *provider = material->textureProviders.at(i: idx)) { |
157 | if (QSGTexture *texture = provider->texture()) { |
158 | |
159 | #ifndef QT_NO_DEBUG |
160 | if (!qsg_safeguard_texture(texture)) |
161 | continue; |
162 | #endif |
163 | |
164 | if (loc >= 0) { |
165 | QRectF r = texture->normalizedTextureSubRect(); |
166 | program()->setUniformValue(location: loc, x: r.x(), y: r.y(), z: r.width(), w: r.height()); |
167 | } else if (texture->isAtlasTexture() && !material->geometryUsesTextureSubRect) { |
168 | texture = texture->removedFromAtlas(); |
169 | } |
170 | texture->bind(); |
171 | continue; |
172 | } |
173 | } |
174 | if (d.specialType == UniformData::Sampler) |
175 | functions->glBindTexture(GL_TEXTURE_2D, texture: 0); |
176 | else if (d.specialType == UniformData::SamplerExternal) |
177 | functions->glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture: 0); |
178 | } else if (d.specialType == UniformData::Opacity) { |
179 | program()->setUniformValue(location: loc, value: state.opacity()); |
180 | } else if (d.specialType == UniformData::Matrix) { |
181 | if (state.isMatrixDirty()) |
182 | program()->setUniformValue(location: loc, value: state.combinedMatrix()); |
183 | } else if (d.specialType == UniformData::None) { |
184 | switch (int(d.value.userType())) { |
185 | case QMetaType::QColor: |
186 | program()->setUniformValue(location: loc, color: qt_premultiply_color(c: qvariant_cast<QColor>(v: d.value))); |
187 | break; |
188 | case QMetaType::Float: |
189 | program()->setUniformValue(location: loc, value: qvariant_cast<float>(v: d.value)); |
190 | break; |
191 | case QMetaType::Double: |
192 | program()->setUniformValue(location: loc, value: (float) qvariant_cast<double>(v: d.value)); |
193 | break; |
194 | case QMetaType::QTransform: |
195 | program()->setUniformValue(location: loc, value: qvariant_cast<QTransform>(v: d.value)); |
196 | break; |
197 | case QMetaType::Int: |
198 | program()->setUniformValue(location: loc, value: d.value.toInt()); |
199 | break; |
200 | case QMetaType::Bool: |
201 | program()->setUniformValue(location: loc, value: GLint(d.value.toBool())); |
202 | break; |
203 | case QMetaType::QSize: |
204 | case QMetaType::QSizeF: |
205 | program()->setUniformValue(location: loc, size: d.value.toSizeF()); |
206 | break; |
207 | case QMetaType::QPoint: |
208 | case QMetaType::QPointF: |
209 | program()->setUniformValue(location: loc, point: d.value.toPointF()); |
210 | break; |
211 | case QMetaType::QRect: |
212 | case QMetaType::QRectF: |
213 | { |
214 | QRectF r = d.value.toRectF(); |
215 | program()->setUniformValue(location: loc, x: r.x(), y: r.y(), z: r.width(), w: r.height()); |
216 | } |
217 | break; |
218 | case QMetaType::QVector2D: |
219 | program()->setUniformValue(location: loc, value: qvariant_cast<QVector2D>(v: d.value)); |
220 | break; |
221 | case QMetaType::QVector3D: |
222 | program()->setUniformValue(location: loc, value: qvariant_cast<QVector3D>(v: d.value)); |
223 | break; |
224 | case QMetaType::QVector4D: |
225 | program()->setUniformValue(location: loc, value: qvariant_cast<QVector4D>(v: d.value)); |
226 | break; |
227 | case QMetaType::QQuaternion: |
228 | { |
229 | QQuaternion q = qvariant_cast<QQuaternion>(v: d.value); |
230 | program()->setUniformValue(location: loc, x: q.x(), y: q.y(), z: q.z(), w: q.scalar()); |
231 | } |
232 | break; |
233 | case QMetaType::QMatrix4x4: |
234 | program()->setUniformValue(location: loc, value: qvariant_cast<QMatrix4x4>(v: d.value)); |
235 | break; |
236 | default: |
237 | break; |
238 | } |
239 | } |
240 | } |
241 | } |
242 | functions->glActiveTexture(GL_TEXTURE0); |
243 | |
244 | const QQuickOpenGLShaderEffectMaterial *oldMaterial = static_cast<const QQuickOpenGLShaderEffectMaterial *>(oldEffect); |
245 | if (oldEffect == nullptr || material->cullMode != oldMaterial->cullMode) { |
246 | switch (material->cullMode) { |
247 | case QQuickShaderEffect::FrontFaceCulling: |
248 | functions->glEnable(GL_CULL_FACE); |
249 | functions->glCullFace(GL_FRONT); |
250 | break; |
251 | case QQuickShaderEffect::BackFaceCulling: |
252 | functions->glEnable(GL_CULL_FACE); |
253 | functions->glCullFace(GL_BACK); |
254 | break; |
255 | default: |
256 | functions->glDisable(GL_CULL_FACE); |
257 | break; |
258 | } |
259 | } |
260 | } |
261 | |
262 | char const *const *QQuickCustomMaterialShader::attributeNames() const |
263 | { |
264 | return m_attributeNames.constData(); |
265 | } |
266 | |
267 | void QQuickCustomMaterialShader::compile() |
268 | { |
269 | Q_ASSERT_X(!program()->isLinked(), "QQuickCustomMaterialShader::compile()" , "Compile called multiple times!" ); |
270 | |
271 | m_log.clear(); |
272 | m_compiled = true; |
273 | if (!program()->addCacheableShaderFromSourceCode(type: QOpenGLShader::Vertex, source: vertexShader())) { |
274 | m_log += QLatin1String("*** Vertex shader ***\n" ) + program()->log(); |
275 | m_compiled = false; |
276 | } |
277 | if (!program()->addCacheableShaderFromSourceCode(type: QOpenGLShader::Fragment, source: fragmentShader())) { |
278 | m_log += QLatin1String("*** Fragment shader ***\n" ) + program()->log(); |
279 | m_compiled = false; |
280 | } |
281 | |
282 | char const *const *attr = attributeNames(); |
283 | #ifndef QT_NO_DEBUG |
284 | int maxVertexAttribs = 0; |
285 | QOpenGLContext::currentContext()->functions()->glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, params: &maxVertexAttribs); |
286 | int attrCount = 0; |
287 | while (attrCount < maxVertexAttribs && attr[attrCount]) |
288 | ++attrCount; |
289 | if (attr[attrCount]) { |
290 | qWarning(msg: "List of attribute names is too long.\n" |
291 | "Maximum number of attributes on this hardware is %i.\n" |
292 | "Vertex shader:\n%s\n" |
293 | "Fragment shader:\n%s\n" , |
294 | maxVertexAttribs, vertexShader(), fragmentShader()); |
295 | } |
296 | #endif |
297 | |
298 | if (m_compiled) { |
299 | #ifndef QT_NO_DEBUG |
300 | for (int i = 0; i < attrCount; ++i) { |
301 | #else |
302 | for (int i = 0; attr[i]; ++i) { |
303 | #endif |
304 | if (*attr[i]) |
305 | program()->bindAttributeLocation(name: attr[i], location: i); |
306 | } |
307 | m_compiled = program()->link(); |
308 | m_log += program()->log(); |
309 | } |
310 | |
311 | if (!m_compiled) { |
312 | qWarning(msg: "QQuickCustomMaterialShader: Shader compilation failed:" ); |
313 | qWarning() << program()->log(); |
314 | |
315 | QSGShaderSourceBuilder::initializeProgramFromFiles( |
316 | program: program(), |
317 | QStringLiteral(":/qt-project.org/items/shaders/shadereffectfallback.vert" ), |
318 | QStringLiteral(":/qt-project.org/items/shaders/shadereffectfallback.frag" )); |
319 | |
320 | #ifndef QT_NO_DEBUG |
321 | for (int i = 0; i < attrCount; ++i) { |
322 | #else |
323 | for (int i = 0; attr[i]; ++i) { |
324 | #endif |
325 | if (qstrcmp(str1: attr[i], str2: qtPositionAttributeName()) == 0) |
326 | program()->bindAttributeLocation(name: "v" , location: i); |
327 | } |
328 | program()->link(); |
329 | } |
330 | } |
331 | |
332 | const char *QQuickCustomMaterialShader::vertexShader() const |
333 | { |
334 | return m_key.sourceCode[QQuickOpenGLShaderEffectMaterialKey::VertexShader].constData(); |
335 | } |
336 | |
337 | const char *QQuickCustomMaterialShader::fragmentShader() const |
338 | { |
339 | return m_key.sourceCode[QQuickOpenGLShaderEffectMaterialKey::FragmentShader].constData(); |
340 | } |
341 | |
342 | |
343 | bool QQuickOpenGLShaderEffectMaterialKey::operator == (const QQuickOpenGLShaderEffectMaterialKey &other) const |
344 | { |
345 | for (int shaderType = 0; shaderType < ShaderTypeCount; ++shaderType) { |
346 | if (sourceCode[shaderType] != other.sourceCode[shaderType]) |
347 | return false; |
348 | } |
349 | return true; |
350 | } |
351 | |
352 | bool QQuickOpenGLShaderEffectMaterialKey::operator != (const QQuickOpenGLShaderEffectMaterialKey &other) const |
353 | { |
354 | return !(*this == other); |
355 | } |
356 | |
357 | uint qHash(const QQuickOpenGLShaderEffectMaterialKey &key) |
358 | { |
359 | uint hash = 1; |
360 | typedef QQuickOpenGLShaderEffectMaterialKey Key; |
361 | for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) |
362 | hash = hash * 31337 + qHash(key: key.sourceCode[shaderType]); |
363 | return hash; |
364 | } |
365 | |
366 | class QQuickOpenGLShaderEffectMaterialCache : public QObject |
367 | { |
368 | Q_OBJECT |
369 | public: |
370 | static QQuickOpenGLShaderEffectMaterialCache *get(bool create = true) { |
371 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
372 | QQuickOpenGLShaderEffectMaterialCache *me = ctx->findChild<QQuickOpenGLShaderEffectMaterialCache *>(QStringLiteral("__qt_ShaderEffectCache" ), options: Qt::FindDirectChildrenOnly); |
373 | if (!me && create) { |
374 | me = new QQuickOpenGLShaderEffectMaterialCache(); |
375 | me->setObjectName(QStringLiteral("__qt_ShaderEffectCache" )); |
376 | me->setParent(ctx); |
377 | } |
378 | return me; |
379 | } |
380 | QHash<QQuickOpenGLShaderEffectMaterialKey, QSGMaterialType *> cache; |
381 | }; |
382 | |
383 | QQuickOpenGLShaderEffectMaterial::QQuickOpenGLShaderEffectMaterial(QQuickOpenGLShaderEffectNode *node) |
384 | : cullMode(QQuickShaderEffect::NoCulling) |
385 | , geometryUsesTextureSubRect(false) |
386 | , m_node(node) |
387 | , m_emittedLogChanged(false) |
388 | { |
389 | setFlag(flags: Blending | RequiresFullMatrix, on: true); |
390 | } |
391 | |
392 | QSGMaterialType *QQuickOpenGLShaderEffectMaterial::type() const |
393 | { |
394 | return m_type; |
395 | } |
396 | |
397 | QSGMaterialShader *QQuickOpenGLShaderEffectMaterial::createShader() const |
398 | { |
399 | return new QQuickCustomMaterialShader(m_source, attributes); |
400 | } |
401 | |
402 | bool QQuickOpenGLShaderEffectMaterial::UniformData::operator == (const UniformData &other) const |
403 | { |
404 | if (specialType != other.specialType) |
405 | return false; |
406 | if (name != other.name) |
407 | return false; |
408 | |
409 | if (specialType == UniformData::Sampler || specialType == UniformData::SamplerExternal) { |
410 | // We can't check the source objects as these live in the GUI thread, |
411 | // so return true here and rely on the textureProvider check for |
412 | // equality of these.. |
413 | return true; |
414 | } else { |
415 | return value == other.value; |
416 | } |
417 | } |
418 | |
419 | int QQuickOpenGLShaderEffectMaterial::compare(const QSGMaterial *o) const |
420 | { |
421 | const QQuickOpenGLShaderEffectMaterial *other = static_cast<const QQuickOpenGLShaderEffectMaterial *>(o); |
422 | if ((hasAtlasTexture(textureProviders) && !geometryUsesTextureSubRect) || (hasAtlasTexture(textureProviders: other->textureProviders) && !other->geometryUsesTextureSubRect)) |
423 | return 1; |
424 | if (cullMode != other->cullMode) |
425 | return 1; |
426 | for (int shaderType = 0; shaderType < QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++shaderType) { |
427 | if (uniforms[shaderType] != other->uniforms[shaderType]) |
428 | return 1; |
429 | } |
430 | |
431 | // Check the texture providers.. |
432 | if (textureProviders.size() != other->textureProviders.size()) |
433 | return 1; |
434 | for (int i=0; i<textureProviders.size(); ++i) { |
435 | QSGTextureProvider *tp1 = textureProviders.at(i); |
436 | QSGTextureProvider *tp2 = other->textureProviders.at(i); |
437 | if (!tp1 || !tp2) |
438 | return tp1 == tp2 ? 0 : 1; |
439 | QSGTexture *t1 = tp1->texture(); |
440 | QSGTexture *t2 = tp2->texture(); |
441 | if (!t1 || !t2) |
442 | return t1 == t2 ? 0 : 1; |
443 | // Check texture id's as textures may be in the same atlas. |
444 | if (t1->textureId() != t2->textureId()) |
445 | return 1; |
446 | } |
447 | return 0; |
448 | } |
449 | |
450 | void QQuickOpenGLShaderEffectMaterial::setProgramSource(const QQuickOpenGLShaderEffectMaterialKey &source) |
451 | { |
452 | m_source = source; |
453 | m_emittedLogChanged = false; |
454 | |
455 | QQuickOpenGLShaderEffectMaterialCache *cache = QQuickOpenGLShaderEffectMaterialCache::get(); |
456 | m_type = cache->cache.value(akey: m_source); |
457 | if (!m_type) { |
458 | m_type = new QSGMaterialType(); |
459 | cache->cache.insert(akey: source, avalue: m_type); |
460 | } |
461 | } |
462 | |
463 | void QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache() |
464 | { |
465 | QQuickOpenGLShaderEffectMaterialCache *cache = QQuickOpenGLShaderEffectMaterialCache::get(create: false); |
466 | if (cache) { |
467 | qDeleteAll(c: cache->cache); |
468 | delete cache; |
469 | } |
470 | } |
471 | |
472 | void QQuickOpenGLShaderEffectMaterial::updateTextures() const |
473 | { |
474 | for (int i = 0; i < textureProviders.size(); ++i) { |
475 | if (QSGTextureProvider *provider = textureProviders.at(i)) { |
476 | if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(object: provider->texture())) |
477 | texture->updateTexture(); |
478 | } |
479 | } |
480 | } |
481 | |
482 | void QQuickOpenGLShaderEffectMaterial::invalidateTextureProvider(const QObject *provider) |
483 | { |
484 | for (int i = 0; i < textureProviders.size(); ++i) { |
485 | if (provider == textureProviders.at(i)) |
486 | textureProviders[i] = nullptr; |
487 | } |
488 | } |
489 | |
490 | |
491 | QQuickOpenGLShaderEffectNode::QQuickOpenGLShaderEffectNode() |
492 | { |
493 | QSGNode::setFlag(UsePreprocess, true); |
494 | |
495 | #ifdef QSG_RUNTIME_DESCRIPTION |
496 | qsgnode_set_description(node: this, description: QLatin1String("shadereffect" )); |
497 | #endif |
498 | } |
499 | |
500 | QQuickOpenGLShaderEffectNode::~QQuickOpenGLShaderEffectNode() |
501 | { |
502 | } |
503 | |
504 | void QQuickOpenGLShaderEffectNode::markDirtyTexture() |
505 | { |
506 | markDirty(bits: DirtyMaterial); |
507 | Q_EMIT dirtyTexture(); |
508 | } |
509 | |
510 | void QQuickOpenGLShaderEffectNode::textureProviderDestroyed(QObject *object) |
511 | { |
512 | Q_ASSERT(material()); |
513 | static_cast<QQuickOpenGLShaderEffectMaterial *>(material())->invalidateTextureProvider(provider: object); |
514 | } |
515 | |
516 | void QQuickOpenGLShaderEffectNode::preprocess() |
517 | { |
518 | Q_ASSERT(material()); |
519 | static_cast<QQuickOpenGLShaderEffectMaterial *>(material())->updateTextures(); |
520 | } |
521 | |
522 | #include "qquickopenglshadereffectnode.moc" |
523 | #include "moc_qquickopenglshadereffectnode_p.cpp" |
524 | |
525 | QT_END_NAMESPACE |
526 | |