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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/quick/scenegraph/qsgrhishadereffectnode.cpp