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.cpp886
1 files changed, 886 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgrhishadereffectnode.cpp b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
new file mode 100644
index 0000000000..fe9cecb51b
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
@@ -0,0 +1,886 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgrhishadereffectnode_p.h"
+#include "qsgdefaultrendercontext_p.h"
+#include "qsgrhisupport_p.h"
+#include <qsgmaterialrhishader.h>
+#include <qsgtextureprovider.h>
+#include <private/qsgplaintexture_p.h>
+#include <QtGui/private/qshaderdescription_p.h>
+#include <QQmlFile>
+#include <QFile>
+#include <QFileSelector>
+
+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();
+}
+
+void QSGRhiShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
+{
+ Q_ASSERT(shader.shaderInfo.variables.count() == shader.varData.count());
+ if (!dirtyIndices) {
+ m_constantBufferSize = qMax(m_constantBufferSize, shader.shaderInfo.constantDataSize);
+ for (int i = 0; i < shader.shaderInfo.variables.count(); ++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 (QSGRhiSupport::instance()->isShaderEffectDebuggingRequested()) {
+ 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 (QSGRhiSupport::instance()->isShaderEffectDebuggingRequested()) {
+ 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.count(); ++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);
+ 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));
+ 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.type() == QVariant::ByteArray) {
+ const QByteArray name = c.value.toByteArray();
+ if (!m_samplerNameMap.contains(name))
+ qWarning("ShaderEffect: qt_SubRect_%s refers to unknown source texture", name.constData());
+ c.value = m_samplerNameMap[name];
+ }
+ }
+ }
+}
+
+void QSGRhiShaderLinker::dump()
+{
+ if (m_error) {
+ qDebug() << "Failed to generate program data";
+ return;
+ }
+ qDebug() << "Combined shader data" << m_vs << m_fs << "cbuffer size" << m_constantBufferSize;
+ 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 *get(const QShader &vs, const QShader &fs);
+ void reset() { qDeleteAll(m_types); m_types.clear(); }
+
+ struct Key {
+ QShader blob[2];
+ Key() { }
+ Key(const QShader &vs, const QShader &fs) { blob[0] = vs; blob[1] = fs; }
+ bool operator==(const Key &other) const {
+ return blob[0] == other.blob[0] && blob[1] == other.blob[1];
+ }
+ };
+ QHash<Key, QSGMaterialType *> m_types;
+};
+
+uint qHash(const QSGRhiShaderMaterialTypeCache::Key &key, uint seed = 0)
+{
+ uint hash = seed;
+ for (int i = 0; i < 2; ++i)
+ hash = hash * 31337 + qHash(key.blob[i]);
+ return hash;
+}
+
+QSGMaterialType *QSGRhiShaderMaterialTypeCache::get(const QShader &vs, const QShader &fs)
+{
+ const Key k(vs, fs);
+ if (m_types.contains(k))
+ return m_types.value(k);
+
+ QSGMaterialType *t = new QSGMaterialType;
+ m_types.insert(k, t);
+ return t;
+}
+
+static QSGRhiShaderMaterialTypeCache shaderMaterialTypeCache;
+
+class QSGRhiShaderEffectMaterialShader : public QSGMaterialRhiShader
+{
+public:
+ QSGRhiShaderEffectMaterialShader(const QSGRhiShaderEffectMaterial *material);
+
+ bool updateUniformData(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+ void updateSampledImage(const RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+ bool updateGraphicsPipelineState(const 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)
+{
+ return QColor::fromRgbF(c.redF() * c.alphaF(), c.greenF() * c.alphaF(), c.blueF() * c.alphaF(), c.alphaF());
+}
+
+bool QSGRhiShaderEffectMaterialShader::updateUniformData(const 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();
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, &f, sizeof(f));
+ changed = true;
+ }
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) {
+ if (state.isMatrixDirty()) {
+ const int sz = 16 * sizeof(float);
+ Q_ASSERT(sz == c.size);
+ memcpy(dst, state.combinedMatrix().constData(), sz);
+ 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()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::None) {
+ changed = true;
+ switch (int(c.value.type())) {
+ case QMetaType::QColor: {
+ const QColor v = qsg_premultiply_color(qvariant_cast<QColor>(c.value));
+ const float f[4] = { float(v.redF()), float(v.greenF()), float(v.blueF()), float(v.alphaF()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::Float: {
+ const float f = qvariant_cast<float>(c.value);
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, &f, sizeof(f));
+ break;
+ }
+ case QMetaType::Double: {
+ const float f = float(qvariant_cast<double>(c.value));
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, &f, sizeof(f));
+ break;
+ }
+ case QMetaType::Int: {
+ const int i = c.value.toInt();
+ Q_ASSERT(sizeof(i) == c.size);
+ memcpy(dst, &i, sizeof(i));
+ break;
+ }
+ case QMetaType::Bool: {
+ const bool b = c.value.toBool();
+ Q_ASSERT(sizeof(b) == c.size);
+ memcpy(dst, &b, sizeof(b));
+ 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()) }
+ };
+ Q_ASSERT(sizeof(m) == c.size);
+ memcpy(dst, m[0], sizeof(m));
+ break;
+ }
+ case QMetaType::QSize:
+ case QMetaType::QSizeF: { // vec2
+ const QSizeF v = c.value.toSizeF();
+ const float f[2] = { float(v.width()), float(v.height()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QPoint:
+ case QMetaType::QPointF: { // vec2
+ const QPointF v = c.value.toPointF();
+ const float f[2] = { float(v.x()), float(v.y()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ 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()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QVector2D: { // vec2
+ const QVector2D v = qvariant_cast<QVector2D>(c.value);
+ const float f[2] = { float(v.x()), float(v.y()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ 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()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ 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()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ 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()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QMatrix4x4: { // mat4
+ const QMatrix4x4 v = qvariant_cast<QMatrix4x4>(c.value);
+ const int sz = 16 * sizeof(float);
+ Q_ASSERT(sz == c.size);
+ memcpy(dst, v.constData(), sz);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ return changed;
+}
+
+void QSGRhiShaderEffectMaterialShader::updateSampledImage(const 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->updateRhiTexture(state.rhi(), state.resourceUpdateBatch());
+ if (t->isAtlasTexture() && !mat->m_geometryUsesTextureSubRect) {
+ // 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.
+ t->setWorkResourceUpdateBatch(state.resourceUpdateBatch());
+ QSGTexture *newTexture = t->removedFromAtlas();
+ t->setWorkResourceUpdateBatch(nullptr);
+ if (newTexture)
+ t = newTexture;
+ }
+ *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->updateRhiTexture(state.rhi(), state.resourceUpdateBatch());
+ }
+ *texture = mat->m_dummyTexture;
+}
+
+bool QSGRhiShaderEffectMaterialShader::updateGraphicsPipelineState(const 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(SupportsRhiShader | Blending | RequiresFullMatrix, true); // may be changed in syncMaterial()
+}
+
+QSGRhiShaderEffectMaterial::~QSGRhiShaderEffectMaterial()
+{
+ 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.count() - o->m_textureProviders.count())
+ 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.count(); 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) {
+ if (int diff = t1->comparisonKey() - t2->comparisonKey())
+ return diff;
+ } 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() const
+{
+ Q_ASSERT(flags().testFlag(RhiShaderWanted));
+ 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, QSGRhiGuiThreadShaderEffectManager *mgr)
+ : QSGShaderEffectNode(mgr),
+ m_rc(rc),
+ m_mgr(mgr),
+ m_material(this)
+{
+ 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.count(); 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 loadShader(const QString &filename)
+{
+ QFile f(filename);
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning() << "Failed to find shader" << filename;
+ return QShader();
+ }
+ return QShader::fromSerialized(f.readAll());
+}
+
+void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
+{
+ static QShader defaultVertexShader;
+ static QShader defaultFragmentShader;
+
+ 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) {
+ m_material.m_hasCustomVertexShader = syncData->vertex.shader->hasShaderCode;
+ if (m_material.m_hasCustomVertexShader) {
+ m_material.m_vertexShader = syncData->vertex.shader->shaderInfo.rhiShader;
+ } else {
+ if (!defaultVertexShader.isValid())
+ defaultVertexShader = loadShader(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"));
+ m_material.m_vertexShader = defaultVertexShader;
+ }
+
+ m_material.m_hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode;
+ if (m_material.m_hasCustomFragmentShader) {
+ m_material.m_fragmentShader = syncData->fragment.shader->shaderInfo.rhiShader;
+ } else {
+ if (!defaultFragmentShader.isValid())
+ defaultFragmentShader = loadShader(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"));
+ m_material.m_fragmentShader = defaultFragmentShader;
+ }
+
+ m_material.m_materialType = shaderMaterialTypeCache.get(m_material.m_vertexShader, m_material.m_fragmentShader);
+ 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; float qt_Opacity; } where only the matrix is used
+ QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
+ v.name = QByteArrayLiteral("qt_Matrix");
+ v.offset = 0;
+ v.size = 16 * sizeof(float);
+ defaultSD.shaderInfo.variables.append(v);
+ QSGShaderEffectNode::VariableData vd;
+ vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
+ defaultSD.varData.append(vd);
+ defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
+ 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; float qt_Opacity; } where only the opacity is used
+ QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
+ v.name = QByteArrayLiteral("qt_Opacity");
+ v.offset = 16 * sizeof(float);
+ 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 : qAsConst(syncData->fragment.shader->varData)) {
+ if (extVarData.specialType == QSGShaderEffectNode::VariableData::Source) {
+ vd.value = extVarData.value;
+ break;
+ }
+ }
+ vd.specialType = QSGShaderEffectNode::VariableData::Source;
+ defaultSD.varData.append(vd);
+
+ defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
+
+ 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 m_mgr->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();
+ }
+ }
+}
+
+void QSGRhiShaderEffectNode::cleanupMaterialTypeCache()
+{
+ shaderMaterialTypeCache.reset();
+}
+
+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 QByteArray &src, ShaderInfo *result)
+{
+ QUrl srcUrl(QString::fromUtf8(src));
+ if (!srcUrl.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || srcUrl.isLocalFile()) {
+ if (!m_fileSelector) {
+ m_fileSelector = new QFileSelector(this);
+ m_fileSelector->setExtraSelectors(QStringList() << QStringLiteral("qsb"));
+ }
+ const QString fn = m_fileSelector->select(QQmlFile::urlToLocalFileOrQrc(srcUrl));
+ QFile f(fn);
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning("ShaderEffect: Failed to read %s", qPrintable(fn));
+ m_status = Error;
+ emit shaderCodePrepared(false, typeHint, src, result);
+ emit logAndStatusChanged();
+ return;
+ }
+ const QShader s = QShader::fromSerialized(f.readAll());
+ f.close();
+ if (!s.isValid()) {
+ qWarning("ShaderEffect: Failed to deserialize QShader from %s", 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();
+ result->constantDataSize = 0;
+
+ int ubufBinding = -1;
+ const QVector<QShaderDescription::UniformBlock> ubufs = desc.uniformBlocks();
+ const int ubufCount = ubufs.count();
+ for (int i = 0; i < ubufCount; ++i) {
+ const QShaderDescription::UniformBlock &ubuf(ubufs[i]);
+ if (ubufBinding == -1 && ubuf.binding >= 0) {
+ ubufBinding = ubuf.binding;
+ result->constantDataSize = ubuf.size;
+ for (const QShaderDescription::BlockVariable &member : ubuf.members) {
+ ShaderInfo::Variable v;
+ v.type = ShaderInfo::Constant;
+ v.name = member.name.toUtf8();
+ v.offset = member.offset;
+ v.size = member.size;
+ result->variables.append(v);
+ }
+ } else {
+ qWarning("Uniform block %s (binding %d) ignored", qPrintable(ubuf.blockName), ubuf.binding);
+ }
+ }
+
+ const QVector<QShaderDescription::InOutVariable> combinedImageSamplers = desc.combinedImageSamplers();
+ const int samplerCount = combinedImageSamplers.count();
+ for (int i = 0; i < samplerCount; ++i) {
+ const QShaderDescription::InOutVariable &combinedImageSampler(combinedImageSamplers[i]);
+ ShaderInfo::Variable v;
+ v.type = ShaderInfo::Sampler;
+ v.name = combinedImageSampler.name.toUtf8();
+ v.bindPoint = combinedImageSampler.binding;
+ result->variables.append(v);
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE