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
54QT_BEGIN_NAMESPACE
55
56static 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
66class QQuickCustomMaterialShader : public QSGMaterialShader
67{
68public:
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
74protected:
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
91QQuickCustomMaterialShader::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
104void QQuickCustomMaterialShader::deactivate()
105{
106 QSGMaterialShader::deactivate();
107 QOpenGLContext::currentContext()->functions()->glDisable(GL_CULL_FACE);
108}
109
110void 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
262char const *const *QQuickCustomMaterialShader::attributeNames() const
263{
264 return m_attributeNames.constData();
265}
266
267void 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
332const char *QQuickCustomMaterialShader::vertexShader() const
333{
334 return m_key.sourceCode[QQuickOpenGLShaderEffectMaterialKey::VertexShader].constData();
335}
336
337const char *QQuickCustomMaterialShader::fragmentShader() const
338{
339 return m_key.sourceCode[QQuickOpenGLShaderEffectMaterialKey::FragmentShader].constData();
340}
341
342
343bool 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
352bool QQuickOpenGLShaderEffectMaterialKey::operator != (const QQuickOpenGLShaderEffectMaterialKey &other) const
353{
354 return !(*this == other);
355}
356
357uint 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
366class QQuickOpenGLShaderEffectMaterialCache : public QObject
367{
368 Q_OBJECT
369public:
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
383QQuickOpenGLShaderEffectMaterial::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
392QSGMaterialType *QQuickOpenGLShaderEffectMaterial::type() const
393{
394 return m_type;
395}
396
397QSGMaterialShader *QQuickOpenGLShaderEffectMaterial::createShader() const
398{
399 return new QQuickCustomMaterialShader(m_source, attributes);
400}
401
402bool 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
419int 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
450void 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
463void QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache()
464{
465 QQuickOpenGLShaderEffectMaterialCache *cache = QQuickOpenGLShaderEffectMaterialCache::get(create: false);
466 if (cache) {
467 qDeleteAll(c: cache->cache);
468 delete cache;
469 }
470}
471
472void 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
482void 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
491QQuickOpenGLShaderEffectNode::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
500QQuickOpenGLShaderEffectNode::~QQuickOpenGLShaderEffectNode()
501{
502}
503
504void QQuickOpenGLShaderEffectNode::markDirtyTexture()
505{
506 markDirty(bits: DirtyMaterial);
507 Q_EMIT dirtyTexture();
508}
509
510void QQuickOpenGLShaderEffectNode::textureProviderDestroyed(QObject *object)
511{
512 Q_ASSERT(material());
513 static_cast<QQuickOpenGLShaderEffectMaterial *>(material())->invalidateTextureProvider(provider: object);
514}
515
516void 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
525QT_END_NAMESPACE
526

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