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

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