blob: 6f6544548c923fb9856fe91fc13df194151f06e6 [file] [log] [blame]
/****************************************************************************
**
** 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