| /**************************************************************************** |
| ** |
| ** 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(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) |
| { |
| return QColor::fromRgbF(c.redF() * c.alphaF(), c.greenF() * c.alphaF(), c.blueF() * c.alphaF(), c.alphaF()); |
| } |
| |
| 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(); |
| 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(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(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 |