Revision control
++ // 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
++ // Qt-Security score:significant reason:default
++
++ #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 <QtGui/qquaternion.h>
++
++ #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)
++ {
++ hash = qHashMulti(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 QList<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 : std::as_const(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 : std::as_const(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 QList<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) {
++ // The uniform buffer is sized from ubuf.size, while the members are
++ // written at their own offsets, so a member reaching outside the block
++ // would have updateUniformData() write past the buffer.
++ if (member.offset < 0 || member.size < 0
++ || member.offset > ubuf.size - member.size)
++ {
++ qWarning("Uniform block member %s at offset %d with size %d does not fit "
++ "the %d byte block; shader will not be used",
++ member.name.constData(), member.offset, member.size, ubuf.size);
++ return false;
++ }
++ 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 QList<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"