Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgrhishadereffectnode.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
8#include <qsgmaterialshader.h>
9#include <qsgtextureprovider.h>
10#include <private/qsgplaintexture_p.h>
11#include <rhi/qshaderdescription.h>
12#include <QQmlFile>
13
14#include <QtGui/qquaternion.h>
15
16#include <QFile>
17#include <QFileSelector>
18#include <QMutexLocker>
19
21
22void QSGRhiShaderLinker::reset(const QShader &vs, const QShader &fs)
23{
24 Q_ASSERT(vs.isValid() && fs.isValid());
25 m_vs = vs;
26 m_fs = fs;
27
28 m_error = false;
29
30 //m_constantBufferSize = 0;
31 m_constants.clear();
32 m_samplers.clear();
33 m_samplerNameMap.clear();
34 m_subRectBindings.clear();
35
36 m_constants.reserve(8);
37 m_samplers.reserve(4);
38 m_samplerNameMap.reserve(4);
39}
40
41void QSGRhiShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
42{
43 Q_ASSERT(shader.shaderInfo.variables.size() == shader.varData.size());
44 static bool shaderEffectDebug = qEnvironmentVariableIntValue("QSG_RHI_SHADEREFFECT_DEBUG");
45 if (!dirtyIndices) {
46 for (int i = 0; i < shader.shaderInfo.variables.size(); ++i) {
47 const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
48 if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Constant) {
49 const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
50 Constant c;
51 c.size = var.size;
52 c.specialType = vd.specialType;
53 if (c.specialType != QSGShaderEffectNode::VariableData::SubRect) {
54 c.value = vd.value;
55 if (shaderEffectDebug) {
56 if (c.specialType == QSGShaderEffectNode::VariableData::None) {
57 qDebug() << "cbuf prepare" << shader.shaderInfo.name << var.name
58 << "offset" << var.offset << "value" << c.value;
59 } else {
60 qDebug() << "cbuf prepare" << shader.shaderInfo.name << var.name
61 << "offset" << var.offset << "special" << c.specialType;
62 }
63 }
64 } else {
65 Q_ASSERT(var.name.startsWith(QByteArrayLiteral("qt_SubRect_")));
66 c.value = var.name.mid(11);
67 }
68 m_constants[var.offset] = c;
69 }
70 }
71 } else {
72 for (int idx : *dirtyIndices) {
73 const int offset = shader.shaderInfo.variables.at(idx).offset;
74 const QVariant value = shader.varData.at(idx).value;
75 m_constants[offset].value = value;
76 if (shaderEffectDebug) {
77 qDebug() << "cbuf update" << shader.shaderInfo.name
78 << "offset" << offset << "value" << value;
79 }
80 }
81 }
82}
83
84void QSGRhiShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
85{
86 if (!dirtyIndices) {
87 for (int i = 0; i < shader.shaderInfo.variables.size(); ++i) {
88 const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
89 const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
90 if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
91 Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Source);
92
93#ifndef QT_NO_DEBUG
94 int existingBindPoint = m_samplerNameMap.value(var.name, -1);
95 Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
96#endif
97
98 m_samplers.insert(var.bindPoint, vd.value);
99 m_samplerNameMap.insert(var.name, var.bindPoint);
100 }
101 }
102 } else {
103 for (int idx : *dirtyIndices) {
104 const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(idx));
105 const QSGShaderEffectNode::VariableData &vd(shader.varData.at(idx));
106
107#ifndef QT_NO_DEBUG
108 int existingBindPoint = m_samplerNameMap.value(var.name, -1);
109 Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
110#endif
111
112 m_samplers.insert(var.bindPoint, vd.value);
113 m_samplerNameMap.insert(var.name, var.bindPoint);
114 }
115 }
116}
117
119{
120 // feedConstants stores <name> in Constant::value for subrect entries. Now
121 // that both constants and textures are known, replace the name with the
122 // texture binding point.
123 for (Constant &c : m_constants) {
124 if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
125 if (c.value.userType() == QMetaType::QByteArray) {
126 const QByteArray name = c.value.toByteArray();
127 if (!m_samplerNameMap.contains(name))
128 qWarning("ShaderEffect: qt_SubRect_%s refers to unknown source texture", name.constData());
129 const int binding = m_samplerNameMap[name];
130 c.value = binding;
131 m_subRectBindings.insert(binding);
132 }
133 }
134 }
135}
136
138{
139 if (m_error) {
140 qDebug() << "Failed to generate program data";
141 return;
142 }
143 qDebug() << "Combined shader data" << m_vs << m_fs;
144 qDebug() << " - constants" << m_constants;
145 qDebug() << " - samplers" << m_samplers;
146}
147
148QDebug operator<<(QDebug debug, const QSGRhiShaderLinker::Constant &c)
149{
150 QDebugStateSaver saver(debug);
151 debug.space();
152 debug << "size" << c.size;
153 if (c.specialType != QSGShaderEffectNode::VariableData::None)
154 debug << "special" << c.specialType;
155 else
156 debug << "value" << c.value;
157 return debug;
158}
159
161{
162 QSGMaterialType *ref(const QShader &vs, const QShader &fs);
163 void unref(const QShader &vs, const QShader &fs);
164 void reset() {
165 for (auto it = m_types.begin(), end = m_types.end(); it != end; ++it)
166 delete it->type;
167 m_types.clear();
169 }
171 qDeleteAll(m_graveyard);
172 m_graveyard.clear();
173 }
174 struct Key {
175 QShader vs;
176 QShader fs;
178 Key(const QShader &vs, const QShader &fs)
179 : vs(vs),
180 fs(fs)
181 {
182 hash = qHashMulti(0, vs, fs);
183 }
184 bool operator==(const Key &other) const {
185 return vs == other.vs && fs == other.fs;
186 }
187 };
194};
195
196size_t qHash(const QSGRhiShaderMaterialTypeCache::Key &key, size_t seed = 0)
197{
198 return seed ^ key.hash;
199}
200
202
203QSGMaterialType *QSGRhiShaderMaterialTypeCache::ref(const QShader &vs, const QShader &fs)
204{
205 QMutexLocker lock(&shaderMaterialTypeCacheMutex);
206 const Key k(vs, fs);
207 auto it = m_types.find(k);
208 if (it != m_types.end()) {
209 it->ref += 1;
210 return it->type;
211 }
212
213 auto reuseIt = m_graveyard.constFind(k);
214 if (reuseIt != m_graveyard.cend()) {
215 QSGMaterialType *t = reuseIt.value();
216 m_types.insert(k, { 1, t });
217 m_graveyard.erase(reuseIt);
218 return t;
219 }
220
221 QSGMaterialType *t = new QSGMaterialType;
222 m_types.insert(k, { 1, t });
223 return t;
224}
225
226void QSGRhiShaderMaterialTypeCache::unref(const QShader &vs, const QShader &fs)
227{
228 QMutexLocker lock(&shaderMaterialTypeCacheMutex);
229 const Key k(vs, fs);
230 auto it = m_types.find(k);
231 if (it != m_types.end()) {
232 if (!--it->ref) {
233 m_graveyard.insert(k, it->type);
234 m_types.erase(it);
235 }
236 }
237}
238
240
241void QSGRhiShaderEffectNode::resetMaterialTypeCache(void *materialTypeCacheKey)
242{
243 QMutexLocker lock(&shaderMaterialTypeCacheMutex);
244 shaderMaterialTypeCache[materialTypeCacheKey].reset();
245}
246
248{
249 QMutexLocker lock(&shaderMaterialTypeCacheMutex);
250 shaderMaterialTypeCache[materialTypeCacheKey].clearGraveyard();
251}
252
254{
255public:
257
258 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
259 void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
260 bool updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
261};
262
264{
265 setFlag(UpdatesGraphicsPipelineState, true);
266 setShader(VertexStage, material->m_vertexShader);
267 setShader(FragmentStage, material->m_fragmentShader);
268}
269
270static inline QColor qsg_premultiply_color(const QColor &c)
271{
272 float r, g, b, a;
273 c.getRgbF(&r, &g, &b, &a);
274 return QColor::fromRgbF(r * a, g * a, b * a, a);
275}
276
277template<typename T>
278static inline void fillUniformBlockMember(char *dst, const T *value, int valueCount, int fieldSizeBytes)
279{
280 const size_t valueBytes = sizeof(T) * valueCount;
281 const size_t fieldBytes = fieldSizeBytes;
282 if (valueBytes <= fieldBytes) {
283 memcpy(dst, value, valueBytes);
284 if (valueBytes < fieldBytes)
285 memset(dst + valueBytes, 0, fieldBytes - valueBytes);
286 } else {
287 memcpy(dst, value, fieldBytes);
288 }
289}
290
291bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
292{
293 Q_UNUSED(oldMaterial);
294 QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
295
296 bool changed = false;
297 QByteArray *buf = state.uniformData();
298
299 for (auto it = mat->m_linker.m_constants.constBegin(), itEnd = mat->m_linker.m_constants.constEnd(); it != itEnd; ++it) {
300 const int offset = it.key();
301 char *dst = buf->data() + offset;
302 const QSGRhiShaderLinker::Constant &c(it.value());
303 if (c.specialType == QSGShaderEffectNode::VariableData::Opacity) {
304 if (state.isOpacityDirty()) {
305 const float f = state.opacity();
306 fillUniformBlockMember<float>(dst, &f, 1, c.size);
307 changed = true;
308 }
309 } else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) {
310 if (state.isMatrixDirty()) {
311 Q_ASSERT(state.projectionMatrixCount() == mat->viewCount());
312 const int rendererViewCount = state.projectionMatrixCount();
313 const int shaderMatrixCount = c.size / 64;
314 if (shaderMatrixCount < mat->viewCount() && mat->viewCount() >= 2) {
315 qWarning("qt_Matrix uniform block member size is wrong: expected at least view_count * 64 bytes, "
316 "where view_count is %d, meaning %d bytes in total, but got only %d bytes. "
317 "This may be due to the ShaderEffect and its shaders not being multiview-capable, "
318 "or they are used with an unexpected render target. "
319 "Check if the shaders declare qt_Matrix as appropriate, "
320 "and if gl_ViewIndex is used correctly in the vertex shader.",
321 mat->viewCount(), mat->viewCount() * 64, c.size);
322 }
323 const int matrixCount = qMin(rendererViewCount, shaderMatrixCount);
324 size_t offset = 0;
325 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
326 const QMatrix4x4 m = state.combinedMatrix(viewIndex);
327 fillUniformBlockMember<float>(dst + offset, m.constData(), 16, 64);
328 offset += 64;
329 }
330 if (offset < c.size)
331 memset(dst + offset, 0, c.size - offset);
332 changed = true;
333 }
334 } else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
335 // vec4
336 QRectF subRect(0, 0, 1, 1);
337 const int binding = c.value.toInt(); // filled in by linkTextureSubRects
339 if (QSGTextureProvider *tp = mat->m_textureProviders.at(binding)) {
340 if (QSGTexture *t = tp->texture())
341 subRect = t->normalizedTextureSubRect();
342 }
343 }
344 const float f[4] = { float(subRect.x()), float(subRect.y()),
345 float(subRect.width()), float(subRect.height()) };
346 fillUniformBlockMember<float>(dst, f, 4, c.size);
347 } else if (c.specialType == QSGShaderEffectNode::VariableData::None) {
348 changed = true;
349 switch (int(c.value.userType())) {
350 case QMetaType::QColor: {
351 const QColor v = qsg_premultiply_color(qvariant_cast<QColor>(c.value)).toRgb();
352 const float f[4] = { float(v.redF()), float(v.greenF()), float(v.blueF()), float(v.alphaF()) };
353 fillUniformBlockMember<float>(dst, f, 4, c.size);
354 break;
355 }
356 case QMetaType::Float: {
357 const float f = qvariant_cast<float>(c.value);
358 fillUniformBlockMember<float>(dst, &f, 1, c.size);
359 break;
360 }
361 case QMetaType::Double: {
362 const float f = float(qvariant_cast<double>(c.value));
363 fillUniformBlockMember<float>(dst, &f, 1, c.size);
364 break;
365 }
366 case QMetaType::Int: {
367 const qint32 i = c.value.toInt();
368 fillUniformBlockMember<qint32>(dst, &i, 1, c.size);
369 break;
370 }
371 case QMetaType::Bool: {
372 const qint32 b = c.value.toBool();
373 fillUniformBlockMember<qint32>(dst, &b, 1, c.size);
374 break;
375 }
376 case QMetaType::QTransform: { // mat3
377 const QTransform v = qvariant_cast<QTransform>(c.value);
378 const float m[3][3] = {
379 { float(v.m11()), float(v.m12()), float(v.m13()) },
380 { float(v.m21()), float(v.m22()), float(v.m23()) },
381 { float(v.m31()), float(v.m32()), float(v.m33()) }
382 };
383 // stored as 4 floats per column, 1 unused
384 memset(dst, 0, c.size);
385 const size_t bytesPerColumn = 4 * sizeof(float);
386 if (c.size >= bytesPerColumn)
387 fillUniformBlockMember<float>(dst, m[0], 3, 3 * sizeof(float));
388 if (c.size >= 2 * bytesPerColumn)
389 fillUniformBlockMember<float>(dst + bytesPerColumn, m[1], 3, 3 * sizeof(float));
390 if (c.size >= 3 * bytesPerColumn)
391 fillUniformBlockMember<float>(dst + 2 * bytesPerColumn, m[2], 3, 3 * sizeof(float));
392 break;
393 }
394 case QMetaType::QSize:
395 case QMetaType::QSizeF: { // vec2
396 const QSizeF v = c.value.toSizeF();
397 const float f[2] = { float(v.width()), float(v.height()) };
398 fillUniformBlockMember<float>(dst, f, 2, c.size);
399 break;
400 }
401 case QMetaType::QPoint:
402 case QMetaType::QPointF: { // vec2
403 const QPointF v = c.value.toPointF();
404 const float f[2] = { float(v.x()), float(v.y()) };
405 fillUniformBlockMember<float>(dst, f, 2, c.size);
406 break;
407 }
408 case QMetaType::QRect:
409 case QMetaType::QRectF: { // vec4
410 const QRectF v = c.value.toRectF();
411 const float f[4] = { float(v.x()), float(v.y()), float(v.width()), float(v.height()) };
412 fillUniformBlockMember<float>(dst, f, 4, c.size);
413 break;
414 }
415 case QMetaType::QVector2D: { // vec2
416 const QVector2D v = qvariant_cast<QVector2D>(c.value);
417 const float f[2] = { float(v.x()), float(v.y()) };
418 fillUniformBlockMember<float>(dst, f, 2, c.size);
419 break;
420 }
421 case QMetaType::QVector3D: { // vec3
422 const QVector3D v = qvariant_cast<QVector3D>(c.value);
423 const float f[3] = { float(v.x()), float(v.y()), float(v.z()) };
424 fillUniformBlockMember<float>(dst, f, 3, c.size);
425 break;
426 }
427 case QMetaType::QVector4D: { // vec4
428 const QVector4D v = qvariant_cast<QVector4D>(c.value);
429 const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.w()) };
430 fillUniformBlockMember<float>(dst, f, 4, c.size);
431 break;
432 }
433 case QMetaType::QQuaternion: { // vec4
434 const QQuaternion v = qvariant_cast<QQuaternion>(c.value);
435 const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.scalar()) };
436 fillUniformBlockMember<float>(dst, f, 4, c.size);
437 break;
438 }
439 case QMetaType::QMatrix4x4: { // mat4
440 const QMatrix4x4 m = qvariant_cast<QMatrix4x4>(c.value);
441 fillUniformBlockMember<float>(dst, m.constData(), 16, c.size);
442 break;
443 }
444 default:
445 break;
446 }
447 }
448 }
449
450 return changed;
451}
452
453void QSGRhiShaderEffectMaterialShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
454 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
455{
456 Q_UNUSED(oldMaterial);
457 QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
458
460 return;
461
462 QSGTextureProvider *tp = mat->m_textureProviders.at(binding);
463 if (tp) {
464 if (QSGTexture *t = tp->texture()) {
465 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
466
467 if (t->isAtlasTexture() && !mat->m_geometryUsesTextureSubRect && !mat->usesSubRectUniform(binding)) {
468 // Why the hassle with the batch: while removedFromAtlas() is
469 // able to operate with its own resource update batch (which is
470 // then committed immediately), that approach is wrong when the
471 // atlas enqueued (in the updateRhiTexture() above) not yet
472 // committed operations to state.resourceUpdateBatch()... The
473 // only safe way then is to use the same batch the atlas'
474 // updateRhiTexture() used.
475 QSGTexture *newTexture = t->removedFromAtlas(state.resourceUpdateBatch());
476 if (newTexture) {
477 t = newTexture;
478 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
479 }
480 }
481 *texture = t;
482 return;
483 }
484 }
485
486 if (!mat->m_dummyTexture) {
487 mat->m_dummyTexture = new QSGPlainTexture;
488 mat->m_dummyTexture->setFiltering(QSGTexture::Nearest);
489 mat->m_dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
490 mat->m_dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
491 QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
492 img.fill(0);
493 mat->m_dummyTexture->setImage(img);
494 mat->m_dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
495 }
496 *texture = mat->m_dummyTexture;
497}
498
499bool QSGRhiShaderEffectMaterialShader::updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps,
500 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
501{
502 Q_UNUSED(state);
503 Q_UNUSED(oldMaterial);
504 QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
505
506 switch (mat->m_cullMode) {
507 case QSGShaderEffectNode::FrontFaceCulling:
508 ps->cullMode = GraphicsPipelineState::CullFront;
509 return true;
510 case QSGShaderEffectNode::BackFaceCulling:
511 ps->cullMode = GraphicsPipelineState::CullBack;
512 return true;
513 default:
514 return false;
515 }
516}
517
519 : m_node(node)
520{
521 setFlag(Blending | RequiresFullMatrix, true); // may be changed in syncMaterial()
522}
523
525{
526 if (m_materialType && m_materialTypeCacheKey)
527 shaderMaterialTypeCache[m_materialTypeCacheKey].unref(m_vertexShader, m_fragmentShader);
528
529 delete m_dummyTexture;
530}
531
532static bool hasAtlasTexture(const QList<QSGTextureProvider *> &textureProviders)
533{
534 for (QSGTextureProvider *tp : textureProviders) {
535 if (tp && tp->texture() && tp->texture()->isAtlasTexture())
536 return true;
537 }
538 return false;
539}
540
541int QSGRhiShaderEffectMaterial::compare(const QSGMaterial *other) const
542{
543 Q_ASSERT(other && type() == other->type());
544 const QSGRhiShaderEffectMaterial *o = static_cast<const QSGRhiShaderEffectMaterial *>(other);
545
546 if (int diff = m_cullMode - o->m_cullMode)
547 return diff;
548
549 if (int diff = m_textureProviders.size() - o->m_textureProviders.size())
550 return diff;
551
552 if (m_linker.m_constants != o->m_linker.m_constants)
553 return 1;
554
555 if (hasAtlasTexture(m_textureProviders) && !m_geometryUsesTextureSubRect)
556 return -1;
557
558 if (hasAtlasTexture(o->m_textureProviders) && !o->m_geometryUsesTextureSubRect)
559 return 1;
560
561 for (int binding = 0, count = m_textureProviders.size(); binding != count; ++binding) {
562 QSGTextureProvider *tp1 = m_textureProviders.at(binding);
563 QSGTextureProvider *tp2 = o->m_textureProviders.at(binding);
564 if (tp1 && tp2) {
565 QSGTexture *t1 = tp1->texture();
566 QSGTexture *t2 = tp2->texture();
567 if (t1 && t2) {
568 const qint64 diff = t1->comparisonKey() - t2->comparisonKey();
569 if (diff != 0)
570 return diff < 0 ? -1 : 1;
571 } else {
572 if (!t1 && t2)
573 return -1;
574 if (t1 && !t2)
575 return 1;
576 }
577 } else {
578 if (!tp1 && tp2)
579 return -1;
580 if (tp1 && !tp2)
581 return 1;
582 }
583 }
584
585 return 0;
586}
587
589{
590 return m_materialType;
591}
592
593QSGMaterialShader *QSGRhiShaderEffectMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
594{
595 Q_UNUSED(renderMode);
597}
598
600{
601 if (layoutChange) {
602 for (QSGTextureProvider *tp : m_textureProviders) {
603 if (tp) {
604 QObject::disconnect(tp, SIGNAL(textureChanged()), m_node,
605 SLOT(handleTextureChange()));
606 QObject::disconnect(tp, SIGNAL(destroyed(QObject*)), m_node,
607 SLOT(handleTextureProviderDestroyed(QObject*)));
608 }
609 }
610 m_textureProviders.fill(nullptr, MAX_BINDINGS);
611 }
612
613 for (auto it = m_linker.m_samplers.constBegin(), itEnd = m_linker.m_samplers.constEnd(); it != itEnd; ++it) {
614 const int binding = it.key();
615 QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(it.value()));
616 QSGTextureProvider *newProvider = source && source->isTextureProvider() ? source->textureProvider() : nullptr;
617 if (binding >= MAX_BINDINGS) {
618 qWarning("Sampler at binding %d exceeds the available ShaderEffect binding slots; ignored",
619 binding);
620 continue;
621 }
622 QSGTextureProvider *&activeProvider(m_textureProviders[binding]);
623 if (newProvider != activeProvider) {
624 if (activeProvider) {
625 QObject::disconnect(activeProvider, SIGNAL(textureChanged()), m_node,
626 SLOT(handleTextureChange()));
627 QObject::disconnect(activeProvider, SIGNAL(destroyed(QObject*)), m_node,
628 SLOT(handleTextureProviderDestroyed(QObject*)));
629 }
630 if (newProvider) {
631 Q_ASSERT_X(newProvider->thread() == QThread::currentThread(),
632 "QSGRhiShaderEffectMaterial::updateTextureProviders",
633 "Texture provider must belong to the rendering thread");
634 QObject::connect(newProvider, SIGNAL(textureChanged()), m_node, SLOT(handleTextureChange()));
635 QObject::connect(newProvider, SIGNAL(destroyed(QObject*)), m_node,
636 SLOT(handleTextureProviderDestroyed(QObject*)));
637 } else {
638 const char *typeName = source ? source->metaObject()->className() : it.value().typeName();
639 qWarning("ShaderEffect: Texture t%d is not assigned a valid texture provider (%s).",
640 binding, typeName);
641 }
642 activeProvider = newProvider;
643 }
644 }
645}
646
647QSGRhiShaderEffectNode::QSGRhiShaderEffectNode(QSGDefaultRenderContext *rc)
648 : m_material(this)
649{
650 Q_UNUSED(rc);
651 setFlag(UsePreprocess, true);
652 setMaterial(&m_material);
653}
654
656{
657 QRectF srcRect(0, 0, 1, 1);
658 bool geometryUsesTextureSubRect = false;
659 if (supportsAtlasTextures) {
660 QSGTextureProvider *tp = nullptr;
661 for (int binding = 0, count = m_material.m_textureProviders.size(); binding != count; ++binding) {
662 if (QSGTextureProvider *candidate = m_material.m_textureProviders.at(binding)) {
663 if (!tp) {
664 tp = candidate;
665 } else { // there can only be one...
666 tp = nullptr;
667 break;
668 }
669 }
670 }
671 if (tp && tp->texture()) {
672 srcRect = tp->texture()->normalizedTextureSubRect();
673 geometryUsesTextureSubRect = true;
674 }
675 }
676
677 if (m_material.m_geometryUsesTextureSubRect != geometryUsesTextureSubRect) {
678 m_material.m_geometryUsesTextureSubRect = geometryUsesTextureSubRect;
679 markDirty(QSGNode::DirtyMaterial);
680 }
681
682 return srcRect;
683}
684
685static QShader loadShaderFromFile(const QString &filename)
686{
687 QFile f(filename);
688 if (!f.open(QIODevice::ReadOnly)) {
689 qWarning() << "Failed to find shader" << filename;
690 return QShader();
691 }
692 return QShader::fromSerialized(f.readAll());
693}
694
703
704QSGRhiShaderEffectDefaultShader QSGRhiShaderEffectDefaultShader::create(const QString &filename, int viewCount)
705{
707 s.shader = loadShaderFromFile(filename);
708 const QList<QShaderDescription::BlockVariable> uboMembers = s.shader.description().uniformBlocks().constFirst().members;
709 for (const auto &member: uboMembers) {
710 if (member.name == QByteArrayLiteral("qt_Matrix"))
711 s.matrixArrayByteSize = member.size;
712 else if (member.name == QByteArrayLiteral("qt_Opacity"))
713 s.opacityOffset = member.offset;
714 }
715 s.viewCount = viewCount;
716 return s;
717}
718
719void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
720{
721 static const int defaultVertexShaderCount = 2;
722 static QSGRhiShaderEffectDefaultShader defaultVertexShaders[defaultVertexShaderCount] = {
723 QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"), 1),
724 QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb.mv2qsb"), 2)
725 };
726 static const int defaultFragmentShaderCount = 2;
727 static QSGRhiShaderEffectDefaultShader defaultFragmentShaders[defaultFragmentShaderCount] = {
728 QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"), 1),
729 QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb.mv2qsb"), 2)
730 };
731
732 if (bool(m_material.flags() & QSGMaterial::Blending) != syncData->blending) {
733 m_material.setFlag(QSGMaterial::Blending, syncData->blending);
734 markDirty(QSGNode::DirtyMaterial);
735 }
736
737 if (m_material.m_cullMode != syncData->cullMode) {
738 m_material.m_cullMode = syncData->cullMode;
739 markDirty(QSGNode::DirtyMaterial);
740 }
741
742 if (syncData->dirty & QSGShaderEffectNode::DirtyShaders) {
743 if (m_material.m_materialType) {
744 shaderMaterialTypeCache[m_material.m_materialTypeCacheKey].unref(m_material.m_vertexShader,
745 m_material.m_fragmentShader);
746 }
747
748 m_material.m_hasCustomVertexShader = syncData->vertex.shader->hasShaderCode;
749 quint32 defaultMatrixArrayByteSize = 0;
750 if (m_material.m_hasCustomVertexShader) {
751 m_material.m_vertexShader = syncData->vertex.shader->shaderInfo.rhiShader;
752 } else {
753 bool found = false;
754 for (int i = 0; i < defaultVertexShaderCount; ++i) {
755 if (defaultVertexShaders[i].viewCount == syncData->viewCount) {
756 m_material.m_vertexShader = defaultVertexShaders[i].shader;
757 defaultMatrixArrayByteSize = defaultVertexShaders[i].matrixArrayByteSize;
758 found = true;
759 break;
760 }
761 }
762 if (!found) {
763 qWarning("No default vertex shader found for view count %d", syncData->viewCount);
764 m_material.m_vertexShader = defaultVertexShaders[0].shader;
765 defaultMatrixArrayByteSize = 64;
766 }
767 }
768
769 m_material.m_hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode;
770 quint32 defaultOpacityOffset = 0;
771 if (m_material.m_hasCustomFragmentShader) {
772 m_material.m_fragmentShader = syncData->fragment.shader->shaderInfo.rhiShader;
773 } else {
774 bool found = false;
775 for (int i = 0; i < defaultFragmentShaderCount; ++i) {
776 if (defaultFragmentShaders[i].viewCount == syncData->viewCount) {
777 m_material.m_fragmentShader = defaultFragmentShaders[i].shader;
778 defaultOpacityOffset = defaultFragmentShaders[i].opacityOffset;
779 found = true;
780 break;
781 }
782 }
783 if (!found) {
784 qWarning("No default fragment shader found for view count %d", syncData->viewCount);
785 m_material.m_fragmentShader = defaultFragmentShaders[0].shader;
786 defaultOpacityOffset = 64;
787 }
788 }
789
790 m_material.m_materialType = shaderMaterialTypeCache[syncData->materialTypeCacheKey].ref(m_material.m_vertexShader,
791 m_material.m_fragmentShader);
792 m_material.m_materialTypeCacheKey = syncData->materialTypeCacheKey;
793
794 m_material.m_linker.reset(m_material.m_vertexShader, m_material.m_fragmentShader);
795
796 if (m_material.m_hasCustomVertexShader) {
797 m_material.m_linker.feedConstants(*syncData->vertex.shader);
798 m_material.m_linker.feedSamplers(*syncData->vertex.shader);
799 } else {
800 QSGShaderEffectNode::ShaderData defaultSD;
801 defaultSD.shaderInfo.name = QLatin1String("Default ShaderEffect vertex shader");
802 defaultSD.shaderInfo.rhiShader = m_material.m_vertexShader;
803 defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex;
804
805 // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the matrix is used
806 QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
807 v.name = QByteArrayLiteral("qt_Matrix");
808 v.offset = 0;
809 v.size = defaultMatrixArrayByteSize;
810 defaultSD.shaderInfo.variables.append(v);
811 QSGShaderEffectNode::VariableData vd;
812 vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
813 defaultSD.varData.append(vd);
814 m_material.m_linker.feedConstants(defaultSD);
815 }
816
817 if (m_material.m_hasCustomFragmentShader) {
818 m_material.m_linker.feedConstants(*syncData->fragment.shader);
819 m_material.m_linker.feedSamplers(*syncData->fragment.shader);
820 } else {
821 QSGShaderEffectNode::ShaderData defaultSD;
822 defaultSD.shaderInfo.name = QLatin1String("Default ShaderEffect fragment shader");
823 defaultSD.shaderInfo.rhiShader = m_material.m_fragmentShader;
824 defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
825
826 // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the opacity is used
827 QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
828 v.name = QByteArrayLiteral("qt_Opacity");
829 v.offset = defaultOpacityOffset;
830 v.size = sizeof(float);
831 defaultSD.shaderInfo.variables.append(v);
832 QSGShaderEffectNode::VariableData vd;
833 vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
834 defaultSD.varData.append(vd);
835
836 v.name = QByteArrayLiteral("source");
837 v.bindPoint = 1;
838 v.type = QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
839 defaultSD.shaderInfo.variables.append(v);
840 for (const QSGShaderEffectNode::VariableData &extVarData : std::as_const(syncData->fragment.shader->varData)) {
841 if (extVarData.specialType == QSGShaderEffectNode::VariableData::Source) {
842 vd.value = extVarData.value;
843 break;
844 }
845 }
846 vd.specialType = QSGShaderEffectNode::VariableData::Source;
847 defaultSD.varData.append(vd);
848
849 m_material.m_linker.feedConstants(defaultSD);
850 m_material.m_linker.feedSamplers(defaultSD);
851 }
852
853 m_material.m_linker.linkTextureSubRects();
854 m_material.updateTextureProviders(true);
855 markDirty(QSGNode::DirtyMaterial);
856
857 } else {
858
859 if (syncData->dirty & QSGShaderEffectNode::DirtyShaderConstant) {
860 if (!syncData->vertex.dirtyConstants->isEmpty())
861 m_material.m_linker.feedConstants(*syncData->vertex.shader, syncData->vertex.dirtyConstants);
862 if (!syncData->fragment.dirtyConstants->isEmpty())
863 m_material.m_linker.feedConstants(*syncData->fragment.shader, syncData->fragment.dirtyConstants);
864 markDirty(QSGNode::DirtyMaterial);
865 }
866
867 if (syncData->dirty & QSGShaderEffectNode::DirtyShaderTexture) {
868 if (!syncData->vertex.dirtyTextures->isEmpty())
869 m_material.m_linker.feedSamplers(*syncData->vertex.shader, syncData->vertex.dirtyTextures);
870 if (!syncData->fragment.dirtyTextures->isEmpty())
871 m_material.m_linker.feedSamplers(*syncData->fragment.shader, syncData->fragment.dirtyTextures);
872 m_material.m_linker.linkTextureSubRects();
873 m_material.updateTextureProviders(false);
874 markDirty(QSGNode::DirtyMaterial);
875 }
876 }
877
878 if (bool(m_material.flags() & QSGMaterial::RequiresFullMatrix) != m_material.m_hasCustomVertexShader) {
879 m_material.setFlag(QSGMaterial::RequiresFullMatrix, m_material.m_hasCustomVertexShader);
880 markDirty(QSGNode::DirtyMaterial);
881 }
882}
883
884void QSGRhiShaderEffectNode::handleTextureChange()
885{
886 markDirty(QSGNode::DirtyMaterial);
887 emit textureChanged();
888}
889
890void QSGRhiShaderEffectNode::handleTextureProviderDestroyed(QObject *object)
891{
892 for (QSGTextureProvider *&tp : m_material.m_textureProviders) {
893 if (tp == object)
894 tp = nullptr;
895 }
896}
897
899{
900 for (QSGTextureProvider *tp : m_material.m_textureProviders) {
901 if (tp) {
902 if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(tp->texture()))
903 texture->updateTexture();
904 }
905 }
906}
907
909{
910 return false; // because SPIR-V and QRhi make it look so, regardless of the underlying API
911}
912
914{
915 return QString();
916}
917
922
923void QSGRhiGuiThreadShaderEffectManager::prepareShaderCode(ShaderInfo::Type typeHint, const QUrl &src, ShaderInfo *result)
924{
925 if (!src.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || src.isLocalFile()) {
926 if (!m_fileSelector) {
927 m_fileSelector = new QFileSelector(this);
928 m_fileSelector->setExtraSelectors(QStringList() << QStringLiteral("qsb"));
929 }
930 const QString fn = m_fileSelector->select(QQmlFile::urlToLocalFileOrQrc(src));
931 const QShader s = loadShaderFromFile(fn);
932 if (!s.isValid()) {
933 qWarning("ShaderEffect: Failed to deserialize QShader from %s. "
934 "Either the filename is incorrect, or it is not a valid .qsb file. "
935 "In Qt 6 shaders must be preprocessed using the Qt Shader Tools infrastructure. "
936 "The vertexShader and fragmentShader properties are now URLs that are expected to point to .qsb files generated by the qsb tool. "
937 "See https://doc.qt.io/qt-6/qtshadertools-index.html for more information.",
938 qPrintable(fn));
939 m_status = Error;
940 emit shaderCodePrepared(false, typeHint, src, result);
941 emit logAndStatusChanged();
942 return;
943 }
944 result->name = fn;
945 result->rhiShader = s;
946 const bool ok = reflect(result);
947 m_status = ok ? Compiled : Error;
948 emit shaderCodePrepared(ok, typeHint, src, result);
949 emit logAndStatusChanged();
950 } else {
951 qWarning("rhi shader effect only supports files (qrc or local) at the moment");
952 emit shaderCodePrepared(false, typeHint, src, result);
953 }
954}
955
956bool QSGRhiGuiThreadShaderEffectManager::reflect(ShaderInfo *result)
957{
958 switch (result->rhiShader.stage()) {
959 case QShader::VertexStage:
960 result->type = ShaderInfo::TypeVertex;
961 break;
962 case QShader::FragmentStage:
963 result->type = ShaderInfo::TypeFragment;
964 break;
965 default:
966 result->type = ShaderInfo::TypeOther;
967 qWarning("Unsupported shader stage (%d)", result->rhiShader.stage());
968 return false;
969 }
970
971 const QShaderDescription desc = result->rhiShader.description();
972
973 int ubufBinding = -1;
974 const QList<QShaderDescription::UniformBlock> ubufs = desc.uniformBlocks();
975 const int ubufCount = ubufs.size();
976 for (int i = 0; i < ubufCount; ++i) {
977 const QShaderDescription::UniformBlock &ubuf(ubufs[i]);
978 if (ubufBinding == -1 && ubuf.binding >= 0) {
979 ubufBinding = ubuf.binding;
980 for (const QShaderDescription::BlockVariable &member : ubuf.members) {
981 ShaderInfo::Variable v;
982 v.type = ShaderInfo::Constant;
983 v.name = member.name;
984 v.offset = member.offset;
985 v.size = member.size;
986 result->variables.append(v);
987 }
988 } else {
989 qWarning("Uniform block %s (binding %d) ignored", ubuf.blockName.constData(),
990 ubuf.binding);
991 }
992 }
993
994 const QList<QShaderDescription::InOutVariable> combinedImageSamplers = desc.combinedImageSamplers();
995 const int samplerCount = combinedImageSamplers.size();
996 for (int i = 0; i < samplerCount; ++i) {
997 const QShaderDescription::InOutVariable &combinedImageSampler(combinedImageSamplers[i]);
998 ShaderInfo::Variable v;
999 v.type = ShaderInfo::Sampler;
1000 v.name = combinedImageSampler.name;
1001 v.bindPoint = combinedImageSampler.binding;
1002 result->variables.append(v);
1003 }
1004
1005 return true;
1006}
1007
1008QT_END_NAMESPACE
1009
1010#include "moc_qsgrhishadereffectnode_p.cpp"
void prepareShaderCode(ShaderInfo::Type typeHint, const QUrl &src, ShaderInfo *result) override
bool hasSeparateSamplerAndTextureObjects() const override
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to prepare use of sampled images in the shader,...
QSGRhiShaderEffectMaterialShader(const QSGRhiShaderEffectMaterial *material)
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to get the contents of the shader program's uniform buffer...
bool updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to enable the material to provide a custom set of graphics...
QSGMaterialType * type() const override
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
QSGRhiShaderEffectMaterial(QSGRhiShaderEffectNode *node)
bool usesSubRectUniform(int binding) const
QSGMaterialShader * createShader(QSGRendererInterface::RenderMode renderMode) const override
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
QSGRhiShaderEffectNode * m_node
void updateTextureProviders(bool layoutChange)
void preprocess() override
Override this function to do processing on the node before it is rendered.
static void garbageCollectMaterialTypeCache(void *materialTypeCacheKey)
QRectF updateNormalizedTextureSubRect(bool supportsAtlasTextures) override
void syncMaterial(SyncData *syncData) override
static void resetMaterialTypeCache(void *materialTypeCacheKey)
void feedSamplers(const QSGShaderEffectNode::ShaderData &shader, const QSet< int > *dirtyIndices=nullptr)
void feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet< int > *dirtyIndices=nullptr)
Combined button and popup list for selecting options.
static void fillUniformBlockMember(char *dst, const T *value, int valueCount, int fieldSizeBytes)
static QShader loadShaderFromFile(const QString &filename)
size_t qHash(const QSGRhiShaderMaterialTypeCache::Key &key, size_t seed=0)
static QHash< void *, QSGRhiShaderMaterialTypeCache > shaderMaterialTypeCache
static QColor qsg_premultiply_color(const QColor &c)
static QMutex shaderMaterialTypeCacheMutex
QDebug operator<<(QDebug debug, const QSGRhiShaderLinker::Constant &c)
static bool hasAtlasTexture(const QList< QSGTextureProvider * > &textureProviders)
static QSGRhiShaderEffectDefaultShader create(const QString &filename, int viewCount)
Key(const QShader &vs, const QShader &fs)
void unref(const QShader &vs, const QShader &fs)
QSGMaterialType * ref(const QShader &vs, const QShader &fs)
QHash< Key, QSGMaterialType * > m_graveyard