1/****************************************************************************
2**
3** Copyright (C) 2019 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 "qsgrhishadereffectnode_p.h"
41#include "qsgdefaultrendercontext_p.h"
42#include "qsgrhisupport_p.h"
43#include <qsgmaterialrhishader.h>
44#include <qsgtextureprovider.h>
45#include <private/qsgplaintexture_p.h>
46#include <QtGui/private/qshaderdescription_p.h>
47#include <QQmlFile>
48#include <QFile>
49#include <QFileSelector>
50
51QT_BEGIN_NAMESPACE
52
53void QSGRhiShaderLinker::reset(const QShader &vs, const QShader &fs)
54{
55 Q_ASSERT(vs.isValid() && fs.isValid());
56 m_vs = vs;
57 m_fs = fs;
58
59 m_error = false;
60
61 m_constantBufferSize = 0;
62 m_constants.clear();
63 m_samplers.clear();
64 m_samplerNameMap.clear();
65}
66
67void QSGRhiShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
68{
69 Q_ASSERT(shader.shaderInfo.variables.count() == shader.varData.count());
70 if (!dirtyIndices) {
71 m_constantBufferSize = qMax(m_constantBufferSize, shader.shaderInfo.constantDataSize);
72 for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) {
73 const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
74 if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Constant) {
75 const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
76 Constant c;
77 c.size = var.size;
78 c.specialType = vd.specialType;
79 if (c.specialType != QSGShaderEffectNode::VariableData::SubRect) {
80 c.value = vd.value;
81 if (QSGRhiSupport::instance()->isShaderEffectDebuggingRequested()) {
82 if (c.specialType == QSGShaderEffectNode::VariableData::None) {
83 qDebug() << "cbuf prepare" << shader.shaderInfo.name << var.name
84 << "offset" << var.offset << "value" << c.value;
85 } else {
86 qDebug() << "cbuf prepare" << shader.shaderInfo.name << var.name
87 << "offset" << var.offset << "special" << c.specialType;
88 }
89 }
90 } else {
91 Q_ASSERT(var.name.startsWith(QByteArrayLiteral("qt_SubRect_")));
92 c.value = var.name.mid(11);
93 }
94 m_constants[var.offset] = c;
95 }
96 }
97 } else {
98 for (int idx : *dirtyIndices) {
99 const int offset = shader.shaderInfo.variables.at(idx).offset;
100 const QVariant value = shader.varData.at(idx).value;
101 m_constants[offset].value = value;
102 if (QSGRhiSupport::instance()->isShaderEffectDebuggingRequested()) {
103 qDebug() << "cbuf update" << shader.shaderInfo.name
104 << "offset" << offset << "value" << value;
105 }
106 }
107 }
108}
109
110void QSGRhiShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
111{
112 if (!dirtyIndices) {
113 for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) {
114 const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
115 const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
116 if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
117 Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Source);
118 m_samplers.insert(var.bindPoint, vd.value);
119 m_samplerNameMap.insert(var.name, var.bindPoint);
120 }
121 }
122 } else {
123 for (int idx : *dirtyIndices) {
124 const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(idx));
125 const QSGShaderEffectNode::VariableData &vd(shader.varData.at(idx));
126 m_samplers.insert(var.bindPoint, vd.value);
127 m_samplerNameMap.insert(var.name, var.bindPoint);
128 }
129 }
130}
131
132void QSGRhiShaderLinker::linkTextureSubRects()
133{
134 // feedConstants stores <name> in Constant::value for subrect entries. Now
135 // that both constants and textures are known, replace the name with the
136 // texture binding point.
137 for (Constant &c : m_constants) {
138 if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
139 if (c.value.type() == QVariant::ByteArray) {
140 const QByteArray name = c.value.toByteArray();
141 if (!m_samplerNameMap.contains(name))
142 qWarning("ShaderEffect: qt_SubRect_%s refers to unknown source texture", name.constData());
143 c.value = m_samplerNameMap[name];
144 }
145 }
146 }
147}
148
149void QSGRhiShaderLinker::dump()
150{
151 if (m_error) {
152 qDebug() << "Failed to generate program data";
153 return;
154 }
155 qDebug() << "Combined shader data" << m_vs << m_fs << "cbuffer size" << m_constantBufferSize;
156 qDebug() << " - constants" << m_constants;
157 qDebug() << " - samplers" << m_samplers;
158}
159
160QDebug operator<<(QDebug debug, const QSGRhiShaderLinker::Constant &c)
161{
162 QDebugStateSaver saver(debug);
163 debug.space();
164 debug << "size" << c.size;
165 if (c.specialType != QSGShaderEffectNode::VariableData::None)
166 debug << "special" << c.specialType;
167 else
168 debug << "value" << c.value;
169 return debug;
170}
171
172struct QSGRhiShaderMaterialTypeCache
173{
174 QSGMaterialType *get(const QShader &vs, const QShader &fs);
175 void reset() { qDeleteAll(m_types); m_types.clear(); }
176
177 struct Key {
178 QShader blob[2];
179 Key() { }
180 Key(const QShader &vs, const QShader &fs) { blob[0] = vs; blob[1] = fs; }
181 bool operator==(const Key &other) const {
182 return blob[0] == other.blob[0] && blob[1] == other.blob[1];
183 }
184 };
185 QHash<Key, QSGMaterialType *> m_types;
186};
187
188uint qHash(const QSGRhiShaderMaterialTypeCache::Key &key, uint seed = 0)
189{
190 uint hash = seed;
191 for (int i = 0; i < 2; ++i)
192 hash = hash * 31337 + qHash(key.blob[i]);
193 return hash;
194}
195
196QSGMaterialType *QSGRhiShaderMaterialTypeCache::get(const QShader &vs, const QShader &fs)
197{
198 const Key k(vs, fs);
199 if (m_types.contains(k))
200 return m_types.value(k);
201
202 QSGMaterialType *t = new QSGMaterialType;
203 m_types.insert(k, t);
204 return t;
205}
206
207static QSGRhiShaderMaterialTypeCache shaderMaterialTypeCache;
208
209class QSGRhiShaderEffectMaterialShader : public QSGMaterialRhiShader
210{
211public:
212 QSGRhiShaderEffectMaterialShader(const QSGRhiShaderEffectMaterial *material);
213
214 bool updateUniformData(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
215 void updateSampledImage(const RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
216 bool updateGraphicsPipelineState(const RenderState &state, GraphicsPipelineState *ps, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
217};
218
219QSGRhiShaderEffectMaterialShader::QSGRhiShaderEffectMaterialShader(const QSGRhiShaderEffectMaterial *material)
220{
221 setFlag(UpdatesGraphicsPipelineState, true);
222 setShader(VertexStage, material->m_vertexShader);
223 setShader(FragmentStage, material->m_fragmentShader);
224}
225
226static inline QColor qsg_premultiply_color(const QColor &c)
227{
228 return QColor::fromRgbF(c.redF() * c.alphaF(), c.greenF() * c.alphaF(), c.blueF() * c.alphaF(), c.alphaF());
229}
230
231bool QSGRhiShaderEffectMaterialShader::updateUniformData(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
232{
233 Q_UNUSED(oldMaterial);
234 QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
235
236 bool changed = false;
237 QByteArray *buf = state.uniformData();
238
239 for (auto it = mat->m_linker.m_constants.constBegin(), itEnd = mat->m_linker.m_constants.constEnd(); it != itEnd; ++it) {
240 const int offset = it.key();
241 char *dst = buf->data() + offset;
242 const QSGRhiShaderLinker::Constant &c(it.value());
243 if (c.specialType == QSGShaderEffectNode::VariableData::Opacity) {
244 if (state.isOpacityDirty()) {
245 const float f = state.opacity();
246 Q_ASSERT(sizeof(f) == c.size);
247 memcpy(dst, &f, sizeof(f));
248 changed = true;
249 }
250 } else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) {
251 if (state.isMatrixDirty()) {
252 const int sz = 16 * sizeof(float);
253 Q_ASSERT(sz == c.size);
254 memcpy(dst, state.combinedMatrix().constData(), sz);
255 changed = true;
256 }
257 } else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
258 // vec4
259 QRectF subRect(0, 0, 1, 1);
260 const int binding = c.value.toInt(); // filled in by linkTextureSubRects
261 if (binding < QSGRhiShaderEffectMaterial::MAX_BINDINGS) {
262 if (QSGTextureProvider *tp = mat->m_textureProviders.at(binding)) {
263 if (QSGTexture *t = tp->texture())
264 subRect = t->normalizedTextureSubRect();
265 }
266 }
267 const float f[4] = { float(subRect.x()), float(subRect.y()),
268 float(subRect.width()), float(subRect.height()) };
269 Q_ASSERT(sizeof(f) == c.size);
270 memcpy(dst, f, sizeof(f));
271 } else if (c.specialType == QSGShaderEffectNode::VariableData::None) {
272 changed = true;
273 switch (int(c.value.type())) {
274 case QMetaType::QColor: {
275 const QColor v = qsg_premultiply_color(qvariant_cast<QColor>(c.value));
276 const float f[4] = { float(v.redF()), float(v.greenF()), float(v.blueF()), float(v.alphaF()) };
277 Q_ASSERT(sizeof(f) == c.size);
278 memcpy(dst, f, sizeof(f));
279 break;
280 }
281 case QMetaType::Float: {
282 const float f = qvariant_cast<float>(c.value);
283 Q_ASSERT(sizeof(f) == c.size);
284 memcpy(dst, &f, sizeof(f));
285 break;
286 }
287 case QMetaType::Double: {
288 const float f = float(qvariant_cast<double>(c.value));
289 Q_ASSERT(sizeof(f) == c.size);
290 memcpy(dst, &f, sizeof(f));
291 break;
292 }
293 case QMetaType::Int: {
294 const int i = c.value.toInt();
295 Q_ASSERT(sizeof(i) == c.size);
296 memcpy(dst, &i, sizeof(i));
297 break;
298 }
299 case QMetaType::Bool: {
300 const bool b = c.value.toBool();
301 Q_ASSERT(sizeof(b) == c.size);
302 memcpy(dst, &b, sizeof(b));
303 break;
304 }
305 case QMetaType::QTransform: { // mat3
306 const QTransform v = qvariant_cast<QTransform>(c.value);
307 const float m[3][3] = {
308 { float(v.m11()), float(v.m12()), float(v.m13()) },
309 { float(v.m21()), float(v.m22()), float(v.m23()) },
310 { float(v.m31()), float(v.m32()), float(v.m33()) }
311 };
312 Q_ASSERT(sizeof(m) == c.size);
313 memcpy(dst, m[0], sizeof(m));
314 break;
315 }
316 case QMetaType::QSize:
317 case QMetaType::QSizeF: { // vec2
318 const QSizeF v = c.value.toSizeF();
319 const float f[2] = { float(v.width()), float(v.height()) };
320 Q_ASSERT(sizeof(f) == c.size);
321 memcpy(dst, f, sizeof(f));
322 break;
323 }
324 case QMetaType::QPoint:
325 case QMetaType::QPointF: { // vec2
326 const QPointF v = c.value.toPointF();
327 const float f[2] = { float(v.x()), float(v.y()) };
328 Q_ASSERT(sizeof(f) == c.size);
329 memcpy(dst, f, sizeof(f));
330 break;
331 }
332 case QMetaType::QRect:
333 case QMetaType::QRectF: { // vec4
334 const QRectF v = c.value.toRectF();
335 const float f[4] = { float(v.x()), float(v.y()), float(v.width()), float(v.height()) };
336 Q_ASSERT(sizeof(f) == c.size);
337 memcpy(dst, f, sizeof(f));
338 break;
339 }
340 case QMetaType::QVector2D: { // vec2
341 const QVector2D v = qvariant_cast<QVector2D>(c.value);
342 const float f[2] = { float(v.x()), float(v.y()) };
343 Q_ASSERT(sizeof(f) == c.size);
344 memcpy(dst, f, sizeof(f));
345 break;
346 }
347 case QMetaType::QVector3D: { // vec3
348 const QVector3D v = qvariant_cast<QVector3D>(c.value);
349 const float f[3] = { float(v.x()), float(v.y()), float(v.z()) };
350 Q_ASSERT(sizeof(f) == c.size);
351 memcpy(dst, f, sizeof(f));
352 break;
353 }
354 case QMetaType::QVector4D: { // vec4
355 const QVector4D v = qvariant_cast<QVector4D>(c.value);
356 const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.w()) };
357 Q_ASSERT(sizeof(f) == c.size);
358 memcpy(dst, f, sizeof(f));
359 break;
360 }
361 case QMetaType::QQuaternion: { // vec4
362 const QQuaternion v = qvariant_cast<QQuaternion>(c.value);
363 const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.scalar()) };
364 Q_ASSERT(sizeof(f) == c.size);
365 memcpy(dst, f, sizeof(f));
366 break;
367 }
368 case QMetaType::QMatrix4x4: { // mat4
369 const QMatrix4x4 v = qvariant_cast<QMatrix4x4>(c.value);
370 const int sz = 16 * sizeof(float);
371 Q_ASSERT(sz == c.size);
372 memcpy(dst, v.constData(), sz);
373 break;
374 }
375 default:
376 break;
377 }
378 }
379 }
380
381 return changed;
382}
383
384void QSGRhiShaderEffectMaterialShader::updateSampledImage(const RenderState &state, int binding, QSGTexture **texture,
385 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
386{
387 Q_UNUSED(oldMaterial);
388 QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
389
390 if (binding >= QSGRhiShaderEffectMaterial::MAX_BINDINGS)
391 return;
392
393 QSGTextureProvider *tp = mat->m_textureProviders.at(binding);
394 if (tp) {
395 if (QSGTexture *t = tp->texture()) {
396 t->updateRhiTexture(state.rhi(), state.resourceUpdateBatch());
397 if (t->isAtlasTexture() && !mat->m_geometryUsesTextureSubRect) {
398 // Why the hassle with the batch: while removedFromAtlas() is
399 // able to operate with its own resource update batch (which is
400 // then committed immediately), that approach is wrong when the
401 // atlas enqueued (in the updateRhiTexture() above) not yet
402 // committed operations to state.resourceUpdateBatch()... The
403 // only safe way then is to use the same batch the atlas'
404 // updateRhiTexture() used.
405 t->setWorkResourceUpdateBatch(state.resourceUpdateBatch());
406 QSGTexture *newTexture = t->removedFromAtlas();
407 t->setWorkResourceUpdateBatch(nullptr);
408 if (newTexture)
409 t = newTexture;
410 }
411 *texture = t;
412 return;
413 }
414 }
415
416 if (!mat->m_dummyTexture) {
417 mat->m_dummyTexture = new QSGPlainTexture;
418 mat->m_dummyTexture->setFiltering(QSGTexture::Nearest);
419 mat->m_dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
420 mat->m_dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
421 QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
422 img.fill(0);
423 mat->m_dummyTexture->setImage(img);
424 mat->m_dummyTexture->updateRhiTexture(state.rhi(), state.resourceUpdateBatch());
425 }
426 *texture = mat->m_dummyTexture;
427}
428
429bool QSGRhiShaderEffectMaterialShader::updateGraphicsPipelineState(const RenderState &state, GraphicsPipelineState *ps,
430 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
431{
432 Q_UNUSED(state);
433 Q_UNUSED(oldMaterial);
434 QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
435
436 switch (mat->m_cullMode) {
437 case QSGShaderEffectNode::FrontFaceCulling:
438 ps->cullMode = GraphicsPipelineState::CullFront;
439 return true;
440 case QSGShaderEffectNode::BackFaceCulling:
441 ps->cullMode = GraphicsPipelineState::CullBack;
442 return true;
443 default:
444 return false;
445 }
446}
447
448QSGRhiShaderEffectMaterial::QSGRhiShaderEffectMaterial(QSGRhiShaderEffectNode *node)
449 : m_node(node)
450{
451 setFlag(SupportsRhiShader | Blending | RequiresFullMatrix, true); // may be changed in syncMaterial()
452}
453
454QSGRhiShaderEffectMaterial::~QSGRhiShaderEffectMaterial()
455{
456 delete m_dummyTexture;
457}
458
459static bool hasAtlasTexture(const QVector<QSGTextureProvider *> &textureProviders)
460{
461 for (QSGTextureProvider *tp : textureProviders) {
462 if (tp && tp->texture() && tp->texture()->isAtlasTexture())
463 return true;
464 }
465 return false;
466}
467
468int QSGRhiShaderEffectMaterial::compare(const QSGMaterial *other) const
469{
470 Q_ASSERT(other && type() == other->type());
471 const QSGRhiShaderEffectMaterial *o = static_cast<const QSGRhiShaderEffectMaterial *>(other);
472
473 if (int diff = m_cullMode - o->m_cullMode)
474 return diff;
475
476 if (int diff = m_textureProviders.count() - o->m_textureProviders.count())
477 return diff;
478
479 if (m_linker.m_constants != o->m_linker.m_constants)
480 return 1;
481
482 if (hasAtlasTexture(m_textureProviders) && !m_geometryUsesTextureSubRect)
483 return -1;
484
485 if (hasAtlasTexture(o->m_textureProviders) && !o->m_geometryUsesTextureSubRect)
486 return 1;
487
488 for (int binding = 0, count = m_textureProviders.count(); binding != count; ++binding) {
489 QSGTextureProvider *tp1 = m_textureProviders.at(binding);
490 QSGTextureProvider *tp2 = o->m_textureProviders.at(binding);
491 if (tp1 && tp2) {
492 QSGTexture *t1 = tp1->texture();
493 QSGTexture *t2 = tp2->texture();
494 if (t1 && t2) {
495 if (int diff = t1->comparisonKey() - t2->comparisonKey())
496 return diff;
497 } else {
498 if (!t1 && t2)
499 return -1;
500 if (t1 && !t2)
501 return 1;
502 }
503 } else {
504 if (!tp1 && tp2)
505 return -1;
506 if (tp1 && !tp2)
507 return 1;
508 }
509 }
510
511 return 0;
512}
513
514QSGMaterialType *QSGRhiShaderEffectMaterial::type() const
515{
516 return m_materialType;
517}
518
519QSGMaterialShader *QSGRhiShaderEffectMaterial::createShader() const
520{
521 Q_ASSERT(flags().testFlag(RhiShaderWanted));
522 return new QSGRhiShaderEffectMaterialShader(this);
523}
524
525void QSGRhiShaderEffectMaterial::updateTextureProviders(bool layoutChange)
526{
527 if (layoutChange) {
528 for (QSGTextureProvider *tp : m_textureProviders) {
529 if (tp) {
530 QObject::disconnect(tp, SIGNAL(textureChanged()), m_node,
531 SLOT(handleTextureChange()));
532 QObject::disconnect(tp, SIGNAL(destroyed(QObject*)), m_node,
533 SLOT(handleTextureProviderDestroyed(QObject*)));
534 }
535 }
536 m_textureProviders.fill(nullptr, MAX_BINDINGS);
537 }
538
539 for (auto it = m_linker.m_samplers.constBegin(), itEnd = m_linker.m_samplers.constEnd(); it != itEnd; ++it) {
540 const int binding = it.key();
541 QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(it.value()));
542 QSGTextureProvider *newProvider = source && source->isTextureProvider() ? source->textureProvider() : nullptr;
543 if (binding >= MAX_BINDINGS) {
544 qWarning("Sampler at binding %d exceeds the available ShaderEffect binding slots; ignored",
545 binding);
546 continue;
547 }
548 QSGTextureProvider *&activeProvider(m_textureProviders[binding]);
549 if (newProvider != activeProvider) {
550 if (activeProvider) {
551 QObject::disconnect(activeProvider, SIGNAL(textureChanged()), m_node,
552 SLOT(handleTextureChange()));
553 QObject::disconnect(activeProvider, SIGNAL(destroyed(QObject*)), m_node,
554 SLOT(handleTextureProviderDestroyed(QObject*)));
555 }
556 if (newProvider) {
557 Q_ASSERT_X(newProvider->thread() == QThread::currentThread(),
558 "QSGRhiShaderEffectMaterial::updateTextureProviders",
559 "Texture provider must belong to the rendering thread");
560 QObject::connect(newProvider, SIGNAL(textureChanged()), m_node, SLOT(handleTextureChange()));
561 QObject::connect(newProvider, SIGNAL(destroyed(QObject*)), m_node,
562 SLOT(handleTextureProviderDestroyed(QObject*)));
563 } else {
564 const char *typeName = source ? source->metaObject()->className() : it.value().typeName();
565 qWarning("ShaderEffect: Texture t%d is not assigned a valid texture provider (%s).",
566 binding, typeName);
567 }
568 activeProvider = newProvider;
569 }
570 }
571}
572
573QSGRhiShaderEffectNode::QSGRhiShaderEffectNode(QSGDefaultRenderContext *rc, QSGRhiGuiThreadShaderEffectManager *mgr)
574 : QSGShaderEffectNode(mgr),
575 m_rc(rc),
576 m_mgr(mgr),
577 m_material(this)
578{
579 setFlag(UsePreprocess, true);
580 setMaterial(&m_material);
581}
582
583QRectF QSGRhiShaderEffectNode::updateNormalizedTextureSubRect(bool supportsAtlasTextures)
584{
585 QRectF srcRect(0, 0, 1, 1);
586 bool geometryUsesTextureSubRect = false;
587 if (supportsAtlasTextures) {
588 QSGTextureProvider *tp = nullptr;
589 for (int binding = 0, count = m_material.m_textureProviders.count(); binding != count; ++binding) {
590 if (QSGTextureProvider *candidate = m_material.m_textureProviders.at(binding)) {
591 if (!tp) {
592 tp = candidate;
593 } else { // there can only be one...
594 tp = nullptr;
595 break;
596 }
597 }
598 }
599 if (tp && tp->texture()) {
600 srcRect = tp->texture()->normalizedTextureSubRect();
601 geometryUsesTextureSubRect = true;
602 }
603 }
604
605 if (m_material.m_geometryUsesTextureSubRect != geometryUsesTextureSubRect) {
606 m_material.m_geometryUsesTextureSubRect = geometryUsesTextureSubRect;
607 markDirty(QSGNode::DirtyMaterial);
608 }
609
610 return srcRect;
611}
612
613static QShader loadShader(const QString &filename)
614{
615 QFile f(filename);
616 if (!f.open(QIODevice::ReadOnly)) {
617 qWarning() << "Failed to find shader" << filename;
618 return QShader();
619 }
620 return QShader::fromSerialized(f.readAll());
621}
622
623void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
624{
625 static QShader defaultVertexShader;
626 static QShader defaultFragmentShader;
627
628 if (bool(m_material.flags() & QSGMaterial::Blending) != syncData->blending) {
629 m_material.setFlag(QSGMaterial::Blending, syncData->blending);
630 markDirty(QSGNode::DirtyMaterial);
631 }
632
633 if (m_material.m_cullMode != syncData->cullMode) {
634 m_material.m_cullMode = syncData->cullMode;
635 markDirty(QSGNode::DirtyMaterial);
636 }
637
638 if (syncData->dirty & QSGShaderEffectNode::DirtyShaders) {
639 m_material.m_hasCustomVertexShader = syncData->vertex.shader->hasShaderCode;
640 if (m_material.m_hasCustomVertexShader) {
641 m_material.m_vertexShader = syncData->vertex.shader->shaderInfo.rhiShader;
642 } else {
643 if (!defaultVertexShader.isValid())
644 defaultVertexShader = loadShader(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"));
645 m_material.m_vertexShader = defaultVertexShader;
646 }
647
648 m_material.m_hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode;
649 if (m_material.m_hasCustomFragmentShader) {
650 m_material.m_fragmentShader = syncData->fragment.shader->shaderInfo.rhiShader;
651 } else {
652 if (!defaultFragmentShader.isValid())
653 defaultFragmentShader = loadShader(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"));
654 m_material.m_fragmentShader = defaultFragmentShader;
655 }
656
657 m_material.m_materialType = shaderMaterialTypeCache.get(m_material.m_vertexShader, m_material.m_fragmentShader);
658 m_material.m_linker.reset(m_material.m_vertexShader, m_material.m_fragmentShader);
659
660 if (m_material.m_hasCustomVertexShader) {
661 m_material.m_linker.feedConstants(*syncData->vertex.shader);
662 m_material.m_linker.feedSamplers(*syncData->vertex.shader);
663 } else {
664 QSGShaderEffectNode::ShaderData defaultSD;
665 defaultSD.shaderInfo.name = QLatin1String("Default ShaderEffect vertex shader");
666 defaultSD.shaderInfo.rhiShader = m_material.m_vertexShader;
667 defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex;
668
669 // { mat4 qt_Matrix; float qt_Opacity; } where only the matrix is used
670 QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
671 v.name = QByteArrayLiteral("qt_Matrix");
672 v.offset = 0;
673 v.size = 16 * sizeof(float);
674 defaultSD.shaderInfo.variables.append(v);
675 QSGShaderEffectNode::VariableData vd;
676 vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
677 defaultSD.varData.append(vd);
678 defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
679 m_material.m_linker.feedConstants(defaultSD);
680 }
681
682 if (m_material.m_hasCustomFragmentShader) {
683 m_material.m_linker.feedConstants(*syncData->fragment.shader);
684 m_material.m_linker.feedSamplers(*syncData->fragment.shader);
685 } else {
686 QSGShaderEffectNode::ShaderData defaultSD;
687 defaultSD.shaderInfo.name = QLatin1String("Default ShaderEffect fragment shader");
688 defaultSD.shaderInfo.rhiShader = m_material.m_fragmentShader;
689 defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
690
691 // { mat4 qt_Matrix; float qt_Opacity; } where only the opacity is used
692 QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
693 v.name = QByteArrayLiteral("qt_Opacity");
694 v.offset = 16 * sizeof(float);
695 v.size = sizeof(float);
696 defaultSD.shaderInfo.variables.append(v);
697 QSGShaderEffectNode::VariableData vd;
698 vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
699 defaultSD.varData.append(vd);
700
701 v.name = QByteArrayLiteral("source");
702 v.bindPoint = 1;
703 v.type = QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
704 defaultSD.shaderInfo.variables.append(v);
705 for (const QSGShaderEffectNode::VariableData &extVarData : qAsConst(syncData->fragment.shader->varData)) {
706 if (extVarData.specialType == QSGShaderEffectNode::VariableData::Source) {
707 vd.value = extVarData.value;
708 break;
709 }
710 }
711 vd.specialType = QSGShaderEffectNode::VariableData::Source;
712 defaultSD.varData.append(vd);
713
714 defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
715
716 m_material.m_linker.feedConstants(defaultSD);
717 m_material.m_linker.feedSamplers(defaultSD);
718 }
719
720 m_material.m_linker.linkTextureSubRects();
721 m_material.updateTextureProviders(true);
722 markDirty(QSGNode::DirtyMaterial);
723
724 } else {
725
726 if (syncData->dirty & QSGShaderEffectNode::DirtyShaderConstant) {
727 if (!syncData->vertex.dirtyConstants->isEmpty())
728 m_material.m_linker.feedConstants(*syncData->vertex.shader, syncData->vertex.dirtyConstants);
729 if (!syncData->fragment.dirtyConstants->isEmpty())
730 m_material.m_linker.feedConstants(*syncData->fragment.shader, syncData->fragment.dirtyConstants);
731 markDirty(QSGNode::DirtyMaterial);
732 }
733
734 if (syncData->dirty & QSGShaderEffectNode::DirtyShaderTexture) {
735 if (!syncData->vertex.dirtyTextures->isEmpty())
736 m_material.m_linker.feedSamplers(*syncData->vertex.shader, syncData->vertex.dirtyTextures);
737 if (!syncData->fragment.dirtyTextures->isEmpty())
738 m_material.m_linker.feedSamplers(*syncData->fragment.shader, syncData->fragment.dirtyTextures);
739 m_material.m_linker.linkTextureSubRects();
740 m_material.updateTextureProviders(false);
741 markDirty(QSGNode::DirtyMaterial);
742 }
743 }
744
745 if (bool(m_material.flags() & QSGMaterial::RequiresFullMatrix) != m_material.m_hasCustomVertexShader) {
746 m_material.setFlag(QSGMaterial::RequiresFullMatrix, m_material.m_hasCustomVertexShader);
747 markDirty(QSGNode::DirtyMaterial);
748 }
749}
750
751void QSGRhiShaderEffectNode::handleTextureChange()
752{
753 markDirty(QSGNode::DirtyMaterial);
754 emit m_mgr->textureChanged();
755}
756
757void QSGRhiShaderEffectNode::handleTextureProviderDestroyed(QObject *object)
758{
759 for (QSGTextureProvider *&tp : m_material.m_textureProviders) {
760 if (tp == object)
761 tp = nullptr;
762 }
763}
764
765void QSGRhiShaderEffectNode::preprocess()
766{
767 for (QSGTextureProvider *tp : m_material.m_textureProviders) {
768 if (tp) {
769 if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(tp->texture()))
770 texture->updateTexture();
771 }
772 }
773}
774
775void QSGRhiShaderEffectNode::cleanupMaterialTypeCache()
776{
777 shaderMaterialTypeCache.reset();
778}
779
780bool QSGRhiGuiThreadShaderEffectManager::hasSeparateSamplerAndTextureObjects() const
781{
782 return false; // because SPIR-V and QRhi make it look so, regardless of the underlying API
783}
784
785QString QSGRhiGuiThreadShaderEffectManager::log() const
786{
787 return QString();
788}
789
790QSGGuiThreadShaderEffectManager::Status QSGRhiGuiThreadShaderEffectManager::status() const
791{
792 return m_status;
793}
794
795void QSGRhiGuiThreadShaderEffectManager::prepareShaderCode(ShaderInfo::Type typeHint, const QByteArray &src, ShaderInfo *result)
796{
797 QUrl srcUrl(QString::fromUtf8(src));
798 if (!srcUrl.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || srcUrl.isLocalFile()) {
799 if (!m_fileSelector) {
800 m_fileSelector = new QFileSelector(this);
801 m_fileSelector->setExtraSelectors(QStringList() << QStringLiteral("qsb"));
802 }
803 const QString fn = m_fileSelector->select(QQmlFile::urlToLocalFileOrQrc(srcUrl));
804 QFile f(fn);
805 if (!f.open(QIODevice::ReadOnly)) {
806 qWarning("ShaderEffect: Failed to read %s", qPrintable(fn));
807 m_status = Error;
808 emit shaderCodePrepared(false, typeHint, src, result);
809 emit logAndStatusChanged();
810 return;
811 }
812 const QShader s = QShader::fromSerialized(f.readAll());
813 f.close();
814 if (!s.isValid()) {
815 qWarning("ShaderEffect: Failed to deserialize QShader from %s", qPrintable(fn));
816 m_status = Error;
817 emit shaderCodePrepared(false, typeHint, src, result);
818 emit logAndStatusChanged();
819 return;
820 }
821 result->name = fn;
822 result->rhiShader = s;
823 const bool ok = reflect(result);
824 m_status = ok ? Compiled : Error;
825 emit shaderCodePrepared(ok, typeHint, src, result);
826 emit logAndStatusChanged();
827 } else {
828 qWarning("rhi shader effect only supports files (qrc or local) at the moment");
829 emit shaderCodePrepared(false, typeHint, src, result);
830 }
831}
832
833bool QSGRhiGuiThreadShaderEffectManager::reflect(ShaderInfo *result)
834{
835 switch (result->rhiShader.stage()) {
836 case QShader::VertexStage:
837 result->type = ShaderInfo::TypeVertex;
838 break;
839 case QShader::FragmentStage:
840 result->type = ShaderInfo::TypeFragment;
841 break;
842 default:
843 result->type = ShaderInfo::TypeOther;
844 qWarning("Unsupported shader stage (%d)", result->rhiShader.stage());
845 return false;
846 }
847
848 const QShaderDescription desc = result->rhiShader.description();
849 result->constantDataSize = 0;
850
851 int ubufBinding = -1;
852 const QVector<QShaderDescription::UniformBlock> ubufs = desc.uniformBlocks();
853 const int ubufCount = ubufs.count();
854 for (int i = 0; i < ubufCount; ++i) {
855 const QShaderDescription::UniformBlock &ubuf(ubufs[i]);
856 if (ubufBinding == -1 && ubuf.binding >= 0) {
857 ubufBinding = ubuf.binding;
858 result->constantDataSize = ubuf.size;
859 for (const QShaderDescription::BlockVariable &member : ubuf.members) {
860 ShaderInfo::Variable v;
861 v.type = ShaderInfo::Constant;
862 v.name = member.name.toUtf8();
863 v.offset = member.offset;
864 v.size = member.size;
865 result->variables.append(v);
866 }
867 } else {
868 qWarning("Uniform block %s (binding %d) ignored", qPrintable(ubuf.blockName), ubuf.binding);
869 }
870 }
871
872 const QVector<QShaderDescription::InOutVariable> combinedImageSamplers = desc.combinedImageSamplers();
873 const int samplerCount = combinedImageSamplers.count();
874 for (int i = 0; i < samplerCount; ++i) {
875 const QShaderDescription::InOutVariable &combinedImageSampler(combinedImageSamplers[i]);
876 ShaderInfo::Variable v;
877 v.type = ShaderInfo::Sampler;
878 v.name = combinedImageSampler.name.toUtf8();
879 v.bindPoint = combinedImageSampler.binding;
880 result->variables.append(v);
881 }
882
883 return true;
884}
885
886QT_END_NAMESPACE
887