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