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