diff options
Diffstat (limited to 'src/quick/scenegraph/qsgrhishadereffectnode.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgrhishadereffectnode.cpp | 886 |
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 |