aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgrhishadereffectnode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph/qsgrhishadereffectnode.cpp')
-rw-r--r--src/quick/scenegraph/qsgrhishadereffectnode.cpp1007
1 files changed, 1007 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgrhishadereffectnode.cpp b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
new file mode 100644
index 0000000000..c3b7b42cb9
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
@@ -0,0 +1,1007 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgrhishadereffectnode_p.h"
+#include "qsgdefaultrendercontext_p.h"
+#include "qsgrhisupport_p.h"
+#include <qsgmaterialshader.h>
+#include <qsgtextureprovider.h>
+#include <private/qsgplaintexture_p.h>
+#include <rhi/qshaderdescription.h>
+#include <QQmlFile>
+#include <QFile>
+#include <QFileSelector>
+#include <QMutexLocker>
+
+QT_BEGIN_NAMESPACE
+
+void QSGRhiShaderLinker::reset(const QShader &vs, const QShader &fs)
+{
+ Q_ASSERT(vs.isValid() && fs.isValid());
+ m_vs = vs;
+ m_fs = fs;
+
+ m_error = false;
+
+ //m_constantBufferSize = 0;
+ m_constants.clear();
+ m_samplers.clear();
+ m_samplerNameMap.clear();
+ m_subRectBindings.clear();
+
+ m_constants.reserve(8);
+ m_samplers.reserve(4);
+ m_samplerNameMap.reserve(4);
+}
+
+void QSGRhiShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
+{
+ Q_ASSERT(shader.shaderInfo.variables.size() == shader.varData.size());
+ static bool shaderEffectDebug = qEnvironmentVariableIntValue("QSG_RHI_SHADEREFFECT_DEBUG");
+ if (!dirtyIndices) {
+ for (int i = 0; i < shader.shaderInfo.variables.size(); ++i) {
+ const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
+ if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Constant) {
+ const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
+ Constant c;
+ c.size = var.size;
+ c.specialType = vd.specialType;
+ if (c.specialType != QSGShaderEffectNode::VariableData::SubRect) {
+ c.value = vd.value;
+ if (shaderEffectDebug) {
+ if (c.specialType == QSGShaderEffectNode::VariableData::None) {
+ qDebug() << "cbuf prepare" << shader.shaderInfo.name << var.name
+ << "offset" << var.offset << "value" << c.value;
+ } else {
+ qDebug() << "cbuf prepare" << shader.shaderInfo.name << var.name
+ << "offset" << var.offset << "special" << c.specialType;
+ }
+ }
+ } else {
+ Q_ASSERT(var.name.startsWith(QByteArrayLiteral("qt_SubRect_")));
+ c.value = var.name.mid(11);
+ }
+ m_constants[var.offset] = c;
+ }
+ }
+ } else {
+ for (int idx : *dirtyIndices) {
+ const int offset = shader.shaderInfo.variables.at(idx).offset;
+ const QVariant value = shader.varData.at(idx).value;
+ m_constants[offset].value = value;
+ if (shaderEffectDebug) {
+ qDebug() << "cbuf update" << shader.shaderInfo.name
+ << "offset" << offset << "value" << value;
+ }
+ }
+ }
+}
+
+void QSGRhiShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
+{
+ if (!dirtyIndices) {
+ for (int i = 0; i < shader.shaderInfo.variables.size(); ++i) {
+ const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
+ const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
+ if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
+ Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Source);
+
+#ifndef QT_NO_DEBUG
+ int existingBindPoint = m_samplerNameMap.value(var.name, -1);
+ Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
+#endif
+
+ m_samplers.insert(var.bindPoint, vd.value);
+ m_samplerNameMap.insert(var.name, var.bindPoint);
+ }
+ }
+ } else {
+ for (int idx : *dirtyIndices) {
+ const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(idx));
+ const QSGShaderEffectNode::VariableData &vd(shader.varData.at(idx));
+
+#ifndef QT_NO_DEBUG
+ int existingBindPoint = m_samplerNameMap.value(var.name, -1);
+ Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
+#endif
+
+ m_samplers.insert(var.bindPoint, vd.value);
+ m_samplerNameMap.insert(var.name, var.bindPoint);
+ }
+ }
+}
+
+void QSGRhiShaderLinker::linkTextureSubRects()
+{
+ // feedConstants stores <name> in Constant::value for subrect entries. Now
+ // that both constants and textures are known, replace the name with the
+ // texture binding point.
+ for (Constant &c : m_constants) {
+ if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
+ if (c.value.userType() == QMetaType::QByteArray) {
+ const QByteArray name = c.value.toByteArray();
+ if (!m_samplerNameMap.contains(name))
+ qWarning("ShaderEffect: qt_SubRect_%s refers to unknown source texture", name.constData());
+ const int binding = m_samplerNameMap[name];
+ c.value = binding;
+ m_subRectBindings.insert(binding);
+ }
+ }
+ }
+}
+
+void QSGRhiShaderLinker::dump()
+{
+ if (m_error) {
+ qDebug() << "Failed to generate program data";
+ return;
+ }
+ qDebug() << "Combined shader data" << m_vs << m_fs;
+ qDebug() << " - constants" << m_constants;
+ qDebug() << " - samplers" << m_samplers;
+}
+
+QDebug operator<<(QDebug debug, const QSGRhiShaderLinker::Constant &c)
+{
+ QDebugStateSaver saver(debug);
+ debug.space();
+ debug << "size" << c.size;
+ if (c.specialType != QSGShaderEffectNode::VariableData::None)
+ debug << "special" << c.specialType;
+ else
+ debug << "value" << c.value;
+ return debug;
+}
+
+struct QSGRhiShaderMaterialTypeCache
+{
+ QSGMaterialType *ref(const QShader &vs, const QShader &fs);
+ void unref(const QShader &vs, const QShader &fs);
+ void reset() {
+ for (auto it = m_types.begin(), end = m_types.end(); it != end; ++it)
+ delete it->type;
+ m_types.clear();
+ clearGraveyard();
+ }
+ void clearGraveyard() {
+ qDeleteAll(m_graveyard);
+ m_graveyard.clear();
+ }
+ struct Key {
+ QShader vs;
+ QShader fs;
+ size_t hash;
+ Key(const QShader &vs, const QShader &fs)
+ : vs(vs),
+ fs(fs)
+ {
+ QtPrivate::QHashCombine hashGen;
+ hash = hashGen(hashGen(0, vs), fs);
+ }
+ bool operator==(const Key &other) const {
+ return vs == other.vs && fs == other.fs;
+ }
+ };
+ struct MaterialType {
+ int ref;
+ QSGMaterialType *type;
+ };
+ QHash<Key, MaterialType> m_types;
+ QHash<Key, QSGMaterialType *> m_graveyard;
+};
+
+size_t qHash(const QSGRhiShaderMaterialTypeCache::Key &key, size_t seed = 0)
+{
+ return seed ^ key.hash;
+}
+
+static QMutex shaderMaterialTypeCacheMutex;
+
+QSGMaterialType *QSGRhiShaderMaterialTypeCache::ref(const QShader &vs, const QShader &fs)
+{
+ QMutexLocker lock(&shaderMaterialTypeCacheMutex);
+ const Key k(vs, fs);
+ auto it = m_types.find(k);
+ if (it != m_types.end()) {
+ it->ref += 1;
+ return it->type;
+ }
+
+ auto reuseIt = m_graveyard.constFind(k);
+ if (reuseIt != m_graveyard.cend()) {
+ QSGMaterialType *t = reuseIt.value();
+ m_types.insert(k, { 1, t });
+ m_graveyard.erase(reuseIt);
+ return t;
+ }
+
+ QSGMaterialType *t = new QSGMaterialType;
+ m_types.insert(k, { 1, t });
+ return t;
+}
+
+void QSGRhiShaderMaterialTypeCache::unref(const QShader &vs, const QShader &fs)
+{
+ QMutexLocker lock(&shaderMaterialTypeCacheMutex);
+ const Key k(vs, fs);
+ auto it = m_types.find(k);
+ if (it != m_types.end()) {
+ if (!--it->ref) {
+ m_graveyard.insert(k, it->type);
+ m_types.erase(it);
+ }
+ }
+}
+
+static QHash<void *, QSGRhiShaderMaterialTypeCache> shaderMaterialTypeCache;
+
+void QSGRhiShaderEffectNode::resetMaterialTypeCache(void *materialTypeCacheKey)
+{
+ QMutexLocker lock(&shaderMaterialTypeCacheMutex);
+ shaderMaterialTypeCache[materialTypeCacheKey].reset();
+}
+
+void QSGRhiShaderEffectNode::garbageCollectMaterialTypeCache(void *materialTypeCacheKey)
+{
+ QMutexLocker lock(&shaderMaterialTypeCacheMutex);
+ shaderMaterialTypeCache[materialTypeCacheKey].clearGraveyard();
+}
+
+class QSGRhiShaderEffectMaterialShader : public QSGMaterialShader
+{
+public:
+ QSGRhiShaderEffectMaterialShader(const QSGRhiShaderEffectMaterial *material);
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+ void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+ bool updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+};
+
+QSGRhiShaderEffectMaterialShader::QSGRhiShaderEffectMaterialShader(const QSGRhiShaderEffectMaterial *material)
+{
+ setFlag(UpdatesGraphicsPipelineState, true);
+ setShader(VertexStage, material->m_vertexShader);
+ setShader(FragmentStage, material->m_fragmentShader);
+}
+
+static inline QColor qsg_premultiply_color(const QColor &c)
+{
+ float r, g, b, a;
+ c.getRgbF(&r, &g, &b, &a);
+ return QColor::fromRgbF(r * a, g * a, b * a, a);
+}
+
+template<typename T>
+static inline void fillUniformBlockMember(char *dst, const T *value, int valueCount, int fieldSizeBytes)
+{
+ const size_t valueBytes = sizeof(T) * valueCount;
+ const size_t fieldBytes = fieldSizeBytes;
+ if (valueBytes <= fieldBytes) {
+ memcpy(dst, value, valueBytes);
+ if (valueBytes < fieldBytes)
+ memset(dst + valueBytes, 0, fieldBytes - valueBytes);
+ } else {
+ memcpy(dst, value, fieldBytes);
+ }
+}
+
+bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+ Q_UNUSED(oldMaterial);
+ QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
+
+ bool changed = false;
+ QByteArray *buf = state.uniformData();
+
+ for (auto it = mat->m_linker.m_constants.constBegin(), itEnd = mat->m_linker.m_constants.constEnd(); it != itEnd; ++it) {
+ const int offset = it.key();
+ char *dst = buf->data() + offset;
+ const QSGRhiShaderLinker::Constant &c(it.value());
+ if (c.specialType == QSGShaderEffectNode::VariableData::Opacity) {
+ if (state.isOpacityDirty()) {
+ const float f = state.opacity();
+ fillUniformBlockMember<float>(dst, &f, 1, c.size);
+ changed = true;
+ }
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) {
+ if (state.isMatrixDirty()) {
+ Q_ASSERT(state.projectionMatrixCount() == mat->viewCount());
+ const int rendererViewCount = state.projectionMatrixCount();
+ const int shaderMatrixCount = c.size / 64;
+ if (shaderMatrixCount < mat->viewCount() && mat->viewCount() >= 2) {
+ qWarning("qt_Matrix uniform block member size is wrong: expected at least view_count * 64 bytes, "
+ "where view_count is %d, meaning %d bytes in total, but got only %d bytes. "
+ "This may be due to the ShaderEffect and its shaders not being multiview-capable, "
+ "or they are used with an unexpected render target. "
+ "Check if the shaders declare qt_Matrix as appropriate, "
+ "and if gl_ViewIndex is used correctly in the vertex shader.",
+ mat->viewCount(), mat->viewCount() * 64, c.size);
+ }
+ const int matrixCount = qMin(rendererViewCount, shaderMatrixCount);
+ size_t offset = 0;
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ fillUniformBlockMember<float>(dst + offset, m.constData(), 16, 64);
+ offset += 64;
+ }
+ if (offset < c.size)
+ memset(dst + offset, 0, c.size - offset);
+ changed = true;
+ }
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
+ // vec4
+ QRectF subRect(0, 0, 1, 1);
+ const int binding = c.value.toInt(); // filled in by linkTextureSubRects
+ if (binding < QSGRhiShaderEffectMaterial::MAX_BINDINGS) {
+ if (QSGTextureProvider *tp = mat->m_textureProviders.at(binding)) {
+ if (QSGTexture *t = tp->texture())
+ subRect = t->normalizedTextureSubRect();
+ }
+ }
+ const float f[4] = { float(subRect.x()), float(subRect.y()),
+ float(subRect.width()), float(subRect.height()) };
+ fillUniformBlockMember<float>(dst, f, 4, c.size);
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::None) {
+ changed = true;
+ switch (int(c.value.userType())) {
+ case QMetaType::QColor: {
+ const QColor v = qsg_premultiply_color(qvariant_cast<QColor>(c.value)).toRgb();
+ const float f[4] = { float(v.redF()), float(v.greenF()), float(v.blueF()), float(v.alphaF()) };
+ fillUniformBlockMember<float>(dst, f, 4, c.size);
+ break;
+ }
+ case QMetaType::Float: {
+ const float f = qvariant_cast<float>(c.value);
+ fillUniformBlockMember<float>(dst, &f, 1, c.size);
+ break;
+ }
+ case QMetaType::Double: {
+ const float f = float(qvariant_cast<double>(c.value));
+ fillUniformBlockMember<float>(dst, &f, 1, c.size);
+ break;
+ }
+ case QMetaType::Int: {
+ const qint32 i = c.value.toInt();
+ fillUniformBlockMember<qint32>(dst, &i, 1, c.size);
+ break;
+ }
+ case QMetaType::Bool: {
+ const qint32 b = c.value.toBool();
+ fillUniformBlockMember<qint32>(dst, &b, 1, c.size);
+ break;
+ }
+ case QMetaType::QTransform: { // mat3
+ const QTransform v = qvariant_cast<QTransform>(c.value);
+ const float m[3][3] = {
+ { float(v.m11()), float(v.m12()), float(v.m13()) },
+ { float(v.m21()), float(v.m22()), float(v.m23()) },
+ { float(v.m31()), float(v.m32()), float(v.m33()) }
+ };
+ // stored as 4 floats per column, 1 unused
+ memset(dst, 0, c.size);
+ const size_t bytesPerColumn = 4 * sizeof(float);
+ if (c.size >= bytesPerColumn)
+ fillUniformBlockMember<float>(dst, m[0], 3, 3 * sizeof(float));
+ if (c.size >= 2 * bytesPerColumn)
+ fillUniformBlockMember<float>(dst + bytesPerColumn, m[1], 3, 3 * sizeof(float));
+ if (c.size >= 3 * bytesPerColumn)
+ fillUniformBlockMember<float>(dst + 2 * bytesPerColumn, m[2], 3, 3 * sizeof(float));
+ break;
+ }
+ case QMetaType::QSize:
+ case QMetaType::QSizeF: { // vec2
+ const QSizeF v = c.value.toSizeF();
+ const float f[2] = { float(v.width()), float(v.height()) };
+ fillUniformBlockMember<float>(dst, f, 2, c.size);
+ break;
+ }
+ case QMetaType::QPoint:
+ case QMetaType::QPointF: { // vec2
+ const QPointF v = c.value.toPointF();
+ const float f[2] = { float(v.x()), float(v.y()) };
+ fillUniformBlockMember<float>(dst, f, 2, c.size);
+ break;
+ }
+ case QMetaType::QRect:
+ case QMetaType::QRectF: { // vec4
+ const QRectF v = c.value.toRectF();
+ const float f[4] = { float(v.x()), float(v.y()), float(v.width()), float(v.height()) };
+ fillUniformBlockMember<float>(dst, f, 4, c.size);
+ break;
+ }
+ case QMetaType::QVector2D: { // vec2
+ const QVector2D v = qvariant_cast<QVector2D>(c.value);
+ const float f[2] = { float(v.x()), float(v.y()) };
+ fillUniformBlockMember<float>(dst, f, 2, c.size);
+ break;
+ }
+ case QMetaType::QVector3D: { // vec3
+ const QVector3D v = qvariant_cast<QVector3D>(c.value);
+ const float f[3] = { float(v.x()), float(v.y()), float(v.z()) };
+ fillUniformBlockMember<float>(dst, f, 3, c.size);
+ break;
+ }
+ case QMetaType::QVector4D: { // vec4
+ const QVector4D v = qvariant_cast<QVector4D>(c.value);
+ const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.w()) };
+ fillUniformBlockMember<float>(dst, f, 4, c.size);
+ break;
+ }
+ case QMetaType::QQuaternion: { // vec4
+ const QQuaternion v = qvariant_cast<QQuaternion>(c.value);
+ const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.scalar()) };
+ fillUniformBlockMember<float>(dst, f, 4, c.size);
+ break;
+ }
+ case QMetaType::QMatrix4x4: { // mat4
+ const QMatrix4x4 m = qvariant_cast<QMatrix4x4>(c.value);
+ fillUniformBlockMember<float>(dst, m.constData(), 16, c.size);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ return changed;
+}
+
+void QSGRhiShaderEffectMaterialShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+ Q_UNUSED(oldMaterial);
+ QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
+
+ if (binding >= QSGRhiShaderEffectMaterial::MAX_BINDINGS)
+ return;
+
+ QSGTextureProvider *tp = mat->m_textureProviders.at(binding);
+ if (tp) {
+ if (QSGTexture *t = tp->texture()) {
+ t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+
+ if (t->isAtlasTexture() && !mat->m_geometryUsesTextureSubRect && !mat->usesSubRectUniform(binding)) {
+ // Why the hassle with the batch: while removedFromAtlas() is
+ // able to operate with its own resource update batch (which is
+ // then committed immediately), that approach is wrong when the
+ // atlas enqueued (in the updateRhiTexture() above) not yet
+ // committed operations to state.resourceUpdateBatch()... The
+ // only safe way then is to use the same batch the atlas'
+ // updateRhiTexture() used.
+ QSGTexture *newTexture = t->removedFromAtlas(state.resourceUpdateBatch());
+ if (newTexture) {
+ t = newTexture;
+ t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+ }
+ }
+ *texture = t;
+ return;
+ }
+ }
+
+ if (!mat->m_dummyTexture) {
+ mat->m_dummyTexture = new QSGPlainTexture;
+ mat->m_dummyTexture->setFiltering(QSGTexture::Nearest);
+ mat->m_dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
+ mat->m_dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
+ QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
+ img.fill(0);
+ mat->m_dummyTexture->setImage(img);
+ mat->m_dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+ }
+ *texture = mat->m_dummyTexture;
+}
+
+bool QSGRhiShaderEffectMaterialShader::updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+ Q_UNUSED(state);
+ Q_UNUSED(oldMaterial);
+ QSGRhiShaderEffectMaterial *mat = static_cast<QSGRhiShaderEffectMaterial *>(newMaterial);
+
+ switch (mat->m_cullMode) {
+ case QSGShaderEffectNode::FrontFaceCulling:
+ ps->cullMode = GraphicsPipelineState::CullFront;
+ return true;
+ case QSGShaderEffectNode::BackFaceCulling:
+ ps->cullMode = GraphicsPipelineState::CullBack;
+ return true;
+ default:
+ return false;
+ }
+}
+
+QSGRhiShaderEffectMaterial::QSGRhiShaderEffectMaterial(QSGRhiShaderEffectNode *node)
+ : m_node(node)
+{
+ setFlag(Blending | RequiresFullMatrix, true); // may be changed in syncMaterial()
+}
+
+QSGRhiShaderEffectMaterial::~QSGRhiShaderEffectMaterial()
+{
+ if (m_materialType && m_materialTypeCacheKey)
+ shaderMaterialTypeCache[m_materialTypeCacheKey].unref(m_vertexShader, m_fragmentShader);
+
+ delete m_dummyTexture;
+}
+
+static bool hasAtlasTexture(const QVector<QSGTextureProvider *> &textureProviders)
+{
+ for (QSGTextureProvider *tp : textureProviders) {
+ if (tp && tp->texture() && tp->texture()->isAtlasTexture())
+ return true;
+ }
+ return false;
+}
+
+int QSGRhiShaderEffectMaterial::compare(const QSGMaterial *other) const
+{
+ Q_ASSERT(other && type() == other->type());
+ const QSGRhiShaderEffectMaterial *o = static_cast<const QSGRhiShaderEffectMaterial *>(other);
+
+ if (int diff = m_cullMode - o->m_cullMode)
+ return diff;
+
+ if (int diff = m_textureProviders.size() - o->m_textureProviders.size())
+ return diff;
+
+ if (m_linker.m_constants != o->m_linker.m_constants)
+ return 1;
+
+ if (hasAtlasTexture(m_textureProviders) && !m_geometryUsesTextureSubRect)
+ return -1;
+
+ if (hasAtlasTexture(o->m_textureProviders) && !o->m_geometryUsesTextureSubRect)
+ return 1;
+
+ for (int binding = 0, count = m_textureProviders.size(); binding != count; ++binding) {
+ QSGTextureProvider *tp1 = m_textureProviders.at(binding);
+ QSGTextureProvider *tp2 = o->m_textureProviders.at(binding);
+ if (tp1 && tp2) {
+ QSGTexture *t1 = tp1->texture();
+ QSGTexture *t2 = tp2->texture();
+ if (t1 && t2) {
+ const qint64 diff = t1->comparisonKey() - t2->comparisonKey();
+ if (diff != 0)
+ return diff < 0 ? -1 : 1;
+ } else {
+ if (!t1 && t2)
+ return -1;
+ if (t1 && !t2)
+ return 1;
+ }
+ } else {
+ if (!tp1 && tp2)
+ return -1;
+ if (tp1 && !tp2)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+QSGMaterialType *QSGRhiShaderEffectMaterial::type() const
+{
+ return m_materialType;
+}
+
+QSGMaterialShader *QSGRhiShaderEffectMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
+{
+ Q_UNUSED(renderMode);
+ return new QSGRhiShaderEffectMaterialShader(this);
+}
+
+void QSGRhiShaderEffectMaterial::updateTextureProviders(bool layoutChange)
+{
+ if (layoutChange) {
+ for (QSGTextureProvider *tp : m_textureProviders) {
+ if (tp) {
+ QObject::disconnect(tp, SIGNAL(textureChanged()), m_node,
+ SLOT(handleTextureChange()));
+ QObject::disconnect(tp, SIGNAL(destroyed(QObject*)), m_node,
+ SLOT(handleTextureProviderDestroyed(QObject*)));
+ }
+ }
+ m_textureProviders.fill(nullptr, MAX_BINDINGS);
+ }
+
+ for (auto it = m_linker.m_samplers.constBegin(), itEnd = m_linker.m_samplers.constEnd(); it != itEnd; ++it) {
+ const int binding = it.key();
+ QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(it.value()));
+ QSGTextureProvider *newProvider = source && source->isTextureProvider() ? source->textureProvider() : nullptr;
+ if (binding >= MAX_BINDINGS) {
+ qWarning("Sampler at binding %d exceeds the available ShaderEffect binding slots; ignored",
+ binding);
+ continue;
+ }
+ QSGTextureProvider *&activeProvider(m_textureProviders[binding]);
+ if (newProvider != activeProvider) {
+ if (activeProvider) {
+ QObject::disconnect(activeProvider, SIGNAL(textureChanged()), m_node,
+ SLOT(handleTextureChange()));
+ QObject::disconnect(activeProvider, SIGNAL(destroyed(QObject*)), m_node,
+ SLOT(handleTextureProviderDestroyed(QObject*)));
+ }
+ if (newProvider) {
+ Q_ASSERT_X(newProvider->thread() == QThread::currentThread(),
+ "QSGRhiShaderEffectMaterial::updateTextureProviders",
+ "Texture provider must belong to the rendering thread");
+ QObject::connect(newProvider, SIGNAL(textureChanged()), m_node, SLOT(handleTextureChange()));
+ QObject::connect(newProvider, SIGNAL(destroyed(QObject*)), m_node,
+ SLOT(handleTextureProviderDestroyed(QObject*)));
+ } else {
+ const char *typeName = source ? source->metaObject()->className() : it.value().typeName();
+ qWarning("ShaderEffect: Texture t%d is not assigned a valid texture provider (%s).",
+ binding, typeName);
+ }
+ activeProvider = newProvider;
+ }
+ }
+}
+
+QSGRhiShaderEffectNode::QSGRhiShaderEffectNode(QSGDefaultRenderContext *rc)
+ : m_material(this)
+{
+ Q_UNUSED(rc);
+ setFlag(UsePreprocess, true);
+ setMaterial(&m_material);
+}
+
+QRectF QSGRhiShaderEffectNode::updateNormalizedTextureSubRect(bool supportsAtlasTextures)
+{
+ QRectF srcRect(0, 0, 1, 1);
+ bool geometryUsesTextureSubRect = false;
+ if (supportsAtlasTextures) {
+ QSGTextureProvider *tp = nullptr;
+ for (int binding = 0, count = m_material.m_textureProviders.size(); binding != count; ++binding) {
+ if (QSGTextureProvider *candidate = m_material.m_textureProviders.at(binding)) {
+ if (!tp) {
+ tp = candidate;
+ } else { // there can only be one...
+ tp = nullptr;
+ break;
+ }
+ }
+ }
+ if (tp && tp->texture()) {
+ srcRect = tp->texture()->normalizedTextureSubRect();
+ geometryUsesTextureSubRect = true;
+ }
+ }
+
+ if (m_material.m_geometryUsesTextureSubRect != geometryUsesTextureSubRect) {
+ m_material.m_geometryUsesTextureSubRect = geometryUsesTextureSubRect;
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ return srcRect;
+}
+
+static QShader loadShaderFromFile(const QString &filename)
+{
+ QFile f(filename);
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning() << "Failed to find shader" << filename;
+ return QShader();
+ }
+ return QShader::fromSerialized(f.readAll());
+}
+
+struct QSGRhiShaderEffectDefaultShader
+{
+ QShader shader;
+ quint32 matrixArrayByteSize;
+ quint32 opacityOffset;
+ qint8 viewCount;
+ static QSGRhiShaderEffectDefaultShader create(const QString &filename, int viewCount);
+};
+
+QSGRhiShaderEffectDefaultShader QSGRhiShaderEffectDefaultShader::create(const QString &filename, int viewCount)
+{
+ QSGRhiShaderEffectDefaultShader s;
+ s.shader = loadShaderFromFile(filename);
+ const QList<QShaderDescription::BlockVariable> uboMembers = s.shader.description().uniformBlocks().constFirst().members;
+ for (const auto &member: uboMembers) {
+ if (member.name == QByteArrayLiteral("qt_Matrix"))
+ s.matrixArrayByteSize = member.size;
+ else if (member.name == QByteArrayLiteral("qt_Opacity"))
+ s.opacityOffset = member.offset;
+ }
+ s.viewCount = viewCount;
+ return s;
+}
+
+void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
+{
+ static const int defaultVertexShaderCount = 2;
+ static QSGRhiShaderEffectDefaultShader defaultVertexShaders[defaultVertexShaderCount] = {
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"), 1),
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb.mv2qsb"), 2)
+ };
+ static const int defaultFragmentShaderCount = 2;
+ static QSGRhiShaderEffectDefaultShader defaultFragmentShaders[defaultFragmentShaderCount] = {
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"), 1),
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb.mv2qsb"), 2)
+ };
+
+ if (bool(m_material.flags() & QSGMaterial::Blending) != syncData->blending) {
+ m_material.setFlag(QSGMaterial::Blending, syncData->blending);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (m_material.m_cullMode != syncData->cullMode) {
+ m_material.m_cullMode = syncData->cullMode;
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (syncData->dirty & QSGShaderEffectNode::DirtyShaders) {
+ if (m_material.m_materialType) {
+ shaderMaterialTypeCache[m_material.m_materialTypeCacheKey].unref(m_material.m_vertexShader,
+ m_material.m_fragmentShader);
+ }
+
+ m_material.m_hasCustomVertexShader = syncData->vertex.shader->hasShaderCode;
+ quint32 defaultMatrixArrayByteSize = 0;
+ if (m_material.m_hasCustomVertexShader) {
+ m_material.m_vertexShader = syncData->vertex.shader->shaderInfo.rhiShader;
+ } else {
+ bool found = false;
+ for (int i = 0; i < defaultVertexShaderCount; ++i) {
+ if (defaultVertexShaders[i].viewCount == syncData->viewCount) {
+ m_material.m_vertexShader = defaultVertexShaders[i].shader;
+ defaultMatrixArrayByteSize = defaultVertexShaders[i].matrixArrayByteSize;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ qWarning("No default vertex shader found for view count %d", syncData->viewCount);
+ m_material.m_vertexShader = defaultVertexShaders[0].shader;
+ defaultMatrixArrayByteSize = 64;
+ }
+ }
+
+ m_material.m_hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode;
+ quint32 defaultOpacityOffset = 0;
+ if (m_material.m_hasCustomFragmentShader) {
+ m_material.m_fragmentShader = syncData->fragment.shader->shaderInfo.rhiShader;
+ } else {
+ bool found = false;
+ for (int i = 0; i < defaultFragmentShaderCount; ++i) {
+ if (defaultFragmentShaders[i].viewCount == syncData->viewCount) {
+ m_material.m_fragmentShader = defaultFragmentShaders[i].shader;
+ defaultOpacityOffset = defaultFragmentShaders[i].opacityOffset;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ qWarning("No default fragment shader found for view count %d", syncData->viewCount);
+ m_material.m_fragmentShader = defaultFragmentShaders[0].shader;
+ defaultOpacityOffset = 64;
+ }
+ }
+
+ m_material.m_materialType = shaderMaterialTypeCache[syncData->materialTypeCacheKey].ref(m_material.m_vertexShader,
+ m_material.m_fragmentShader);
+ m_material.m_materialTypeCacheKey = syncData->materialTypeCacheKey;
+
+ m_material.m_linker.reset(m_material.m_vertexShader, m_material.m_fragmentShader);
+
+ if (m_material.m_hasCustomVertexShader) {
+ m_material.m_linker.feedConstants(*syncData->vertex.shader);
+ m_material.m_linker.feedSamplers(*syncData->vertex.shader);
+ } else {
+ QSGShaderEffectNode::ShaderData defaultSD;
+ defaultSD.shaderInfo.name = QLatin1String("Default ShaderEffect vertex shader");
+ defaultSD.shaderInfo.rhiShader = m_material.m_vertexShader;
+ defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex;
+
+ // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the matrix is used
+ QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
+ v.name = QByteArrayLiteral("qt_Matrix");
+ v.offset = 0;
+ v.size = defaultMatrixArrayByteSize;
+ defaultSD.shaderInfo.variables.append(v);
+ QSGShaderEffectNode::VariableData vd;
+ vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
+ defaultSD.varData.append(vd);
+ m_material.m_linker.feedConstants(defaultSD);
+ }
+
+ if (m_material.m_hasCustomFragmentShader) {
+ m_material.m_linker.feedConstants(*syncData->fragment.shader);
+ m_material.m_linker.feedSamplers(*syncData->fragment.shader);
+ } else {
+ QSGShaderEffectNode::ShaderData defaultSD;
+ defaultSD.shaderInfo.name = QLatin1String("Default ShaderEffect fragment shader");
+ defaultSD.shaderInfo.rhiShader = m_material.m_fragmentShader;
+ defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
+
+ // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the opacity is used
+ QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
+ v.name = QByteArrayLiteral("qt_Opacity");
+ v.offset = defaultOpacityOffset;
+ v.size = sizeof(float);
+ defaultSD.shaderInfo.variables.append(v);
+ QSGShaderEffectNode::VariableData vd;
+ vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
+ defaultSD.varData.append(vd);
+
+ v.name = QByteArrayLiteral("source");
+ v.bindPoint = 1;
+ v.type = QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
+ defaultSD.shaderInfo.variables.append(v);
+ for (const QSGShaderEffectNode::VariableData &extVarData : std::as_const(syncData->fragment.shader->varData)) {
+ if (extVarData.specialType == QSGShaderEffectNode::VariableData::Source) {
+ vd.value = extVarData.value;
+ break;
+ }
+ }
+ vd.specialType = QSGShaderEffectNode::VariableData::Source;
+ defaultSD.varData.append(vd);
+
+ m_material.m_linker.feedConstants(defaultSD);
+ m_material.m_linker.feedSamplers(defaultSD);
+ }
+
+ m_material.m_linker.linkTextureSubRects();
+ m_material.updateTextureProviders(true);
+ markDirty(QSGNode::DirtyMaterial);
+
+ } else {
+
+ if (syncData->dirty & QSGShaderEffectNode::DirtyShaderConstant) {
+ if (!syncData->vertex.dirtyConstants->isEmpty())
+ m_material.m_linker.feedConstants(*syncData->vertex.shader, syncData->vertex.dirtyConstants);
+ if (!syncData->fragment.dirtyConstants->isEmpty())
+ m_material.m_linker.feedConstants(*syncData->fragment.shader, syncData->fragment.dirtyConstants);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (syncData->dirty & QSGShaderEffectNode::DirtyShaderTexture) {
+ if (!syncData->vertex.dirtyTextures->isEmpty())
+ m_material.m_linker.feedSamplers(*syncData->vertex.shader, syncData->vertex.dirtyTextures);
+ if (!syncData->fragment.dirtyTextures->isEmpty())
+ m_material.m_linker.feedSamplers(*syncData->fragment.shader, syncData->fragment.dirtyTextures);
+ m_material.m_linker.linkTextureSubRects();
+ m_material.updateTextureProviders(false);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+ }
+
+ if (bool(m_material.flags() & QSGMaterial::RequiresFullMatrix) != m_material.m_hasCustomVertexShader) {
+ m_material.setFlag(QSGMaterial::RequiresFullMatrix, m_material.m_hasCustomVertexShader);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+}
+
+void QSGRhiShaderEffectNode::handleTextureChange()
+{
+ markDirty(QSGNode::DirtyMaterial);
+ emit textureChanged();
+}
+
+void QSGRhiShaderEffectNode::handleTextureProviderDestroyed(QObject *object)
+{
+ for (QSGTextureProvider *&tp : m_material.m_textureProviders) {
+ if (tp == object)
+ tp = nullptr;
+ }
+}
+
+void QSGRhiShaderEffectNode::preprocess()
+{
+ for (QSGTextureProvider *tp : m_material.m_textureProviders) {
+ if (tp) {
+ if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(tp->texture()))
+ texture->updateTexture();
+ }
+ }
+}
+
+bool QSGRhiGuiThreadShaderEffectManager::hasSeparateSamplerAndTextureObjects() const
+{
+ return false; // because SPIR-V and QRhi make it look so, regardless of the underlying API
+}
+
+QString QSGRhiGuiThreadShaderEffectManager::log() const
+{
+ return QString();
+}
+
+QSGGuiThreadShaderEffectManager::Status QSGRhiGuiThreadShaderEffectManager::status() const
+{
+ return m_status;
+}
+
+void QSGRhiGuiThreadShaderEffectManager::prepareShaderCode(ShaderInfo::Type typeHint, const QUrl &src, ShaderInfo *result)
+{
+ if (!src.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || src.isLocalFile()) {
+ if (!m_fileSelector) {
+ m_fileSelector = new QFileSelector(this);
+ m_fileSelector->setExtraSelectors(QStringList() << QStringLiteral("qsb"));
+ }
+ const QString fn = m_fileSelector->select(QQmlFile::urlToLocalFileOrQrc(src));
+ const QShader s = loadShaderFromFile(fn);
+ if (!s.isValid()) {
+ qWarning("ShaderEffect: Failed to deserialize QShader from %s. "
+ "Either the filename is incorrect, or it is not a valid .qsb file. "
+ "In Qt 6 shaders must be preprocessed using the Qt Shader Tools infrastructure. "
+ "The vertexShader and fragmentShader properties are now URLs that are expected to point to .qsb files generated by the qsb tool. "
+ "See https://doc.qt.io/qt-6/qtshadertools-index.html for more information.",
+ qPrintable(fn));
+ m_status = Error;
+ emit shaderCodePrepared(false, typeHint, src, result);
+ emit logAndStatusChanged();
+ return;
+ }
+ result->name = fn;
+ result->rhiShader = s;
+ const bool ok = reflect(result);
+ m_status = ok ? Compiled : Error;
+ emit shaderCodePrepared(ok, typeHint, src, result);
+ emit logAndStatusChanged();
+ } else {
+ qWarning("rhi shader effect only supports files (qrc or local) at the moment");
+ emit shaderCodePrepared(false, typeHint, src, result);
+ }
+}
+
+bool QSGRhiGuiThreadShaderEffectManager::reflect(ShaderInfo *result)
+{
+ switch (result->rhiShader.stage()) {
+ case QShader::VertexStage:
+ result->type = ShaderInfo::TypeVertex;
+ break;
+ case QShader::FragmentStage:
+ result->type = ShaderInfo::TypeFragment;
+ break;
+ default:
+ result->type = ShaderInfo::TypeOther;
+ qWarning("Unsupported shader stage (%d)", result->rhiShader.stage());
+ return false;
+ }
+
+ const QShaderDescription desc = result->rhiShader.description();
+
+ int ubufBinding = -1;
+ const QVector<QShaderDescription::UniformBlock> ubufs = desc.uniformBlocks();
+ const int ubufCount = ubufs.size();
+ for (int i = 0; i < ubufCount; ++i) {
+ const QShaderDescription::UniformBlock &ubuf(ubufs[i]);
+ if (ubufBinding == -1 && ubuf.binding >= 0) {
+ ubufBinding = ubuf.binding;
+ for (const QShaderDescription::BlockVariable &member : ubuf.members) {
+ ShaderInfo::Variable v;
+ v.type = ShaderInfo::Constant;
+ v.name = member.name;
+ v.offset = member.offset;
+ v.size = member.size;
+ result->variables.append(v);
+ }
+ } else {
+ qWarning("Uniform block %s (binding %d) ignored", ubuf.blockName.constData(),
+ ubuf.binding);
+ }
+ }
+
+ const QVector<QShaderDescription::InOutVariable> combinedImageSamplers = desc.combinedImageSamplers();
+ const int samplerCount = combinedImageSamplers.size();
+ for (int i = 0; i < samplerCount; ++i) {
+ const QShaderDescription::InOutVariable &combinedImageSampler(combinedImageSamplers[i]);
+ ShaderInfo::Variable v;
+ v.type = ShaderInfo::Sampler;
+ v.name = combinedImageSampler.name;
+ v.bindPoint = combinedImageSampler.binding;
+ result->variables.append(v);
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qsgrhishadereffectnode_p.cpp"