diff options
-rw-r--r-- | src/quick/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/quick/items/items.pri | 6 | ||||
-rw-r--r-- | src/quick/items/qquickgenericshadereffect.cpp | 738 | ||||
-rw-r--r-- | src/quick/items/qquickgenericshadereffect_p.h | 154 | ||||
-rw-r--r-- | src/quick/items/qquickshadereffect.cpp | 780 | ||||
-rw-r--r-- | src/quick/items/qquickshadereffect_p.h | 4 |
6 files changed, 782 insertions, 901 deletions
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt index c1362e5bb6..f045a5f05d 100644 --- a/src/quick/CMakeLists.txt +++ b/src/quick/CMakeLists.txt @@ -495,7 +495,6 @@ qt_extend_target(Quick CONDITION QT_FEATURE_quick_repeater qt_extend_target(Quick CONDITION QT_FEATURE_quick_shadereffect SOURCES - items/qquickgenericshadereffect.cpp items/qquickgenericshadereffect_p.h items/qquickshadereffect.cpp items/qquickshadereffect_p.h items/qquickshadereffectmesh.cpp items/qquickshadereffectmesh_p.h items/qquickshadereffectsource.cpp items/qquickshadereffectsource_p.h diff --git a/src/quick/items/items.pri b/src/quick/items/items.pri index f514c306ff..c3f350809d 100644 --- a/src/quick/items/items.pri +++ b/src/quick/items/items.pri @@ -209,13 +209,11 @@ qtConfig(quick-shadereffect) { HEADERS += \ $$PWD/qquickshadereffectsource_p.h \ $$PWD/qquickshadereffectmesh_p.h \ - $$PWD/qquickshadereffect_p.h \ - $$PWD/qquickgenericshadereffect_p.h + $$PWD/qquickshadereffect_p.h SOURCES += \ $$PWD/qquickshadereffectsource.cpp \ $$PWD/qquickshadereffectmesh.cpp \ - $$PWD/qquickshadereffect.cpp \ - $$PWD/qquickgenericshadereffect.cpp + $$PWD/qquickshadereffect.cpp qtConfig(opengl) { OTHER_FILES += \ diff --git a/src/quick/items/qquickgenericshadereffect.cpp b/src/quick/items/qquickgenericshadereffect.cpp deleted file mode 100644 index 6f82d94ae9..0000000000 --- a/src/quick/items/qquickgenericshadereffect.cpp +++ /dev/null @@ -1,738 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <private/qquickgenericshadereffect_p.h> -#include <private/qquickwindow_p.h> -#include <private/qquickitem_p.h> - -QT_BEGIN_NAMESPACE - -namespace QtPrivate { -class EffectSlotMapper: public QtPrivate::QSlotObjectBase -{ -public: - typedef std::function<void()> PropChangedFunc; - - explicit EffectSlotMapper(PropChangedFunc func) - : QSlotObjectBase(&impl), _signalIndex(-1), func(func) - { ref(); } - - void setSignalIndex(int idx) { _signalIndex = idx; } - int signalIndex() const { return _signalIndex; } - -private: - int _signalIndex; - PropChangedFunc func; - - static void impl(int which, QSlotObjectBase *this_, QObject *, void **a, bool *ret) - { - auto thiz = static_cast<EffectSlotMapper*>(this_); - switch (which) { - case Destroy: - delete thiz; - break; - case Call: - thiz->func(); - break; - case Compare: - *ret = thiz == reinterpret_cast<EffectSlotMapper *>(a[0]); - break; - case NumOperations: ; - } - } -}; -} // namespace QtPrivate - -// The generic shader effect is used whenever on the RHI code path, or when the -// scenegraph backend indicates SupportsShaderEffectNode. This, unlike the -// monolithic and interconnected (e.g. with particles) OpenGL variant, passes -// most of the work to a scenegraph node created via the adaptation layer, thus -// allowing different implementation in the backends. - -QQuickGenericShaderEffect::QQuickGenericShaderEffect(QQuickShaderEffect *item, QObject *parent) - : QObject(parent) - , m_item(item) - , m_meshResolution(1, 1) - , m_mesh(nullptr) - , m_cullMode(QQuickShaderEffect::NoCulling) - , m_blending(true) - , m_supportsAtlasTextures(false) - , m_mgr(nullptr) - , m_fragNeedsUpdate(true) - , m_vertNeedsUpdate(true) -{ - qRegisterMetaType<QSGGuiThreadShaderEffectManager::ShaderInfo::Type>("ShaderInfo::Type"); - for (int i = 0; i < NShader; ++i) - m_inProgress[i] = nullptr; -} - -QQuickGenericShaderEffect::~QQuickGenericShaderEffect() -{ - for (int i = 0; i < NShader; ++i) { - disconnectSignals(Shader(i)); - clearMappers(Shader(i)); - } - - delete m_mgr; -} - -void QQuickGenericShaderEffect::setFragmentShader(const QByteArray &src) -{ - // Compare the actual values since they are often just filenames. - // Optimizing by comparing constData() is a bad idea since seemingly static - // strings in QML may in fact have different addresses when a binding - // triggers assigning the "same" value to the property. - if (m_fragShader == src) - return; - - m_fragShader = src; - - m_fragNeedsUpdate = true; - if (m_item->isComponentComplete()) - maybeUpdateShaders(); - - emit m_item->fragmentShaderChanged(); -} - -void QQuickGenericShaderEffect::setVertexShader(const QByteArray &src) -{ - if (m_vertShader == src) - return; - - m_vertShader = src; - - m_vertNeedsUpdate = true; - if (m_item->isComponentComplete()) - maybeUpdateShaders(); - - emit m_item->vertexShaderChanged(); -} - -void QQuickGenericShaderEffect::setBlending(bool enable) -{ - if (m_blending == enable) - return; - - m_blending = enable; - m_item->update(); - emit m_item->blendingChanged(); -} - -QVariant QQuickGenericShaderEffect::mesh() const -{ - return m_mesh ? QVariant::fromValue(static_cast<QObject *>(m_mesh)) - : QVariant::fromValue(m_meshResolution); -} - -void QQuickGenericShaderEffect::setMesh(const QVariant &mesh) -{ - QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qvariant_cast<QObject *>(mesh)); - if (newMesh && newMesh == m_mesh) - return; - - if (m_mesh) - disconnect(m_mesh, SIGNAL(geometryChanged()), this, nullptr); - - m_mesh = newMesh; - - if (m_mesh) { - connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(markGeometryDirtyAndUpdate())); - } else { - if (mesh.canConvert<QSize>()) { - m_meshResolution = mesh.toSize(); - } else { - QList<QByteArray> res = mesh.toByteArray().split('x'); - bool ok = res.size() == 2; - if (ok) { - int w = res.at(0).toInt(&ok); - if (ok) { - int h = res.at(1).toInt(&ok); - if (ok) - m_meshResolution = QSize(w, h); - } - } - if (!ok) - qWarning("ShaderEffect: mesh property must be a size or an object deriving from QQuickShaderEffectMesh"); - } - m_defaultMesh.setResolution(m_meshResolution); - } - - m_dirty |= QSGShaderEffectNode::DirtyShaderMesh; - m_item->update(); - - emit m_item->meshChanged(); -} - -void QQuickGenericShaderEffect::setCullMode(QQuickShaderEffect::CullMode face) -{ - if (m_cullMode == face) - return; - - m_cullMode = face; - m_item->update(); - emit m_item->cullModeChanged(); -} - -void QQuickGenericShaderEffect::setSupportsAtlasTextures(bool supports) -{ - if (m_supportsAtlasTextures == supports) - return; - - m_supportsAtlasTextures = supports; - markGeometryDirtyAndUpdate(); - emit m_item->supportsAtlasTexturesChanged(); -} - -QString QQuickGenericShaderEffect::parseLog() -{ - maybeUpdateShaders(); - return log(); -} - -QString QQuickGenericShaderEffect::log() const -{ - QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); - if (!mgr) - return QString(); - - return mgr->log(); -} - -QQuickShaderEffect::Status QQuickGenericShaderEffect::status() const -{ - QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); - if (!mgr) - return QQuickShaderEffect::Uncompiled; - - return QQuickShaderEffect::Status(mgr->status()); -} - -void QQuickGenericShaderEffect::handleEvent(QEvent *event) -{ - if (event->type() == QEvent::DynamicPropertyChange) { - QDynamicPropertyChangeEvent *e = static_cast<QDynamicPropertyChangeEvent *>(event); - for (int shaderType = 0; shaderType < NShader; ++shaderType) { - const auto &vars(m_shaders[shaderType].shaderInfo.variables); - for (int idx = 0; idx < vars.count(); ++idx) { - if (vars[idx].name == e->propertyName()) { - propertyChanged((shaderType << 16) | idx); - break; - } - } - } - } -} - -void QQuickGenericShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &) -{ - m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; -} - -QSGNode *QQuickGenericShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) -{ - QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode); - - if (m_item->width() <= 0 || m_item->height() <= 0) { - delete node; - return nullptr; - } - - // Do not change anything while a new shader is being reflected or compiled. - if (m_inProgress[Vertex] || m_inProgress[Fragment]) - return node; - - // The manager should be already created on the gui thread. Just take that instance. - QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); - if (!mgr) { - delete node; - return nullptr; - } - - if (!node) { - QSGRenderContext *rc = QQuickWindowPrivate::get(m_item->window())->context; - node = rc->sceneGraphContext()->createShaderEffectNode(rc); - if (!node) { - qWarning("No shader effect node"); - return nullptr; - } - m_dirty = QSGShaderEffectNode::DirtyShaderAll; - connect(node, &QSGShaderEffectNode::textureChanged, - this, &QQuickGenericShaderEffect::markGeometryDirtyAndUpdateIfSupportsAtlas); - } - - QSGShaderEffectNode::SyncData sd; - sd.dirty = m_dirty; - sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode); - sd.blending = m_blending; - sd.vertex.shader = &m_shaders[Vertex]; - sd.vertex.dirtyConstants = &m_dirtyConstants[Vertex]; - sd.vertex.dirtyTextures = &m_dirtyTextures[Vertex]; - sd.fragment.shader = &m_shaders[Fragment]; - sd.fragment.dirtyConstants = &m_dirtyConstants[Fragment]; - sd.fragment.dirtyTextures = &m_dirtyTextures[Fragment]; - node->syncMaterial(&sd); - - if (m_dirty & QSGShaderEffectNode::DirtyShaderMesh) { - node->setGeometry(nullptr); - m_dirty &= ~QSGShaderEffectNode::DirtyShaderMesh; - m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; - } - - if (m_dirty & QSGShaderEffectNode::DirtyShaderGeometry) { - const QRectF rect(0, 0, m_item->width(), m_item->height()); - QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh; - QSGGeometry *geometry = node->geometry(); - - const QRectF srcRect = node->updateNormalizedTextureSubRect(m_supportsAtlasTextures); - geometry = mesh->updateGeometry(geometry, 2, 0, srcRect, rect); - - node->setFlag(QSGNode::OwnsGeometry, false); - node->setGeometry(geometry); - node->setFlag(QSGNode::OwnsGeometry, true); - - m_dirty &= ~QSGShaderEffectNode::DirtyShaderGeometry; - } - - m_dirty = {}; - for (int i = 0; i < NShader; ++i) { - m_dirtyConstants[i].clear(); - m_dirtyTextures[i].clear(); - } - - return node; -} - -void QQuickGenericShaderEffect::maybeUpdateShaders() -{ - if (m_vertNeedsUpdate) - m_vertNeedsUpdate = !updateShader(Vertex, m_vertShader); - if (m_fragNeedsUpdate) - m_fragNeedsUpdate = !updateShader(Fragment, m_fragShader); - if (m_vertNeedsUpdate || m_fragNeedsUpdate) { - // This function is invoked either from componentComplete or in a - // response to a previous invocation's polish() request. If this is - // case #1 then updateShader can fail due to not having a window or - // scenegraph ready. Schedule the polish to try again later. In case #2 - // the backend probably does not have shadereffect support so there is - // nothing to do for us here. - if (!m_item->window() || !m_item->window()->isSceneGraphInitialized()) - m_item->polish(); - } -} - -void QQuickGenericShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) -{ - // Move the window ref. - if (change == QQuickItem::ItemSceneChange) { - for (int shaderType = 0; shaderType < NShader; ++shaderType) { - for (const auto &vd : qAsConst(m_shaders[shaderType].varData)) { - if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { - QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); - if (source) { - if (value.window) - QQuickItemPrivate::get(source)->refWindow(value.window); - else - QQuickItemPrivate::get(source)->derefWindow(); - } - } - } - } - } -} - -QSGGuiThreadShaderEffectManager *QQuickGenericShaderEffect::shaderEffectManager() const -{ - if (!m_mgr) { - // return null if this is not the gui thread and not already created - if (QThread::currentThread() != m_item->thread()) - return m_mgr; - QQuickWindow *w = m_item->window(); - if (w) { // note: just the window, don't care about isSceneGraphInitialized() here - m_mgr = QQuickWindowPrivate::get(w)->context->sceneGraphContext()->createGuiThreadShaderEffectManager(); - if (m_mgr) { - connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(logChanged())); - connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(statusChanged())); - connect(m_mgr, &QSGGuiThreadShaderEffectManager::shaderCodePrepared, this, &QQuickGenericShaderEffect::shaderCodePrepared); - } - } - } - - return m_mgr; -} - -void QQuickGenericShaderEffect::disconnectSignals(Shader shaderType) -{ - for (auto *mapper : m_mappers[shaderType]) { - void *a = mapper; - if (mapper) - QObjectPrivate::disconnect(m_item, mapper->signalIndex(), &a); - } - for (const auto &vd : qAsConst(m_shaders[shaderType].varData)) { - if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { - QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); - if (source) { - if (m_item->window()) - QQuickItemPrivate::get(source)->derefWindow(); - QObject::disconnect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); - } - } - } -} - -void QQuickGenericShaderEffect::clearMappers(QQuickGenericShaderEffect::Shader shaderType) -{ - for (auto *mapper : qAsConst(m_mappers[shaderType])) { - if (mapper) - mapper->destroyIfLastRef(); - } - m_mappers[shaderType].clear(); -} - -static inline QVariant getValueFromProperty(QObject *item, const QMetaObject *itemMetaObject, - const QByteArray &name, int propertyIndex) -{ - QVariant value; - if (propertyIndex == -1) { - value = item->property(name); - } else { - value = itemMetaObject->property(propertyIndex).read(item); - } - return value; -} - -struct ShaderInfoCache -{ - bool contains(const QByteArray &key) const - { - return m_shaderInfoCache.contains(key); - } - - QSGGuiThreadShaderEffectManager::ShaderInfo value(const QByteArray &key) const - { - return m_shaderInfoCache.value(key); - } - - void insert(const QByteArray &key, const QSGGuiThreadShaderEffectManager::ShaderInfo &value) - { - m_shaderInfoCache.insert(key, value); - } - - QHash<QByteArray, QSGGuiThreadShaderEffectManager::ShaderInfo> m_shaderInfoCache; -}; - -Q_GLOBAL_STATIC(ShaderInfoCache, shaderInfoCache) - -bool QQuickGenericShaderEffect::updateShader(Shader shaderType, const QByteArray &src) -{ - QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); - if (!mgr) - return false; - - const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects(); - - disconnectSignals(shaderType); - - m_shaders[shaderType].shaderInfo = QSGGuiThreadShaderEffectManager::ShaderInfo(); - m_shaders[shaderType].varData.clear(); - - if (!src.isEmpty()) { - if (shaderInfoCache()->contains(src)) { - m_shaders[shaderType].shaderInfo = shaderInfoCache()->value(src); - m_shaders[shaderType].hasShaderCode = true; - } else { - // Each prepareShaderCode call needs its own work area, hence the - // dynamic alloc. If there are calls in progress, let those run to - // finish, their results can then simply be ignored because - // m_inProgress indicates what we care about. - m_inProgress[shaderType] = new QSGGuiThreadShaderEffectManager::ShaderInfo; - const QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint = - shaderType == Vertex ? QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex - : QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment; - // Figure out what input parameters and variables are used in the - // shader. For file-based shader source/bytecode this is where the data - // is pulled in from the file. Some backends may choose to do - // source->bytecode compilation as well in this step. - mgr->prepareShaderCode(typeHint, src, m_inProgress[shaderType]); - // the rest is handled in shaderCodePrepared() - return true; - } - } else { - m_shaders[shaderType].hasShaderCode = false; - if (shaderType == Fragment) { - // With built-in shaders hasShaderCode is set to false and all - // metadata is empty, as it is left up to the node to provide a - // built-in default shader and its metadata. However, in case of - // the built-in fragment shader the value for 'source' has to be - // provided and monitored like with an application-provided shader. - QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v; - v.name = QByteArrayLiteral("source"); - v.bindPoint = 0; // fake - v.type = texturesSeparate ? QSGGuiThreadShaderEffectManager::ShaderInfo::Texture - : QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler; - m_shaders[shaderType].shaderInfo.variables.append(v); - } - } - - updateShaderVars(shaderType); - m_dirty |= QSGShaderEffectNode::DirtyShaders; - m_item->update(); - return true; -} - -void QQuickGenericShaderEffect::shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint, - const QByteArray &src, QSGGuiThreadShaderEffectManager::ShaderInfo *result) -{ - const Shader shaderType = typeHint == QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? Vertex : Fragment; - - // If another call was made to updateShader() for the same shader type in - // the meantime then our results are useless, just drop them. - if (result != m_inProgress[shaderType]) { - delete result; - return; - } - - m_shaders[shaderType].shaderInfo = *result; - delete result; - m_inProgress[shaderType] = nullptr; - - if (!ok) { - qWarning("ShaderEffect: shader preparation failed for %s\n%s\n", src.constData(), qPrintable(log())); - m_shaders[shaderType].hasShaderCode = false; - return; - } - - m_shaders[shaderType].hasShaderCode = true; - shaderInfoCache()->insert(src, m_shaders[shaderType].shaderInfo); - updateShaderVars(shaderType); - m_dirty |= QSGShaderEffectNode::DirtyShaders; - m_item->update(); -} - -void QQuickGenericShaderEffect::updateShaderVars(Shader shaderType) -{ - QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); - if (!mgr) - return; - - const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects(); - - const int varCount = m_shaders[shaderType].shaderInfo.variables.count(); - m_shaders[shaderType].varData.resize(varCount); - - // Reuse signal mappers as much as possible since the mapping is based on - // the index and shader type which are both constant. - if (m_mappers[shaderType].count() < varCount) - m_mappers[shaderType].resize(varCount); - - auto *engine = qmlEngine(m_item); - QQmlPropertyCache *propCache = engine ? QQmlData::ensurePropertyCache(engine, m_item) : nullptr; - - if (!m_itemMetaObject) - m_itemMetaObject = m_item->metaObject(); - - // Hook up the signals to get notified about changes for properties that - // correspond to variables in the shader. Store also the values. - for (int i = 0; i < varCount; ++i) { - const auto &v(m_shaders[shaderType].shaderInfo.variables.at(i)); - QSGShaderEffectNode::VariableData &vd(m_shaders[shaderType].varData[i]); - const bool isSpecial = v.name.startsWith("qt_"); // special names not mapped to properties - if (isSpecial) { - if (v.name == "qt_Opacity") - vd.specialType = QSGShaderEffectNode::VariableData::Opacity; - else if (v.name == "qt_Matrix") - vd.specialType = QSGShaderEffectNode::VariableData::Matrix; - else if (v.name.startsWith("qt_SubRect_")) - vd.specialType = QSGShaderEffectNode::VariableData::SubRect; - continue; - } - - // The value of a property corresponding to a sampler is the source - // item ref, unless there are separate texture objects in which case - // the sampler is ignored (here). - if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) { - if (texturesSeparate) { - vd.specialType = QSGShaderEffectNode::VariableData::Unused; - continue; - } else { - vd.specialType = QSGShaderEffectNode::VariableData::Source; - } - } else if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) { - Q_ASSERT(texturesSeparate); - vd.specialType = QSGShaderEffectNode::VariableData::Source; - } else { - vd.specialType = QSGShaderEffectNode::VariableData::None; - } - - // Find the property on the ShaderEffect item. - int propIdx = -1; - QQmlPropertyData *pd = nullptr; - if (propCache) { - pd = propCache->property(QLatin1String(v.name), nullptr, nullptr); - if (pd) { - if (!pd->isFunction()) - propIdx = pd->coreIndex(); - } - } - if (propIdx >= 0) { - if (pd && !pd->isFunction()) { - if (pd->notifyIndex() == -1) { - qWarning("QQuickGenericShaderEffect: property '%s' does not have notification method!", v.name.constData()); - } else { - auto *&mapper = m_mappers[shaderType][i]; - if (!mapper) { - const int mappedId = i | (shaderType << 16); - mapper = new QtPrivate::EffectSlotMapper([this, mappedId](){ - this->propertyChanged(mappedId); - }); - } - mapper->setSignalIndex(m_itemMetaObject->property(propIdx).notifySignal().methodIndex()); - Q_ASSERT(m_item->metaObject() == m_itemMetaObject); - bool ok = QObjectPrivate::connectImpl(m_item, pd->notifyIndex(), m_item, nullptr, mapper, - Qt::AutoConnection, nullptr, m_itemMetaObject); - if (!ok) - qWarning() << "Failed to connect to property" << m_itemMetaObject->property(propIdx).name() - << "(" << propIdx << ", signal index" << pd->notifyIndex() - << ") of item" << m_item; - } - } - } else { - // Do not warn for dynamic properties. - if (!m_item->property(v.name.constData()).isValid()) - qWarning("ShaderEffect: '%s' does not have a matching property", v.name.constData()); - } - - - vd.propertyIndex = propIdx; - vd.value = getValueFromProperty(m_item, m_itemMetaObject, v.name, vd.propertyIndex); - if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { - QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); - if (source) { - if (m_item->window()) - QQuickItemPrivate::get(source)->refWindow(m_item->window()); - QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); - } - } - } -} - -bool QQuickGenericShaderEffect::sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const -{ - for (int shaderType = 0; shaderType < NShader; ++shaderType) { - for (int idx = 0; idx < m_shaders[shaderType].varData.count(); ++idx) { - if (shaderType != typeToSkip || idx != indexToSkip) { - const auto &vd(m_shaders[shaderType].varData[idx]); - if (vd.specialType == QSGShaderEffectNode::VariableData::Source && qvariant_cast<QObject *>(vd.value) == source) - return false; - } - } - } - return true; -} - -void QQuickGenericShaderEffect::propertyChanged(int mappedId) -{ - const Shader type = Shader(mappedId >> 16); - const int idx = mappedId & 0xFFFF; - const auto &v(m_shaders[type].shaderInfo.variables[idx]); - auto &vd(m_shaders[type].varData[idx]); - - vd.value = getValueFromProperty(m_item, m_itemMetaObject, v.name, vd.propertyIndex); - - if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { - QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); - if (source) { - if (m_item->window()) - QQuickItemPrivate::get(source)->derefWindow(); - // QObject::disconnect() will disconnect all matching connections. - // If the same source has been attached to two separate - // textures/samplers, then changing one of them would trigger both - // to be disconnected. So check first. - if (sourceIsUnique(source, type, idx)) - QObject::disconnect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); - } - - source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); - if (source) { - // 'source' needs a window to get a scene graph node. It usually gets one through its - // parent, but if the source item is "inline" rather than a reference -- i.e. - // "property variant source: Image { }" instead of "property variant source: foo" -- it - // will not get a parent. In those cases, 'source' should get the window from 'item'. - if (m_item->window()) - QQuickItemPrivate::get(source)->refWindow(m_item->window()); - QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); - } - - m_dirty |= QSGShaderEffectNode::DirtyShaderTexture; - m_dirtyTextures[type].insert(idx); - - } else { - m_dirty |= QSGShaderEffectNode::DirtyShaderConstant; - m_dirtyConstants[type].insert(idx); - } - - m_item->update(); -} - -void QQuickGenericShaderEffect::sourceDestroyed(QObject *object) -{ - for (int shaderType = 0; shaderType < NShader; ++shaderType) { - for (auto &vd : m_shaders[shaderType].varData) { - if (vd.specialType == QSGShaderEffectNode::VariableData::Source && vd.value.canConvert<QObject *>()) { - if (qvariant_cast<QObject *>(vd.value) == object) - vd.value = QVariant(); - } - } - } -} - -void QQuickGenericShaderEffect::markGeometryDirtyAndUpdate() -{ - m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; - m_item->update(); -} - -void QQuickGenericShaderEffect::markGeometryDirtyAndUpdateIfSupportsAtlas() -{ - if (m_supportsAtlasTextures) - markGeometryDirtyAndUpdate(); -} - -QT_END_NAMESPACE - -#include "moc_qquickgenericshadereffect_p.cpp" diff --git a/src/quick/items/qquickgenericshadereffect_p.h b/src/quick/items/qquickgenericshadereffect_p.h deleted file mode 100644 index 076131f8e1..0000000000 --- a/src/quick/items/qquickgenericshadereffect_p.h +++ /dev/null @@ -1,154 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef QQUICKGENERICSHADEREFFECT_P_H -#define QQUICKGENERICSHADEREFFECT_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <QtQuick/qquickitem.h> -#include <private/qtquickglobal_p.h> -#include <private/qsgadaptationlayer_p.h> -#include "qquickshadereffect_p.h" -#include "qquickshadereffectmesh_p.h" - -#include <functional> - -QT_BEGIN_NAMESPACE - -namespace QtPrivate { -class EffectSlotMapper; -} - -class Q_QUICK_PRIVATE_EXPORT QQuickGenericShaderEffect : public QObject -{ - Q_OBJECT - -public: - QQuickGenericShaderEffect(QQuickShaderEffect *item, QObject *parent = nullptr); - ~QQuickGenericShaderEffect(); - - QByteArray fragmentShader() const { return m_fragShader; } - void setFragmentShader(const QByteArray &src); - - QByteArray vertexShader() const { return m_vertShader; } - void setVertexShader(const QByteArray &src); - - bool blending() const { return m_blending; } - void setBlending(bool enable); - - QVariant mesh() const; - void setMesh(const QVariant &mesh); - - QQuickShaderEffect::CullMode cullMode() const { return m_cullMode; } - void setCullMode(QQuickShaderEffect::CullMode face); - - QString log() const; - QQuickShaderEffect::Status status() const; - - bool supportsAtlasTextures() const { return m_supportsAtlasTextures; } - void setSupportsAtlasTextures(bool supports); - - QString parseLog(); - - void handleEvent(QEvent *); - void handleGeometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); - QSGNode *handleUpdatePaintNode(QSGNode *, QQuickItem::UpdatePaintNodeData *); - void handleComponentComplete(); - void handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value); - void maybeUpdateShaders(); - -private slots: - void propertyChanged(int mappedId); - void sourceDestroyed(QObject *object); - void markGeometryDirtyAndUpdate(); - void markGeometryDirtyAndUpdateIfSupportsAtlas(); - void shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint, - const QByteArray &src, QSGGuiThreadShaderEffectManager::ShaderInfo *result); - -private: - QSGGuiThreadShaderEffectManager *shaderEffectManager() const; - - enum Shader { - Vertex, - Fragment, - - NShader - }; - bool updateShader(Shader shaderType, const QByteArray &src); - void updateShaderVars(Shader shaderType); - void disconnectSignals(Shader shaderType); - void clearMappers(Shader shaderType); - bool sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const; - - QQuickShaderEffect *m_item; - const QMetaObject *m_itemMetaObject = nullptr; - QSize m_meshResolution; - QQuickShaderEffectMesh *m_mesh; - QQuickGridMesh m_defaultMesh; - QQuickShaderEffect::CullMode m_cullMode; - bool m_blending; - bool m_supportsAtlasTextures; - mutable QSGGuiThreadShaderEffectManager *m_mgr; - QByteArray m_fragShader; - bool m_fragNeedsUpdate; - QByteArray m_vertShader; - bool m_vertNeedsUpdate; - - QSGShaderEffectNode::ShaderData m_shaders[NShader]; - QSGShaderEffectNode::DirtyShaderFlags m_dirty; - QSet<int> m_dirtyConstants[NShader]; - QSet<int> m_dirtyTextures[NShader]; - QSGGuiThreadShaderEffectManager::ShaderInfo *m_inProgress[NShader]; - - QVector<QtPrivate::EffectSlotMapper*> m_mappers[NShader]; -}; - -QT_END_NAMESPACE - -#endif // QQUICKGENERICSHADEREFFECT_P_H diff --git a/src/quick/items/qquickshadereffect.cpp b/src/quick/items/qquickshadereffect.cpp index 7a37410faa..c4c48fdea5 100644 --- a/src/quick/items/qquickshadereffect.cpp +++ b/src/quick/items/qquickshadereffect.cpp @@ -40,11 +40,14 @@ #include <private/qquickshadereffect_p.h> #include <private/qsgcontextplugin_p.h> #include <private/qquickitem_p.h> -#include <private/qquickgenericshadereffect_p.h> #if QT_CONFIG(opengl) /* || QT_CONFIG(vulkan) || defined(Q_OS_WIN) || defined(Q_OS_DARWIN) */ #include <private/qsgrhisupport_p.h> #endif +#include <private/qquickwindow_p.h> +#include <private/qquickitem_p.h> +#include "qquickshadereffectmesh_p.h" + QT_BEGIN_NAMESPACE /*! @@ -284,6 +287,128 @@ QT_BEGIN_NAMESPACE \sa {Item Layers} */ + +namespace QtPrivate { +class EffectSlotMapper: public QtPrivate::QSlotObjectBase +{ +public: + typedef std::function<void()> PropChangedFunc; + + explicit EffectSlotMapper(PropChangedFunc func) + : QSlotObjectBase(&impl), _signalIndex(-1), func(func) + { ref(); } + + void setSignalIndex(int idx) { _signalIndex = idx; } + int signalIndex() const { return _signalIndex; } + +private: + int _signalIndex; + PropChangedFunc func; + + static void impl(int which, QSlotObjectBase *this_, QObject *, void **a, bool *ret) + { + auto thiz = static_cast<EffectSlotMapper*>(this_); + switch (which) { + case Destroy: + delete thiz; + break; + case Call: + thiz->func(); + break; + case Compare: + *ret = thiz == reinterpret_cast<EffectSlotMapper *>(a[0]); + break; + case NumOperations: ; + } + } +}; +} // namespace QtPrivate + +class QQuickShaderEffectImpl : public QObject +{ + Q_OBJECT + +public: + QQuickShaderEffectImpl(QQuickShaderEffect *item); + ~QQuickShaderEffectImpl(); + + QByteArray fragmentShader() const { return m_fragShader; } + void setFragmentShader(const QByteArray &src); + + QByteArray vertexShader() const { return m_vertShader; } + void setVertexShader(const QByteArray &src); + + bool blending() const { return m_blending; } + void setBlending(bool enable); + + QVariant mesh() const; + void setMesh(const QVariant &mesh); + + QQuickShaderEffect::CullMode cullMode() const { return m_cullMode; } + void setCullMode(QQuickShaderEffect::CullMode face); + + QString log() const; + QQuickShaderEffect::Status status() const; + + bool supportsAtlasTextures() const { return m_supportsAtlasTextures; } + void setSupportsAtlasTextures(bool supports); + + QString parseLog(); + + void handleEvent(QEvent *); + void handleGeometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + QSGNode *handleUpdatePaintNode(QSGNode *, QQuickItem::UpdatePaintNodeData *); + void handleComponentComplete(); + void handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value); + void maybeUpdateShaders(); + +private slots: + void propertyChanged(int mappedId); + void sourceDestroyed(QObject *object); + void markGeometryDirtyAndUpdate(); + void markGeometryDirtyAndUpdateIfSupportsAtlas(); + void shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint, + const QByteArray &src, QSGGuiThreadShaderEffectManager::ShaderInfo *result); + +private: + QSGGuiThreadShaderEffectManager *shaderEffectManager() const; + + enum Shader { + Vertex, + Fragment, + + NShader + }; + bool updateShader(Shader shaderType, const QByteArray &src); + void updateShaderVars(Shader shaderType); + void disconnectSignals(Shader shaderType); + void clearMappers(Shader shaderType); + bool sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const; + + QQuickShaderEffect *m_item; + const QMetaObject *m_itemMetaObject = nullptr; + QSize m_meshResolution; + QQuickShaderEffectMesh *m_mesh; + QQuickGridMesh m_defaultMesh; + QQuickShaderEffect::CullMode m_cullMode; + bool m_blending; + bool m_supportsAtlasTextures; + mutable QSGGuiThreadShaderEffectManager *m_mgr; + QByteArray m_fragShader; + bool m_fragNeedsUpdate; + QByteArray m_vertShader; + bool m_vertNeedsUpdate; + + QSGShaderEffectNode::ShaderData m_shaders[NShader]; + QSGShaderEffectNode::DirtyShaderFlags m_dirty; + QSet<int> m_dirtyConstants[NShader]; + QSet<int> m_dirtyTextures[NShader]; + QSGGuiThreadShaderEffectManager::ShaderInfo *m_inProgress[NShader]; + + QVector<QtPrivate::EffectSlotMapper*> m_mappers[NShader]; +}; + + class QQuickShaderEffectPrivate : public QQuickItemPrivate { Q_DECLARE_PUBLIC(QQuickShaderEffect) @@ -300,7 +425,7 @@ QQuickShaderEffect::QQuickShaderEffect(QQuickItem *parent) { setFlag(QQuickItem::ItemHasContents); - m_impl = new QQuickGenericShaderEffect(this, this); + m_impl = new QQuickShaderEffectImpl(this); } QQuickShaderEffect::~QQuickShaderEffect() @@ -558,6 +683,657 @@ void QQuickShaderEffectPrivate::updatePolish() q->m_impl->maybeUpdateShaders(); } +QQuickShaderEffectImpl::QQuickShaderEffectImpl(QQuickShaderEffect *item) + : QObject(item) + , m_item(item) + , m_meshResolution(1, 1) + , m_mesh(nullptr) + , m_cullMode(QQuickShaderEffect::NoCulling) + , m_blending(true) + , m_supportsAtlasTextures(false) + , m_mgr(nullptr) + , m_fragNeedsUpdate(true) + , m_vertNeedsUpdate(true) +{ + qRegisterMetaType<QSGGuiThreadShaderEffectManager::ShaderInfo::Type>("ShaderInfo::Type"); + for (int i = 0; i < NShader; ++i) + m_inProgress[i] = nullptr; +} + +QQuickShaderEffectImpl::~QQuickShaderEffectImpl() +{ + for (int i = 0; i < NShader; ++i) { + disconnectSignals(Shader(i)); + clearMappers(Shader(i)); + } + + delete m_mgr; +} + +void QQuickShaderEffectImpl::setFragmentShader(const QByteArray &src) +{ + // Compare the actual values since they are often just filenames. + // Optimizing by comparing constData() is a bad idea since seemingly static + // strings in QML may in fact have different addresses when a binding + // triggers assigning the "same" value to the property. + if (m_fragShader == src) + return; + + m_fragShader = src; + + m_fragNeedsUpdate = true; + if (m_item->isComponentComplete()) + maybeUpdateShaders(); + + emit m_item->fragmentShaderChanged(); +} + +void QQuickShaderEffectImpl::setVertexShader(const QByteArray &src) +{ + if (m_vertShader == src) + return; + + m_vertShader = src; + + m_vertNeedsUpdate = true; + if (m_item->isComponentComplete()) + maybeUpdateShaders(); + + emit m_item->vertexShaderChanged(); +} + +void QQuickShaderEffectImpl::setBlending(bool enable) +{ + if (m_blending == enable) + return; + + m_blending = enable; + m_item->update(); + emit m_item->blendingChanged(); +} + +QVariant QQuickShaderEffectImpl::mesh() const +{ + return m_mesh ? QVariant::fromValue(static_cast<QObject *>(m_mesh)) + : QVariant::fromValue(m_meshResolution); +} + +void QQuickShaderEffectImpl::setMesh(const QVariant &mesh) +{ + QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qvariant_cast<QObject *>(mesh)); + if (newMesh && newMesh == m_mesh) + return; + + if (m_mesh) + disconnect(m_mesh, SIGNAL(geometryChanged()), this, nullptr); + + m_mesh = newMesh; + + if (m_mesh) { + connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(markGeometryDirtyAndUpdate())); + } else { + if (mesh.canConvert<QSize>()) { + m_meshResolution = mesh.toSize(); + } else { + QList<QByteArray> res = mesh.toByteArray().split('x'); + bool ok = res.size() == 2; + if (ok) { + int w = res.at(0).toInt(&ok); + if (ok) { + int h = res.at(1).toInt(&ok); + if (ok) + m_meshResolution = QSize(w, h); + } + } + if (!ok) + qWarning("ShaderEffect: mesh property must be a size or an object deriving from QQuickShaderEffectMesh"); + } + m_defaultMesh.setResolution(m_meshResolution); + } + + m_dirty |= QSGShaderEffectNode::DirtyShaderMesh; + m_item->update(); + + emit m_item->meshChanged(); +} + +void QQuickShaderEffectImpl::setCullMode(QQuickShaderEffect::CullMode face) +{ + if (m_cullMode == face) + return; + + m_cullMode = face; + m_item->update(); + emit m_item->cullModeChanged(); +} + +void QQuickShaderEffectImpl::setSupportsAtlasTextures(bool supports) +{ + if (m_supportsAtlasTextures == supports) + return; + + m_supportsAtlasTextures = supports; + markGeometryDirtyAndUpdate(); + emit m_item->supportsAtlasTexturesChanged(); +} + +QString QQuickShaderEffectImpl::parseLog() +{ + maybeUpdateShaders(); + return log(); +} + +QString QQuickShaderEffectImpl::log() const +{ + QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); + if (!mgr) + return QString(); + + return mgr->log(); +} + +QQuickShaderEffect::Status QQuickShaderEffectImpl::status() const +{ + QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); + if (!mgr) + return QQuickShaderEffect::Uncompiled; + + return QQuickShaderEffect::Status(mgr->status()); +} + +void QQuickShaderEffectImpl::handleEvent(QEvent *event) +{ + if (event->type() == QEvent::DynamicPropertyChange) { + QDynamicPropertyChangeEvent *e = static_cast<QDynamicPropertyChangeEvent *>(event); + for (int shaderType = 0; shaderType < NShader; ++shaderType) { + const auto &vars(m_shaders[shaderType].shaderInfo.variables); + for (int idx = 0; idx < vars.count(); ++idx) { + if (vars[idx].name == e->propertyName()) { + propertyChanged((shaderType << 16) | idx); + break; + } + } + } + } +} + +void QQuickShaderEffectImpl::handleGeometryChanged(const QRectF &, const QRectF &) +{ + m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; +} + +QSGNode *QQuickShaderEffectImpl::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) +{ + QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode); + + if (m_item->width() <= 0 || m_item->height() <= 0) { + delete node; + return nullptr; + } + + // Do not change anything while a new shader is being reflected or compiled. + if (m_inProgress[Vertex] || m_inProgress[Fragment]) + return node; + + // The manager should be already created on the gui thread. Just take that instance. + QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); + if (!mgr) { + delete node; + return nullptr; + } + + if (!node) { + QSGRenderContext *rc = QQuickWindowPrivate::get(m_item->window())->context; + node = rc->sceneGraphContext()->createShaderEffectNode(rc); + if (!node) { + qWarning("No shader effect node"); + return nullptr; + } + m_dirty = QSGShaderEffectNode::DirtyShaderAll; + connect(node, &QSGShaderEffectNode::textureChanged, + this, &QQuickShaderEffectImpl::markGeometryDirtyAndUpdateIfSupportsAtlas); + } + + QSGShaderEffectNode::SyncData sd; + sd.dirty = m_dirty; + sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode); + sd.blending = m_blending; + sd.vertex.shader = &m_shaders[Vertex]; + sd.vertex.dirtyConstants = &m_dirtyConstants[Vertex]; + sd.vertex.dirtyTextures = &m_dirtyTextures[Vertex]; + sd.fragment.shader = &m_shaders[Fragment]; + sd.fragment.dirtyConstants = &m_dirtyConstants[Fragment]; + sd.fragment.dirtyTextures = &m_dirtyTextures[Fragment]; + node->syncMaterial(&sd); + + if (m_dirty & QSGShaderEffectNode::DirtyShaderMesh) { + node->setGeometry(nullptr); + m_dirty &= ~QSGShaderEffectNode::DirtyShaderMesh; + m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; + } + + if (m_dirty & QSGShaderEffectNode::DirtyShaderGeometry) { + const QRectF rect(0, 0, m_item->width(), m_item->height()); + QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh; + QSGGeometry *geometry = node->geometry(); + + const QRectF srcRect = node->updateNormalizedTextureSubRect(m_supportsAtlasTextures); + geometry = mesh->updateGeometry(geometry, 2, 0, srcRect, rect); + + node->setFlag(QSGNode::OwnsGeometry, false); + node->setGeometry(geometry); + node->setFlag(QSGNode::OwnsGeometry, true); + + m_dirty &= ~QSGShaderEffectNode::DirtyShaderGeometry; + } + + m_dirty = {}; + for (int i = 0; i < NShader; ++i) { + m_dirtyConstants[i].clear(); + m_dirtyTextures[i].clear(); + } + + return node; +} + +void QQuickShaderEffectImpl::maybeUpdateShaders() +{ + if (m_vertNeedsUpdate) + m_vertNeedsUpdate = !updateShader(Vertex, m_vertShader); + if (m_fragNeedsUpdate) + m_fragNeedsUpdate = !updateShader(Fragment, m_fragShader); + if (m_vertNeedsUpdate || m_fragNeedsUpdate) { + // This function is invoked either from componentComplete or in a + // response to a previous invocation's polish() request. If this is + // case #1 then updateShader can fail due to not having a window or + // scenegraph ready. Schedule the polish to try again later. In case #2 + // the backend probably does not have shadereffect support so there is + // nothing to do for us here. + if (!m_item->window() || !m_item->window()->isSceneGraphInitialized()) + m_item->polish(); + } +} + +void QQuickShaderEffectImpl::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + // Move the window ref. + if (change == QQuickItem::ItemSceneChange) { + for (int shaderType = 0; shaderType < NShader; ++shaderType) { + for (const auto &vd : qAsConst(m_shaders[shaderType].varData)) { + if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { + QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); + if (source) { + if (value.window) + QQuickItemPrivate::get(source)->refWindow(value.window); + else + QQuickItemPrivate::get(source)->derefWindow(); + } + } + } + } + } +} + +QSGGuiThreadShaderEffectManager *QQuickShaderEffectImpl::shaderEffectManager() const +{ + if (!m_mgr) { + // return null if this is not the gui thread and not already created + if (QThread::currentThread() != m_item->thread()) + return m_mgr; + QQuickWindow *w = m_item->window(); + if (w) { // note: just the window, don't care about isSceneGraphInitialized() here + m_mgr = QQuickWindowPrivate::get(w)->context->sceneGraphContext()->createGuiThreadShaderEffectManager(); + if (m_mgr) { + connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(logChanged())); + connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(statusChanged())); + connect(m_mgr, &QSGGuiThreadShaderEffectManager::shaderCodePrepared, this, &QQuickShaderEffectImpl::shaderCodePrepared); + } + } + } + + return m_mgr; +} + +void QQuickShaderEffectImpl::disconnectSignals(Shader shaderType) +{ + for (auto *mapper : m_mappers[shaderType]) { + void *a = mapper; + if (mapper) + QObjectPrivate::disconnect(m_item, mapper->signalIndex(), &a); + } + for (const auto &vd : qAsConst(m_shaders[shaderType].varData)) { + if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { + QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); + if (source) { + if (m_item->window()) + QQuickItemPrivate::get(source)->derefWindow(); + QObject::disconnect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); + } + } + } +} + +void QQuickShaderEffectImpl::clearMappers(QQuickShaderEffectImpl::Shader shaderType) +{ + for (auto *mapper : qAsConst(m_mappers[shaderType])) { + if (mapper) + mapper->destroyIfLastRef(); + } + m_mappers[shaderType].clear(); +} + +static inline QVariant getValueFromProperty(QObject *item, const QMetaObject *itemMetaObject, + const QByteArray &name, int propertyIndex) +{ + QVariant value; + if (propertyIndex == -1) { + value = item->property(name); + } else { + value = itemMetaObject->property(propertyIndex).read(item); + } + return value; +} + +struct ShaderInfoCache +{ + bool contains(const QByteArray &key) const + { + return m_shaderInfoCache.contains(key); + } + + QSGGuiThreadShaderEffectManager::ShaderInfo value(const QByteArray &key) const + { + return m_shaderInfoCache.value(key); + } + + void insert(const QByteArray &key, const QSGGuiThreadShaderEffectManager::ShaderInfo &value) + { + m_shaderInfoCache.insert(key, value); + } + + QHash<QByteArray, QSGGuiThreadShaderEffectManager::ShaderInfo> m_shaderInfoCache; +}; + +Q_GLOBAL_STATIC(ShaderInfoCache, shaderInfoCache) + +bool QQuickShaderEffectImpl::updateShader(Shader shaderType, const QByteArray &src) +{ + QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); + if (!mgr) + return false; + + const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects(); + + disconnectSignals(shaderType); + + m_shaders[shaderType].shaderInfo = QSGGuiThreadShaderEffectManager::ShaderInfo(); + m_shaders[shaderType].varData.clear(); + + if (!src.isEmpty()) { + if (shaderInfoCache()->contains(src)) { + m_shaders[shaderType].shaderInfo = shaderInfoCache()->value(src); + m_shaders[shaderType].hasShaderCode = true; + } else { + // Each prepareShaderCode call needs its own work area, hence the + // dynamic alloc. If there are calls in progress, let those run to + // finish, their results can then simply be ignored because + // m_inProgress indicates what we care about. + m_inProgress[shaderType] = new QSGGuiThreadShaderEffectManager::ShaderInfo; + const QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint = + shaderType == Vertex ? QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex + : QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment; + // Figure out what input parameters and variables are used in the + // shader. For file-based shader source/bytecode this is where the data + // is pulled in from the file. Some backends may choose to do + // source->bytecode compilation as well in this step. + mgr->prepareShaderCode(typeHint, src, m_inProgress[shaderType]); + // the rest is handled in shaderCodePrepared() + return true; + } + } else { + m_shaders[shaderType].hasShaderCode = false; + if (shaderType == Fragment) { + // With built-in shaders hasShaderCode is set to false and all + // metadata is empty, as it is left up to the node to provide a + // built-in default shader and its metadata. However, in case of + // the built-in fragment shader the value for 'source' has to be + // provided and monitored like with an application-provided shader. + QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v; + v.name = QByteArrayLiteral("source"); + v.bindPoint = 0; // fake + v.type = texturesSeparate ? QSGGuiThreadShaderEffectManager::ShaderInfo::Texture + : QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler; + m_shaders[shaderType].shaderInfo.variables.append(v); + } + } + + updateShaderVars(shaderType); + m_dirty |= QSGShaderEffectNode::DirtyShaders; + m_item->update(); + return true; +} + +void QQuickShaderEffectImpl::shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint, + const QByteArray &src, QSGGuiThreadShaderEffectManager::ShaderInfo *result) +{ + const Shader shaderType = typeHint == QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? Vertex : Fragment; + + // If another call was made to updateShader() for the same shader type in + // the meantime then our results are useless, just drop them. + if (result != m_inProgress[shaderType]) { + delete result; + return; + } + + m_shaders[shaderType].shaderInfo = *result; + delete result; + m_inProgress[shaderType] = nullptr; + + if (!ok) { + qWarning("ShaderEffect: shader preparation failed for %s\n%s\n", src.constData(), qPrintable(log())); + m_shaders[shaderType].hasShaderCode = false; + return; + } + + m_shaders[shaderType].hasShaderCode = true; + shaderInfoCache()->insert(src, m_shaders[shaderType].shaderInfo); + updateShaderVars(shaderType); + m_dirty |= QSGShaderEffectNode::DirtyShaders; + m_item->update(); +} + +void QQuickShaderEffectImpl::updateShaderVars(Shader shaderType) +{ + QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager(); + if (!mgr) + return; + + const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects(); + + const int varCount = m_shaders[shaderType].shaderInfo.variables.count(); + m_shaders[shaderType].varData.resize(varCount); + + // Reuse signal mappers as much as possible since the mapping is based on + // the index and shader type which are both constant. + if (m_mappers[shaderType].count() < varCount) + m_mappers[shaderType].resize(varCount); + + auto *engine = qmlEngine(m_item); + QQmlPropertyCache *propCache = engine ? QQmlData::ensurePropertyCache(engine, m_item) : nullptr; + + if (!m_itemMetaObject) + m_itemMetaObject = m_item->metaObject(); + + // Hook up the signals to get notified about changes for properties that + // correspond to variables in the shader. Store also the values. + for (int i = 0; i < varCount; ++i) { + const auto &v(m_shaders[shaderType].shaderInfo.variables.at(i)); + QSGShaderEffectNode::VariableData &vd(m_shaders[shaderType].varData[i]); + const bool isSpecial = v.name.startsWith("qt_"); // special names not mapped to properties + if (isSpecial) { + if (v.name == "qt_Opacity") + vd.specialType = QSGShaderEffectNode::VariableData::Opacity; + else if (v.name == "qt_Matrix") + vd.specialType = QSGShaderEffectNode::VariableData::Matrix; + else if (v.name.startsWith("qt_SubRect_")) + vd.specialType = QSGShaderEffectNode::VariableData::SubRect; + continue; + } + + // The value of a property corresponding to a sampler is the source + // item ref, unless there are separate texture objects in which case + // the sampler is ignored (here). + if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) { + if (texturesSeparate) { + vd.specialType = QSGShaderEffectNode::VariableData::Unused; + continue; + } else { + vd.specialType = QSGShaderEffectNode::VariableData::Source; + } + } else if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) { + Q_ASSERT(texturesSeparate); + vd.specialType = QSGShaderEffectNode::VariableData::Source; + } else { + vd.specialType = QSGShaderEffectNode::VariableData::None; + } + + // Find the property on the ShaderEffect item. + int propIdx = -1; + QQmlPropertyData *pd = nullptr; + if (propCache) { + pd = propCache->property(QLatin1String(v.name), nullptr, nullptr); + if (pd) { + if (!pd->isFunction()) + propIdx = pd->coreIndex(); + } + } + if (propIdx >= 0) { + if (pd && !pd->isFunction()) { + if (pd->notifyIndex() == -1) { + qWarning("QQuickShaderEffect: property '%s' does not have notification method!", v.name.constData()); + } else { + auto *&mapper = m_mappers[shaderType][i]; + if (!mapper) { + const int mappedId = i | (shaderType << 16); + mapper = new QtPrivate::EffectSlotMapper([this, mappedId](){ + this->propertyChanged(mappedId); + }); + } + mapper->setSignalIndex(m_itemMetaObject->property(propIdx).notifySignal().methodIndex()); + Q_ASSERT(m_item->metaObject() == m_itemMetaObject); + bool ok = QObjectPrivate::connectImpl(m_item, pd->notifyIndex(), m_item, nullptr, mapper, + Qt::AutoConnection, nullptr, m_itemMetaObject); + if (!ok) + qWarning() << "Failed to connect to property" << m_itemMetaObject->property(propIdx).name() + << "(" << propIdx << ", signal index" << pd->notifyIndex() + << ") of item" << m_item; + } + } + } else { + // Do not warn for dynamic properties. + if (!m_item->property(v.name.constData()).isValid()) + qWarning("ShaderEffect: '%s' does not have a matching property", v.name.constData()); + } + + + vd.propertyIndex = propIdx; + vd.value = getValueFromProperty(m_item, m_itemMetaObject, v.name, vd.propertyIndex); + if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { + QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); + if (source) { + if (m_item->window()) + QQuickItemPrivate::get(source)->refWindow(m_item->window()); + QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); + } + } + } +} + +bool QQuickShaderEffectImpl::sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const +{ + for (int shaderType = 0; shaderType < NShader; ++shaderType) { + for (int idx = 0; idx < m_shaders[shaderType].varData.count(); ++idx) { + if (shaderType != typeToSkip || idx != indexToSkip) { + const auto &vd(m_shaders[shaderType].varData[idx]); + if (vd.specialType == QSGShaderEffectNode::VariableData::Source && qvariant_cast<QObject *>(vd.value) == source) + return false; + } + } + } + return true; +} + +void QQuickShaderEffectImpl::propertyChanged(int mappedId) +{ + const Shader type = Shader(mappedId >> 16); + const int idx = mappedId & 0xFFFF; + const auto &v(m_shaders[type].shaderInfo.variables[idx]); + auto &vd(m_shaders[type].varData[idx]); + + vd.value = getValueFromProperty(m_item, m_itemMetaObject, v.name, vd.propertyIndex); + + if (vd.specialType == QSGShaderEffectNode::VariableData::Source) { + QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); + if (source) { + if (m_item->window()) + QQuickItemPrivate::get(source)->derefWindow(); + // QObject::disconnect() will disconnect all matching connections. + // If the same source has been attached to two separate + // textures/samplers, then changing one of them would trigger both + // to be disconnected. So check first. + if (sourceIsUnique(source, type, idx)) + QObject::disconnect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); + } + + source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value)); + if (source) { + // 'source' needs a window to get a scene graph node. It usually gets one through its + // parent, but if the source item is "inline" rather than a reference -- i.e. + // "property variant source: Image { }" instead of "property variant source: foo" -- it + // will not get a parent. In those cases, 'source' should get the window from 'item'. + if (m_item->window()) + QQuickItemPrivate::get(source)->refWindow(m_item->window()); + QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*))); + } + + m_dirty |= QSGShaderEffectNode::DirtyShaderTexture; + m_dirtyTextures[type].insert(idx); + + } else { + m_dirty |= QSGShaderEffectNode::DirtyShaderConstant; + m_dirtyConstants[type].insert(idx); + } + + m_item->update(); +} + +void QQuickShaderEffectImpl::sourceDestroyed(QObject *object) +{ + for (int shaderType = 0; shaderType < NShader; ++shaderType) { + for (auto &vd : m_shaders[shaderType].varData) { + if (vd.specialType == QSGShaderEffectNode::VariableData::Source && vd.value.canConvert<QObject *>()) { + if (qvariant_cast<QObject *>(vd.value) == object) + vd.value = QVariant(); + } + } + } +} + +void QQuickShaderEffectImpl::markGeometryDirtyAndUpdate() +{ + m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry; + m_item->update(); +} + +void QQuickShaderEffectImpl::markGeometryDirtyAndUpdateIfSupportsAtlas() +{ + if (m_supportsAtlasTextures) + markGeometryDirtyAndUpdate(); +} + + + QT_END_NAMESPACE #include "moc_qquickshadereffect_p.cpp" +#include "qquickshadereffect.moc" diff --git a/src/quick/items/qquickshadereffect_p.h b/src/quick/items/qquickshadereffect_p.h index 504a1efcf1..b895353272 100644 --- a/src/quick/items/qquickshadereffect_p.h +++ b/src/quick/items/qquickshadereffect_p.h @@ -60,7 +60,7 @@ QT_REQUIRE_CONFIG(quick_shadereffect); QT_BEGIN_NAMESPACE -class QQuickGenericShaderEffect; +class QQuickShaderEffectImpl; class QQuickShaderEffectPrivate; class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffect : public QQuickItem @@ -137,7 +137,7 @@ protected: void itemChange(ItemChange change, const ItemChangeData &value) override; private: - QQuickGenericShaderEffect *m_impl; + QQuickShaderEffectImpl *m_impl; Q_DECLARE_PRIVATE(QQuickShaderEffect) }; |