// 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 #include #include #include #include #include #include #include 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 *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 *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 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 m_types; QHash 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 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 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(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(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(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(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(c.value)).toRgb(); const float f[4] = { float(v.redF()), float(v.greenF()), float(v.blueF()), float(v.alphaF()) }; fillUniformBlockMember(dst, f, 4, c.size); break; } case QMetaType::Float: { const float f = qvariant_cast(c.value); fillUniformBlockMember(dst, &f, 1, c.size); break; } case QMetaType::Double: { const float f = float(qvariant_cast(c.value)); fillUniformBlockMember(dst, &f, 1, c.size); break; } case QMetaType::Int: { const qint32 i = c.value.toInt(); fillUniformBlockMember(dst, &i, 1, c.size); break; } case QMetaType::Bool: { const qint32 b = c.value.toBool(); fillUniformBlockMember(dst, &b, 1, c.size); break; } case QMetaType::QTransform: { // mat3 const QTransform v = qvariant_cast(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(dst, m[0], 3, 3 * sizeof(float)); if (c.size >= 2 * bytesPerColumn) fillUniformBlockMember(dst + bytesPerColumn, m[1], 3, 3 * sizeof(float)); if (c.size >= 3 * bytesPerColumn) fillUniformBlockMember(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(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(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(dst, f, 4, c.size); break; } case QMetaType::QVector2D: { // vec2 const QVector2D v = qvariant_cast(c.value); const float f[2] = { float(v.x()), float(v.y()) }; fillUniformBlockMember(dst, f, 2, c.size); break; } case QMetaType::QVector3D: { // vec3 const QVector3D v = qvariant_cast(c.value); const float f[3] = { float(v.x()), float(v.y()), float(v.z()) }; fillUniformBlockMember(dst, f, 3, c.size); break; } case QMetaType::QVector4D: { // vec4 const QVector4D v = qvariant_cast(c.value); const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.w()) }; fillUniformBlockMember(dst, f, 4, c.size); break; } case QMetaType::QQuaternion: { // vec4 const QQuaternion v = qvariant_cast(c.value); const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.scalar()) }; fillUniformBlockMember(dst, f, 4, c.size); break; } case QMetaType::QMatrix4x4: { // mat4 const QMatrix4x4 m = qvariant_cast(c.value); fillUniformBlockMember(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(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(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 &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(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(qvariant_cast(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 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(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 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 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"