aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick')
-rw-r--r--src/quick/items/qquickgenericshadereffect.cpp473
-rw-r--r--src/quick/items/qquickgenericshadereffect_p.h57
-rw-r--r--src/quick/items/qquickitemsmodule.cpp2
-rw-r--r--src/quick/items/qquickopenglshadereffect.cpp8
-rw-r--r--src/quick/items/qquickshadereffect.cpp319
-rw-r--r--src/quick/items/qquickshadereffect_p.h30
-rw-r--r--src/quick/items/qquickshadereffectmesh.cpp76
-rw-r--r--src/quick/items/qquickshadereffectmesh_p.h10
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwareadaptation.cpp5
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendererinterface.cpp9
-rw-r--r--src/quick/scenegraph/qsgadaptationlayer.cpp26
-rw-r--r--src/quick/scenegraph/qsgadaptationlayer_p.h133
-rw-r--r--src/quick/scenegraph/qsgcontext.cpp26
-rw-r--r--src/quick/scenegraph/qsgcontext_p.h5
-rw-r--r--src/quick/scenegraph/qsgcontextplugin_p.h2
15 files changed, 1085 insertions, 96 deletions
diff --git a/src/quick/items/qquickgenericshadereffect.cpp b/src/quick/items/qquickgenericshadereffect.cpp
index 419acaeb72..769c408672 100644
--- a/src/quick/items/qquickgenericshadereffect.cpp
+++ b/src/quick/items/qquickgenericshadereffect.cpp
@@ -38,11 +38,14 @@
****************************************************************************/
#include <private/qquickgenericshadereffect_p.h>
+#include <private/qquickwindow_p.h>
+#include <private/qquickitem_p.h>
+#include <QSignalMapper>
QT_BEGIN_NAMESPACE
// The generic shader effect is used when the scenegraph backend indicates
-// SupportsShaderEffectV2. This, unlike the monolithic and interconnected (e.g.
+// 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.
@@ -51,31 +54,64 @@ QQuickGenericShaderEffect::QQuickGenericShaderEffect(QQuickShaderEffect *item, Q
: QObject(parent)
, m_item(item)
, m_meshResolution(1, 1)
- , m_mesh(0)
+ , m_mesh(nullptr)
, m_cullMode(QQuickShaderEffect::NoCulling)
- , m_status(QQuickShaderEffect::Uncompiled)
, m_blending(true)
, m_supportsAtlasTextures(false)
+ , m_mgr(nullptr)
+ , m_dirty(0)
{
}
QQuickGenericShaderEffect::~QQuickGenericShaderEffect()
{
+ for (int i = 0; i < NShader; ++i) {
+ disconnectSignals(Shader(i));
+ for (const auto &sm : qAsConst(m_signalMappers[i]))
+ delete sm.mapper;
+ }
+
+ delete m_mgr;
}
-void QQuickGenericShaderEffect::setFragmentShader(const QByteArray &code)
+void QQuickGenericShaderEffect::setFragmentShader(const QByteArray &src)
{
- Q_UNUSED(code);
+ if (m_fragShader.constData() == src.constData())
+ return;
+
+ m_fragShader = src;
+ m_dirty |= QSGShaderEffectNode::DirtyShaderFragment;
+
+ if (m_item->isComponentComplete())
+ updateShader(Fragment, src);
+
+ m_item->update();
+ emit m_item->fragmentShaderChanged();
}
-void QQuickGenericShaderEffect::setVertexShader(const QByteArray &code)
+void QQuickGenericShaderEffect::setVertexShader(const QByteArray &src)
{
- Q_UNUSED(code);
+ if (m_vertShader.constData() == src.constData())
+ return;
+
+ m_vertShader = src;
+ m_dirty |= QSGShaderEffectNode::DirtyShaderVertex;
+
+ if (m_item->isComponentComplete())
+ updateShader(Vertex, src);
+
+ m_item->update();
+ emit m_item->vertexShaderChanged();
}
void QQuickGenericShaderEffect::setBlending(bool enable)
{
- Q_UNUSED(enable);
+ if (m_blending == enable)
+ return;
+
+ m_blending = enable;
+ m_item->update();
+ emit m_item->blendingChanged();
}
QVariant QQuickGenericShaderEffect::mesh() const
@@ -86,42 +122,447 @@ QVariant QQuickGenericShaderEffect::mesh() const
void QQuickGenericShaderEffect::setMesh(const QVariant &mesh)
{
- Q_UNUSED(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, 0);
+
+ 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)
{
- Q_UNUSED(face);
+ if (m_cullMode == face)
+ return;
+
+ m_cullMode = face;
+ m_item->update();
+ emit m_item->cullModeChanged();
}
void QQuickGenericShaderEffect::setSupportsAtlasTextures(bool supports)
{
- Q_UNUSED(supports);
+ if (m_supportsAtlasTextures == supports)
+ return;
+
+ m_supportsAtlasTextures = supports;
+ markGeometryDirtyAndUpdate();
+ emit m_item->supportsAtlasTexturesChanged();
+}
+
+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::Error;
+
+ return QQuickShaderEffect::Status(mgr->status());
+}
+
+QQuickShaderEffect::ShaderType QQuickGenericShaderEffect::shaderType() const
+{
+ QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
+ if (!mgr)
+ return QQuickShaderEffect::HLSL;
+
+ return QQuickShaderEffect::ShaderType(mgr->shaderType());
+}
+
+QQuickShaderEffect::ShaderCompilationType QQuickGenericShaderEffect::shaderCompilationType() const
+{
+ QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
+ if (!mgr)
+ return QQuickShaderEffect::OfflineCompilation;
+
+ return QQuickShaderEffect::ShaderCompilationType(mgr->shaderCompilationType());
+}
+
+QQuickShaderEffect::ShaderSourceType QQuickGenericShaderEffect::shaderSourceType() const
+{
+ QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
+ if (!mgr)
+ return QQuickShaderEffect::ShaderByteCode;
+
+ return QQuickShaderEffect::ShaderSourceType(mgr->shaderSourceType());
}
void QQuickGenericShaderEffect::handleEvent(QEvent *event)
{
- Q_UNUSED(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 *)
{
- Q_UNUSED(oldNode);
- return nullptr;
+ QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode);
+
+ if (m_item->width() <= 0 || m_item->height() <= 0) {
+ delete node;
+ return nullptr;
+ }
+
+ // 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, mgr);
+ m_dirty = QSGShaderEffectNode::DirtyShaderVertex | QSGShaderEffectNode::DirtyShaderFragment
+ | QSGShaderEffectNode::DirtyShaderConstant | QSGShaderEffectNode::DirtyShaderTexture
+ | QSGShaderEffectNode::DirtyShaderGeometry | QSGShaderEffectNode::DirtyShaderMesh;
+ }
+
+ // Dirty mesh and geometry are handled here, the rest is passed on to the node.
+ 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();
+
+ geometry = mesh->updateGeometry(geometry, 2, 0, node->normalizedTextureSubRect(), rect);
+
+ node->setFlag(QSGNode::OwnsGeometry, false);
+ node->setGeometry(geometry);
+ node->setFlag(QSGNode::OwnsGeometry, true);
+
+ m_dirty &= ~QSGShaderEffectNode::DirtyShaderGeometry;
+ }
+
+ QSGShaderEffectNode::SyncData sd;
+ sd.dirty = m_dirty;
+ sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode);
+ sd.blending = m_blending;
+ sd.supportsAtlasTextures = m_supportsAtlasTextures;
+ sd.vertexShader = (m_dirty & QSGShaderEffectNode::DirtyShaderVertex) ? &m_shaders[Vertex] : nullptr;
+ sd.fragmentShader = (m_dirty & QSGShaderEffectNode::DirtyShaderFragment) ? &m_shaders[Fragment] : nullptr;
+
+ node->sync(&sd);
+
+ m_dirty = 0;
+
+ return node;
}
void QQuickGenericShaderEffect::handleComponentComplete()
{
+ updateShader(Vertex, m_vertShader);
+ updateShader(Fragment, m_fragShader);
}
void QQuickGenericShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
- Q_UNUSED(change);
- Q_UNUSED(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;
+ // need a window and a rendercontext (i.e. the scenegraph backend is ready)
+ QQuickWindow *w = m_item->window();
+ if (w && w->isSceneGraphInitialized()) {
+ m_mgr = QQuickWindowPrivate::get(w)->context->sceneGraphContext()->createGuiThreadShaderEffectManager();
+ if (m_mgr) {
+ QObject::connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(logChanged()));
+ QObject::connect(m_mgr, SIGNAL(logAndStatusChanged()), m_item, SIGNAL(statusChanged()));
+ QObject::connect(m_mgr, SIGNAL(textureChanged()), this, SLOT(markGeometryDirtyAndUpdateIfSupportsAtlas()));
+ }
+ } else if (!w) {
+ qWarning("ShaderEffect: Backend specifics cannot be queried until the item has a window");
+ } else {
+ qWarning("ShaderEffect: Backend specifics cannot be queried until the scenegraph has initialized");
+ }
+ }
+
+ return m_mgr;
+}
+
+void QQuickGenericShaderEffect::disconnectSignals(Shader shaderType)
+{
+ for (auto &sm : m_signalMappers[shaderType]) {
+ if (sm.active) {
+ sm.active = false;
+ QObject::disconnect(m_item, nullptr, sm.mapper, SLOT(map()));
+ QObject::disconnect(sm.mapper, SIGNAL(mapped(int)), this, SLOT(propertyChanged(int)));
+ }
+ }
+ 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::updateShader(Shader shaderType, const QByteArray &src)
+{
+ QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
+ if (!mgr)
+ return;
+
+ disconnectSignals(shaderType);
+
+ m_shaders[shaderType].varData.clear();
+
+ if (src.isEmpty()) {
+ m_shaders[shaderType].valid = false;
+ return;
+ }
+
+ // 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.
+ QSGGuiThreadShaderEffectManager::ShaderInfo shaderInfo;
+ if (!mgr->reflect(src, &shaderInfo)) {
+ qWarning("ShaderEffect: shader reflection failed for %s", src.constData());
+ m_shaders[shaderType].valid = false;
+ return;
+ }
+
+ m_shaders[shaderType].shaderInfo = shaderInfo;
+ m_shaders[shaderType].valid = true;
+
+ const int varCount = 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_signalMappers[shaderType].count() < varCount)
+ m_signalMappers[shaderType].resize(varCount);
+
+ const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects();
+
+ // Hook up the signals to get notified about changes for properties that
+ // correspond to variables in the shader.
+ for (int i = 0; i < varCount; ++i) {
+ const auto &v(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 == QByteArrayLiteral("qt_Opacity"))
+ vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
+ else if (v.name == QByteArrayLiteral("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.
+ 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.
+ const int propIdx = m_item->metaObject()->indexOfProperty(v.name.constData());
+ if (propIdx >= 0) {
+ QMetaProperty mp = m_item->metaObject()->property(propIdx);
+ if (!mp.hasNotifySignal())
+ qWarning("ShaderEffect: property '%s' does not have notification method", v.name.constData());
+
+ // Have a QSignalMapper that emits mapped() with an index+type on each property change notify signal.
+ auto &sm(m_signalMappers[shaderType][i]);
+ if (!sm.mapper) {
+ sm.mapper = new QSignalMapper;
+ sm.mapper->setMapping(m_item, i | (shaderType << 16));
+ }
+ sm.active = true;
+ const QByteArray signalName = '2' + mp.notifySignal().methodSignature();
+ QObject::connect(m_item, signalName, sm.mapper, SLOT(map()));
+ QObject::connect(sm.mapper, SIGNAL(mapped(int)), this, SLOT(propertyChanged(int)));
+ } 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.value = m_item->property(v.name.constData());
+
+ 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]);
+
+ 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*)));
+ }
+
+ vd.value = m_item->property(v.name.constData());
+
+ 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;
+
+ } else {
+ vd.value = m_item->property(v.name.constData());
+ m_dirty |= QSGShaderEffectNode::DirtyShaderConstant;
+ }
+
+ 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
diff --git a/src/quick/items/qquickgenericshadereffect_p.h b/src/quick/items/qquickgenericshadereffect_p.h
index 6a31276a61..bc90b493ca 100644
--- a/src/quick/items/qquickgenericshadereffect_p.h
+++ b/src/quick/items/qquickgenericshadereffect_p.h
@@ -53,11 +53,14 @@
#include <QtQuick/qquickitem.h>
#include <private/qtquickglobal_p.h>
+#include <private/qsgadaptationlayer_p.h>
#include "qquickshadereffect_p.h"
#include "qquickshadereffectmesh_p.h"
QT_BEGIN_NAMESPACE
+class QSignalMapper;
+
class Q_QUICK_PRIVATE_EXPORT QQuickGenericShaderEffect : public QObject
{
Q_OBJECT
@@ -66,11 +69,11 @@ public:
QQuickGenericShaderEffect(QQuickShaderEffect *item, QObject *parent = 0);
~QQuickGenericShaderEffect();
- QByteArray fragmentShader() const { return QByteArray(); }
- void setFragmentShader(const QByteArray &code);
+ QByteArray fragmentShader() const { return m_fragShader; }
+ void setFragmentShader(const QByteArray &src);
- QByteArray vertexShader() const { return QByteArray(); }
- void setVertexShader(const QByteArray &code);
+ QByteArray vertexShader() const { return m_vertShader; }
+ void setVertexShader(const QByteArray &src);
bool blending() const { return m_blending; }
void setBlending(bool enable);
@@ -81,31 +84,61 @@ public:
QQuickShaderEffect::CullMode cullMode() const { return m_cullMode; }
void setCullMode(QQuickShaderEffect::CullMode face);
- QString log() const { return m_log; }
- QQuickShaderEffect::Status status() const { return m_status; }
+ QString log() const;
+ QQuickShaderEffect::Status status() const;
bool supportsAtlasTextures() const { return m_supportsAtlasTextures; }
void setSupportsAtlasTextures(bool supports);
+ QQuickShaderEffect::ShaderType shaderType() const;
+ QQuickShaderEffect::ShaderCompilationType shaderCompilationType() const;
+ QQuickShaderEffect::ShaderSourceType shaderSourceType() const;
+
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);
- QString parseLog() { return QString(); }
+private slots:
+ void propertyChanged(int mappedId);
+ void sourceDestroyed(QObject *object);
+ void markGeometryDirtyAndUpdate();
+ void markGeometryDirtyAndUpdateIfSupportsAtlas();
private:
+ QSGGuiThreadShaderEffectManager *shaderEffectManager() const;
+
+ enum Shader {
+ Vertex,
+ Fragment,
+
+ NShader
+ };
+ void updateShader(Shader which, const QByteArray &src);
+ void disconnectSignals(Shader which);
+ bool sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const;
+
QQuickShaderEffect *m_item;
QSize m_meshResolution;
QQuickShaderEffectMesh *m_mesh;
QQuickGridMesh m_defaultMesh;
QQuickShaderEffect::CullMode m_cullMode;
- QString m_log;
- QQuickShaderEffect::Status m_status;
-
- uint m_blending : 1;
- uint m_supportsAtlasTextures : 1;
+ bool m_blending;
+ bool m_supportsAtlasTextures;
+ mutable QSGGuiThreadShaderEffectManager *m_mgr;
+ QByteArray m_fragShader;
+ QByteArray m_vertShader;
+
+ QSGShaderEffectNode::ShaderData m_shaders[NShader];
+ QSGShaderEffectNode::DirtyShaderFlags m_dirty;
+
+ struct SignalMapper {
+ SignalMapper() : mapper(nullptr), active(false) { }
+ QSignalMapper *mapper;
+ bool active;
+ };
+ QVector<SignalMapper> m_signalMappers[NShader];
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp
index a2323248c7..fa01c8c563 100644
--- a/src/quick/items/qquickitemsmodule.cpp
+++ b/src/quick/items/qquickitemsmodule.cpp
@@ -292,6 +292,8 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
qmlRegisterType<QQuickTextInput, 7>(uri, 2, 7, "TextInput");
qmlRegisterType<QQuickTextEdit, 7>(uri, 2, 7, "TextEdit");
+ qmlRegisterType<QQuickShaderEffect, 2>(uri, 2, 8, "ShaderEffect");
+
qmlRegisterUncreatableType<QQuickMouseEvent, 7>(uri, 2, 7, nullptr, QQuickMouseEvent::tr("MouseEvent is only available within handlers in MouseArea"));
}
diff --git a/src/quick/items/qquickopenglshadereffect.cpp b/src/quick/items/qquickopenglshadereffect.cpp
index e3ac3600bc..f6f2503cd0 100644
--- a/src/quick/items/qquickopenglshadereffect.cpp
+++ b/src/quick/items/qquickopenglshadereffect.cpp
@@ -631,7 +631,7 @@ void QQuickOpenGLShaderEffect::setMesh(const QVariant &mesh)
}
}
if (!ok)
- qWarning("ShaderEffect: mesh property must be size or object deriving from QQuickOpenGLShaderEffectMesh.");
+ qWarning("ShaderEffect: mesh property must be size or object deriving from QQuickShaderEffectMesh.");
}
m_defaultMesh.setResolution(m_meshResolution);
}
@@ -822,8 +822,8 @@ QSGNode *QQuickOpenGLShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuic
QRectF rect(0, 0, m_item->width(), m_item->height());
QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
- geometry = mesh->updateGeometry(geometry, m_common.attributes, srcRect, rect);
- if (!geometry) {
+ int posIndex = 0;
+ if (!mesh->validateAttributes(m_common.attributes, &posIndex)) {
QString log = mesh->log();
if (!log.isNull()) {
m_log = parseLog();
@@ -837,6 +837,8 @@ QSGNode *QQuickOpenGLShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuic
return 0;
}
+ geometry = mesh->updateGeometry(geometry, m_common.attributes.count(), posIndex, srcRect, rect);
+
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry, true);
diff --git a/src/quick/items/qquickshadereffect.cpp b/src/quick/items/qquickshadereffect.cpp
index 0f6f88f19b..36fc7ef3c8 100644
--- a/src/quick/items/qquickshadereffect.cpp
+++ b/src/quick/items/qquickshadereffect.cpp
@@ -54,11 +54,18 @@ QT_BEGIN_NAMESPACE
\ingroup qtquick-effects
\brief Applies custom shaders to a rectangle
- The ShaderEffect type applies a custom OpenGL
- \l{vertexShader}{vertex} and \l{fragmentShader}{fragment} shader to a
+ The ShaderEffect type applies a custom
+ \l{vertexShader}{vertex} and \l{fragmentShader}{fragment (pixel)} shader to a
rectangle. It allows you to write effects such as drop shadow, blur,
colorize and page curl directly in QML.
+ \note Depending on the Qt Quick scenegraph backend in use, the ShaderEffect
+ type may not be supported (for example, with the software backend), or may
+ use a different shading language with rules and expectations different from
+ OpenGL and GLSL.
+
+ \section1 OpenGL and GLSL
+
There are two types of input to the \l vertexShader:
uniform variables and attributes. Some are predefined:
\list
@@ -158,9 +165,161 @@ QT_BEGIN_NAMESPACE
\endqml
\endtable
- By default, the ShaderEffect consists of four vertices, one for each
- corner. For non-linear vertex transformations, like page curl, you can
- specify a fine grid of vertices by specifying a \l mesh resolution.
+ \note Scene Graph textures have origin in the top-left corner rather than
+ bottom-left which is common in OpenGL.
+
+ For information about the GLSL version being used, see \l QtQuick::OpenGLInfo.
+
+ \section1 Direct3D and HLSL
+
+ Direct3D backends provide ShaderEffect support with HLSL. The Direct3D 12
+ backend requires using at least Shader Model 5.0 both for vertex and pixel
+ shaders. When necessary, the \l shaderType property can be used to decide
+ at runtime what kind of value to assign to \l fragmentShader or
+ \l vertexShader.
+
+ All concepts described above for OpenGL and GLSL apply to Direct3D and HLSL
+ as well. There are however a number of notable practical differences, which
+ are the following:
+
+ Instead of uniforms, HLSL shaders are expected to use a single constant
+ buffer, assigned to register \c b0. The special names \c qt_Matrix,
+ \c qt_Opacity, and \c qt_SubRect_<name> function the same way as with GLSL.
+ All other members of the buffer are expected to map to properties in the
+ ShaderEffect item.
+
+ \note The buffer layout must be compatible for both shaders. This means
+ that application-provided shaders must make sure \c qt_Matrix and
+ \c qt_Opacity are included in the buffer, starting at offset 0, when custom
+ code is provided for one type of shader only, leading to ShaderEffect
+ providing the other shader. This is due to ShaderEffect's built-in shader code
+ declaring a constant buffer containing \c{float4x4 qt_Matrix; float qt_Opacity;}.
+
+ Unlike GLSL's attributes, no names are used for vertex input elements.
+ Therefore qt_Vertex and qt_MultiTexCoord0 are not relevant. Instead, the
+ standard Direct3D semantics, \c POSITION and \c TEXCOORD (or \c TEXCOORD0)
+ are used for identifying the correct input layout.
+
+ Unlike GLSL's samplers, texture and sampler objects are separate in HLSL.
+ Shaders are expected to expect 2D, non-array, non-multisample textures.
+ Both the texture and sampler binding points are expected to be sequential
+ and start from 0 (meaning registers \c{t0, t1, ...}, and \c{s0, s1, ...},
+ respectively). Unlike with OpenGL, samplers are not mapped to Qt Quick item
+ properties and therefore the name of the sampler is not relevant. Instead,
+ it is the textures that map to properties referencing \l Image or
+ \l ShaderEffectSource items.
+
+ Unlike with OpenGL, runtime compilation of shader source code may not be
+ supported. Backends for modern APIs are likely to prefer offline
+ compilation and shipping pre-compiled bytecode with applications instead of
+ inlined shader source strings. See the \l shaderSourceType and
+ \l shaderCompilationType properties.
+
+ \table 70%
+ \row
+ \li \image declarative-shadereffectitem.png
+ \li \qml
+ import QtQuick 2.8
+
+ Rectangle {
+ width: 200; height: 100
+ Row {
+ Image { id: img;
+ sourceSize { width: 100; height: 100 } source: "qt-logo.png" }
+ ShaderEffect {
+ width: 100; height: 100
+ property variant src: img
+ fragmentShader: "qrc:/effect_ps.cso"
+ }
+ }
+ }
+ \endqml
+ \row
+ \li where \c effect_ps.cso is the compiled bytecode for the following HLSL shader:
+ \code
+ cbuffer ConstantBuffer : register(b0)
+ {
+ float4x4 qt_Matrix;
+ float qt_Opacity;
+ };
+ Texture2D src : register(t0);
+ SamplerState srcSampler : register(s0);
+ float4 ExamplePixelShader(float4 position : SV_POSITION, float2 coord : TEXCOORD0) : SV_TARGET
+ {
+ float4 tex = src.Sample(srcSampler, coord);
+ float3 col = dot(tex.rgb, float3(0.344, 0.5, 0.156));
+ return float4(col, tex.a) * qt_Opacity;
+ }
+ \endcode
+ \endtable
+
+ The above is equivalent to the OpenGL example presented earlier. The vertex
+ shader is provided implicitly by ShaderEffect. Note that the output of the
+ pixel shader is using premultiplied alpha and that \c qt_Matrix is present
+ in the constant buffer at offset 0, even though the pixel shader does not
+ use the value.
+
+ Some effects will want to provide a vertex shader as well. Below is a
+ similar effect with both the vertex and fragment shader provided by the
+ application. This time the colorization factor is provided by the QML item
+ instead of hardcoding it in the shader. This can allow, among others,
+ animating the value using QML's and Qt Quick's standard facilities.
+
+ \table 70%
+ \row
+ \li \image declarative-shadereffectitem.png
+ \li \qml
+ import QtQuick 2.8
+
+ Rectangle {
+ width: 200; height: 100
+ Row {
+ Image { id: img;
+ sourceSize { width: 100; height: 100 } source: "qt-logo.png" }
+ ShaderEffect {
+ width: 100; height: 100
+ property variant src: img
+ property variant color: Qt.vector3d(0.344, 0.5, 0.156)
+ vertexShader: "qrc:/effect_vs.cso"
+ fragmentShader: "qrc:/effect_ps.cso"
+ }
+ }
+ }
+ \endqml
+ \row
+ \li where \c effect_vs.cso and \c effect_ps.cso are the compiled bytecode
+ for \c ExampleVertexShader and \c ExamplePixelShader. The source code is
+ presented as one snippet here, the shaders can however be placed in
+ separate source files as well.
+ \code
+ cbuffer ConstantBuffer : register(b0)
+ {
+ float4x4 qt_Matrix;
+ float qt_Opacity;
+ float3 color;
+ };
+ Texture2D src : register(t0);
+ SamplerState srcSampler : register(s0);
+ struct PSInput
+ {
+ float4 position : SV_POSITION;
+ float2 coord : TEXCOORD0;
+ };
+ PSInput ExampleVertexShader(float4 position : POSITION, float2 coord : TEXCOORD0)
+ {
+ PSInput result;
+ result.position = mul(qt_Matrix, position);
+ result.coord = coord;
+ return result;
+ }
+ float4 ExamplePixelShader(PSInput input) : SV_TARGET
+ {
+ float4 tex = src.Sample(srcSampler, coord);
+ float3 col = dot(tex.rgb, color);
+ return float4(col, tex.a) * qt_Opacity;
+ }
+ \endcode
+ \endtable
\section1 ShaderEffect and Item Layers
@@ -183,13 +342,14 @@ QT_BEGIN_NAMESPACE
\li \snippet qml/opacitymask.qml 1
\endtable
- The \l {Qt Graphical Effects} module contains several ready-made effects
- for using with Qt Quick applications.
+ \section1 Other notes
- \note Scene Graph textures have origin in the top-left corner rather than
- bottom-left which is common in OpenGL.
+ By default, the ShaderEffect consists of four vertices, one for each
+ corner. For non-linear vertex transformations, like page curl, you can
+ specify a fine grid of vertices by specifying a \l mesh resolution.
- For information about the GLSL version being used, see \l QtQuick::OpenGLInfo.
+ The \l {Qt Graphical Effects} module contains several ready-made effects
+ for using with Qt Quick applications.
\sa {Item Layers}
*/
@@ -204,7 +364,7 @@ QQuickShaderEffect::QQuickShaderEffect(QQuickItem *parent)
setFlag(QQuickItem::ItemHasContents);
#ifndef QT_NO_OPENGL
- if (!qsg_backend_flags().testFlag(QSGContextFactoryInterface::SupportsShaderEffectV2))
+ if (!qsg_backend_flags().testFlag(QSGContextFactoryInterface::SupportsShaderEffectNode))
m_glImpl = new QQuickOpenGLShaderEffect(this, this);
#endif
if (!m_glImpl)
@@ -214,10 +374,23 @@ QQuickShaderEffect::QQuickShaderEffect(QQuickItem *parent)
/*!
\qmlproperty string QtQuick::ShaderEffect::fragmentShader
- This property holds the fragment shader's GLSL source code.
- The default shader expects the texture coordinate to be passed from the
- vertex shader as "varying highp vec2 qt_TexCoord0", and it samples from a
- sampler2D named "source".
+ This property holds the fragment (pixel) shader's source code or a
+ reference to the pre-compiled bytecode. Some APIs, like OpenGL, always
+ support runtime compilation and therefore the traditional Qt Quick way of
+ inlining shader source strings is functional. Qt Quick backends for other
+ APIs may however limit support to pre-compiled bytecode like SPIR-V or D3D
+ shader bytecode. There the string is simply a filename, which may be a file
+ in the filesystem or bundled with the executable via Qt's resource system.
+
+ With GLSL the default shader expects the texture coordinate to be passed
+ from the vertex shader as \c{varying highp vec2 qt_TexCoord0}, and it
+ samples from a sampler2D named \c source. With HLSL the texture is named
+ \c source, while the vertex shader is expected to provide
+ \c{float2 coord : TEXCOORD0} in its output in addition to
+ \c{float4 position : SV_POSITION} (names can differ since linking is done
+ based on the semantics).
+
+ \sa vertexShader, shaderType, shaderCompilationType, shaderSourceType
*/
QByteArray QQuickShaderEffect::fragmentShader() const
@@ -243,9 +416,20 @@ void QQuickShaderEffect::setFragmentShader(const QByteArray &code)
/*!
\qmlproperty string QtQuick::ShaderEffect::vertexShader
- This property holds the vertex shader's GLSL source code.
- The default shader passes the texture coordinate along to the fragment
- shader as "varying highp vec2 qt_TexCoord0".
+ This property holds the vertex shader's source code or a reference to the
+ pre-compiled bytecode. Some APIs, like OpenGL, always support runtime
+ compilation and therefore the traditional Qt Quick way of inlining shader
+ source strings is functional. Qt Quick backends for other APIs may however
+ limit support to pre-compiled bytecode like SPIR-V or D3D shader bytecode.
+ There the string is simply a filename, which may be a file in the
+ filesystem or bundled with the executable via Qt's resource system.
+
+ With GLSL the default shader passes the texture coordinate along to the
+ fragment shader as \c{varying highp vec2 qt_TexCoord0}. With HLSL it is
+ enough to use the standard \c TEXCOORD0 semantic, for example
+ \c{float2 coord : TEXCOORD0}.
+
+ \sa fragmentShader, shaderType, shaderCompilationType, shaderSourceType
*/
QByteArray QQuickShaderEffect::vertexShader() const
@@ -416,9 +600,17 @@ void QQuickShaderEffect::setSupportsAtlasTextures(bool supports)
\li ShaderEffect.Error - the shader program failed to compile or link.
\endlist
- When setting the fragment or vertex shader source code, the status will become Uncompiled.
- The first time the ShaderEffect is rendered with new shader source code, the shaders are
- compiled and linked, and the status is updated to Compiled or Error.
+ When setting the fragment or vertex shader source code, the status will
+ become Uncompiled. The first time the ShaderEffect is rendered with new
+ shader source code, the shaders are compiled and linked, and the status is
+ updated to Compiled or Error.
+
+ When runtime compilation is not in use and the shader properties refer to
+ files with bytecode, the status is always Compiled. The contents of the
+ shader is not examined (apart from basic reflection to discover vertex
+ input elements and constant buffer data) until later in the rendering
+ pipeline so potential errors (like layout or root signature mismatches)
+ will only be detected at a later point.
\sa log
*/
@@ -426,9 +618,9 @@ void QQuickShaderEffect::setSupportsAtlasTextures(bool supports)
/*!
\qmlproperty string QtQuick::ShaderEffect::log
- This property holds a log of warnings and errors from the latest attempt at compiling and
- linking the OpenGL shader program. It is updated at the same time \l status is set to Compiled
- or Error.
+ This property holds a log of warnings and errors from the latest attempt at
+ compiling and linking the OpenGL shader program. It is updated at the same
+ time \l status is set to Compiled or Error.
\sa status
*/
@@ -451,6 +643,81 @@ QQuickShaderEffect::Status QQuickShaderEffect::status() const
return m_impl->status();
}
+/*!
+ \qmlproperty QtQuick::ShaderEffect::ShaderType QtQuick::ShaderEffect::shaderType
+
+ This property contains the shading language supported by the current Qt
+ Quick backend the application is using.
+
+ With OpenGL the value is GLSL.
+
+ \since 5.8
+ \since QtQuick 2.8
+
+ \sa shaderCompilationType, shaderSourceType, vertexShader, fragmentShader
+*/
+
+QQuickShaderEffect::ShaderType QQuickShaderEffect::shaderType() const
+{
+#ifndef QT_NO_OPENGL
+ if (m_glImpl)
+ return GLSL;
+#endif
+ return m_impl->shaderType();
+}
+
+/*!
+ \qmlproperty QtQuick::ShaderEffect::ShaderCompilationType QtQuick::ShaderEffect::shaderCompilationType
+
+ This property contains a bitmask of the shader compilation approaches
+ supported by the current Qt Quick backend the application is using.
+
+ With OpenGL the value is RuntimeCompilation, which corresponds to the
+ traditional way of using ShaderEffect. Non-OpenGL backends are expected to
+ focus more on OfflineCompilation, however.
+
+ \since 5.8
+ \since QtQuick 2.8
+
+ \sa shaderType, shaderSourceType, vertexShader, fragmentShader
+*/
+
+QQuickShaderEffect::ShaderCompilationType QQuickShaderEffect::shaderCompilationType() const
+{
+#ifndef QT_NO_OPENGL
+ if (m_glImpl)
+ return RuntimeCompilation;
+#endif
+ return m_impl->shaderCompilationType();
+}
+
+/*!
+ \qmlproperty QtQuick::ShaderEffect::ShaderSourceType QtQuick::ShaderEffect::shaderSourceType
+
+ This property contains a bitmask of the supported ways of providing shader
+ sources.
+
+ With OpenGL the value is ShaderSourceString, which corresponds to the
+ traditional way of inlining GLSL source code into QML. Other, non-OpenGL Qt
+ Quick backends may however decide not to support inlined shader sources, or
+ even shader sources at all. In this case shaders are expected to be
+ pre-compiled into formats like SPIR-V or D3D shader bytecode.
+
+ \since 5.8
+ \since QtQuick 2.8
+
+ \sa shaderType, shaderCompilationType, vertexShader, fragmentShader
+*/
+
+QQuickShaderEffect::ShaderSourceType QQuickShaderEffect::shaderSourceType() const
+{
+#ifndef QT_NO_OPENGL
+ if (m_glImpl)
+ return ShaderSourceString;
+#endif
+ return m_impl->shaderSourceType();
+}
+
bool QQuickShaderEffect::event(QEvent *e)
{
#ifndef QT_NO_OPENGL
@@ -516,13 +783,13 @@ bool QQuickShaderEffect::isComponentComplete() const
return QQuickItem::isComponentComplete();
}
-QString QQuickShaderEffect::parseLog()
+QString QQuickShaderEffect::parseLog() // for OpenGL-based autotests
{
#ifndef QT_NO_OPENGL
if (m_glImpl)
return m_glImpl->parseLog();
#endif
- return m_impl->parseLog();
+ return QString();
}
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickshadereffect_p.h b/src/quick/items/qquickshadereffect_p.h
index 62d7e6fe5f..390703350f 100644
--- a/src/quick/items/qquickshadereffect_p.h
+++ b/src/quick/items/qquickshadereffect_p.h
@@ -70,6 +70,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffect : public QQuickItem
Q_PROPERTY(QString log READ log NOTIFY logChanged)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(bool supportsAtlasTextures READ supportsAtlasTextures WRITE setSupportsAtlasTextures NOTIFY supportsAtlasTexturesChanged REVISION 1)
+ Q_PROPERTY(ShaderType shaderType READ shaderType NOTIFY shaderTypeChanged REVISION 2)
+ Q_PROPERTY(ShaderCompilationType shaderCompilationType READ shaderCompilationType NOTIFY shaderCompilationTypeChanged REVISION 2)
+ Q_PROPERTY(ShaderSourceType shaderSourceType READ shaderSourceType NOTIFY shaderSourceTypeChanged REVISION 2)
public:
enum CullMode {
@@ -86,6 +89,26 @@ public:
};
Q_ENUM(Status)
+ enum ShaderType {
+ GLSL,
+ HLSL,
+ Metal
+ };
+ Q_ENUM(ShaderType)
+
+ enum ShaderCompilationType {
+ RuntimeCompilation = 0x01,
+ OfflineCompilation = 0x02
+ };
+ Q_ENUM(ShaderCompilationType)
+
+ enum ShaderSourceType {
+ ShaderSourceString = 0x01,
+ ShaderSourceFile = 0x02,
+ ShaderByteCode = 0x04
+ };
+ Q_ENUM(ShaderSourceType)
+
QQuickShaderEffect(QQuickItem *parent = 0);
QByteArray fragmentShader() const;
@@ -109,6 +132,10 @@ public:
QString log() const;
Status status() const;
+ ShaderType shaderType() const;
+ ShaderCompilationType shaderCompilationType() const;
+ ShaderSourceType shaderSourceType() const;
+
bool isComponentComplete() const;
QString parseLog();
@@ -121,6 +148,9 @@ Q_SIGNALS:
void logChanged();
void statusChanged();
void supportsAtlasTexturesChanged();
+ void shaderTypeChanged();
+ void shaderCompilationTypeChanged();
+ void shaderSourceTypeChanged();
protected:
bool event(QEvent *e) override;
diff --git a/src/quick/items/qquickshadereffectmesh.cpp b/src/quick/items/qquickshadereffectmesh.cpp
index 478954e142..f6c2bb0dce 100644
--- a/src/quick/items/qquickshadereffectmesh.cpp
+++ b/src/quick/items/qquickshadereffectmesh.cpp
@@ -79,49 +79,59 @@ QQuickGridMesh::QQuickGridMesh(QObject *parent)
{
}
-QSGGeometry *QQuickGridMesh::updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &srcRect, const QRectF &dstRect)
+bool QQuickGridMesh::validateAttributes(const QVector<QByteArray> &attributes, int *posIndex)
{
- int vmesh = m_resolution.height();
- int hmesh = m_resolution.width();
- int attrCount = attributes.count();
-
+ const int attrCount = attributes.count();
int positionIndex = attributes.indexOf(qtPositionAttributeName());
int texCoordIndex = attributes.indexOf(qtTexCoordAttributeName());
- if (!geometry) {
- switch (attrCount) {
- case 0:
- m_log = QLatin1String("Error: No attributes specified.");
- return 0;
- case 1:
- if (positionIndex != 0) {
+ switch (attrCount) {
+ case 0:
+ m_log = QLatin1String("Error: No attributes specified.");
+ return false;
+ case 1:
+ if (positionIndex != 0) {
+ m_log = QLatin1String("Error: Missing \'");
+ m_log += QLatin1String(qtPositionAttributeName());
+ m_log += QLatin1String("\' attribute.\n");
+ return false;
+ }
+ break;
+ case 2:
+ if (positionIndex == -1 || texCoordIndex == -1) {
+ m_log.clear();
+ if (positionIndex == -1) {
m_log = QLatin1String("Error: Missing \'");
m_log += QLatin1String(qtPositionAttributeName());
m_log += QLatin1String("\' attribute.\n");
- return 0;
}
- break;
- case 2:
- if (positionIndex == -1 || texCoordIndex == -1) {
- m_log.clear();
- if (positionIndex == -1) {
- m_log = QLatin1String("Error: Missing \'");
- m_log += QLatin1String(qtPositionAttributeName());
- m_log += QLatin1String("\' attribute.\n");
- }
- if (texCoordIndex == -1) {
- m_log += QLatin1String("Error: Missing \'");
- m_log += QLatin1String(qtTexCoordAttributeName());
- m_log += QLatin1String("\' attribute.\n");
- }
- return 0;
+ if (texCoordIndex == -1) {
+ m_log += QLatin1String("Error: Missing \'");
+ m_log += QLatin1String(qtTexCoordAttributeName());
+ m_log += QLatin1String("\' attribute.\n");
}
- break;
- default:
- m_log = QLatin1String("Error: Too many attributes specified.");
- return 0;
+ return false;
}
+ break;
+ default:
+ m_log = QLatin1String("Error: Too many attributes specified.");
+ return false;
+ }
+ if (posIndex)
+ *posIndex = positionIndex;
+
+ return true;
+}
+
+QSGGeometry *QQuickGridMesh::updateGeometry(QSGGeometry *geometry, int attrCount, int posIndex,
+ const QRectF &srcRect, const QRectF &dstRect)
+{
+ int vmesh = m_resolution.height();
+ int hmesh = m_resolution.width();
+
+ if (!geometry) {
+ Q_ASSERT(attrCount == 1 || attrCount == 2);
geometry = new QSGGeometry(attrCount == 1
? QSGGeometry::defaultAttributes_Point2D()
: QSGGeometry::defaultAttributes_TexturedPoint2D(),
@@ -141,7 +151,7 @@ QSGGeometry *QQuickGridMesh::updateGeometry(QSGGeometry *geometry, const QVector
for (int ix = 0; ix <= hmesh; ++ix) {
float fx = ix / float(hmesh);
for (int ia = 0; ia < attrCount; ++ia) {
- if (ia == positionIndex) {
+ if (ia == posIndex) {
vdata->x = float(dstRect.left()) + fx * float(dstRect.width());
vdata->y = y;
++vdata;
diff --git a/src/quick/items/qquickshadereffectmesh_p.h b/src/quick/items/qquickshadereffectmesh_p.h
index c3dcb0322e..b6894dc2ec 100644
--- a/src/quick/items/qquickshadereffectmesh_p.h
+++ b/src/quick/items/qquickshadereffectmesh_p.h
@@ -73,8 +73,10 @@ class QQuickShaderEffectMesh : public QObject
Q_OBJECT
public:
QQuickShaderEffectMesh(QObject *parent = 0);
- // If 'geometry' != 0, 'attributes' is the same as last time the function was called.
- virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &srcRect, const QRectF &rect) = 0;
+ virtual bool validateAttributes(const QVector<QByteArray> &attributes, int *posIndex) = 0;
+ // If 'geometry' != 0, 'attrCount' is the same as last time the function was called.
+ virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, int attrCount, int posIndex,
+ const QRectF &srcRect, const QRectF &rect) = 0;
// If updateGeometry() fails, the reason should appear in the log.
virtual QString log() const { return QString(); }
@@ -89,7 +91,9 @@ class QQuickGridMesh : public QQuickShaderEffectMesh
Q_PROPERTY(QSize resolution READ resolution WRITE setResolution NOTIFY resolutionChanged)
public:
QQuickGridMesh(QObject *parent = 0);
- QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &srcRect, const QRectF &rect) Q_DECL_OVERRIDE;
+ bool validateAttributes(const QVector<QByteArray> &attributes, int *posIndex) Q_DECL_OVERRIDE;
+ QSGGeometry *updateGeometry(QSGGeometry *geometry, int attrCount, int posIndex,
+ const QRectF &srcRect, const QRectF &rect) Q_DECL_OVERRIDE;
QString log() const Q_DECL_OVERRIDE { return m_log; }
void setResolution(const QSize &res);
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareadaptation.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareadaptation.cpp
index 05628ea69c..0e2f4f5382 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwareadaptation.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareadaptation.cpp
@@ -65,7 +65,10 @@ QSGContext *QSGSoftwareAdaptation::create(const QString &) const
QSGContextFactoryInterface::Flags QSGSoftwareAdaptation::flags(const QString &) const
{
- return QSGContextFactoryInterface::SupportsShaderEffectV2;
+ // Claim we support adaptable shader effects, then return null for the
+ // shader effect node. The result is shader effects not being rendered,
+ // with the application working fine in all other respects.
+ return QSGContextFactoryInterface::SupportsShaderEffectNode;
}
QSGRenderLoop *QSGSoftwareAdaptation::createWindowManager()
diff --git a/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp b/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
index 6a01fac212..b680dbe3d5 100644
--- a/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
@@ -87,6 +87,11 @@ QSGRendererInterface::~QSGRendererInterface()
\fn QSGRenderNode::GraphicsAPI QSGRenderNode::graphicsAPI() const
Returns the graphics API that is in use by the Qt Quick scenegraph.
+
+ \note This function can be called on any thread. However, the renderer
+ interface's lifetime may be tied to the render thread and therefore calling
+ this function from other threads during the process of application shutdown
+ or QQuickWindow closing is likely to become invalid.
*/
/*!
@@ -98,6 +103,8 @@ QSGRendererInterface::~QSGRendererInterface()
pointer to an opaque handle that needs to be dereferenced first (for
example, \c{VkDevice dev = *static_cast<VkDevice *>(result)}). The latter
is necessary since such handles may have sizes different from a pointer.
+
+ \note This function must only be called on the render thread.
*/
void *QSGRendererInterface::getResource(Resource resource) const
{
@@ -109,6 +116,8 @@ void *QSGRendererInterface::getResource(Resource resource) const
Queries a graphics resource. \a resource is a backend-specific key. This
allows supporting any future resources that are not listed in the
Resource enum.
+
+ \note This function must only be called on the render thread.
*/
void *QSGRendererInterface::getResource(const char *resource) const
{
diff --git a/src/quick/scenegraph/qsgadaptationlayer.cpp b/src/quick/scenegraph/qsgadaptationlayer.cpp
index e8ef6befcd..b0259c50b0 100644
--- a/src/quick/scenegraph/qsgadaptationlayer.cpp
+++ b/src/quick/scenegraph/qsgadaptationlayer.cpp
@@ -519,4 +519,30 @@ void QSGNodeVisitorEx::visitChildren(QSGNode *node)
}
}
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::InputParameter &p)
+{
+ debug << p.semanticName << p.semanticIndex;
+ return debug;
+}
+QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &v)
+{
+ debug << v.name;
+ switch (v.type) {
+ case QSGGuiThreadShaderEffectManager::ShaderInfo::Constant:
+ debug << "cvar" << "offset" << v.offset << "size" << v.size;
+ break;
+ case QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler:
+ debug << "sampler" << "bindpoint" << v.bindPoint;
+ break;
+ case QSGGuiThreadShaderEffectManager::ShaderInfo::Texture:
+ debug << "texture" << "bindpoint" << v.bindPoint;
+ break;
+ default:
+ break;
+ }
+ return debug;
+}
+#endif
+
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgadaptationlayer_p.h b/src/quick/scenegraph/qsgadaptationlayer_p.h
index 78bb07599f..5155cdd719 100644
--- a/src/quick/scenegraph/qsgadaptationlayer_p.h
+++ b/src/quick/scenegraph/qsgadaptationlayer_p.h
@@ -223,6 +223,139 @@ Q_SIGNALS:
void scheduledUpdateCompleted();
};
+class Q_QUICK_PRIVATE_EXPORT QSGGuiThreadShaderEffectManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ // Enum values must match ShaderEffect.
+ enum ShaderType {
+ GLSL,
+ HLSL,
+ Metal
+ };
+ enum ShaderCompilationType {
+ RuntimeCompilation = 0x01,
+ OfflineCompilation = 0x02
+ };
+ enum ShaderSourceType {
+ ShaderSourceString = 0x01,
+ ShaderSourceFile = 0x02,
+ ShaderByteCode = 0x04
+ };
+ enum Status {
+ Compiled,
+ Uncompiled,
+ Error
+ };
+
+ virtual ShaderType shaderType() const = 0;
+ virtual int shaderCompilationType() const = 0;
+ virtual int shaderSourceType() const = 0;
+
+ virtual bool hasSeparateSamplerAndTextureObjects() const = 0;
+
+ virtual QString log() const = 0;
+ virtual Status status() const = 0;
+
+ struct ShaderInfo {
+ enum Type {
+ TypeVertex,
+ TypeFragment,
+ TypeOther
+ };
+ enum VariableType {
+ Constant, // cbuffer members or uniforms
+ Sampler,
+ Texture // for APIs with separate texture and sampler objects
+ };
+ struct InputParameter {
+ InputParameter() : semanticIndex(0) { }
+ // Semantics use the D3D keys (POSITION, TEXCOORD).
+ // Attribute name based APIs can map based on pre-defined names.
+ QByteArray semanticName;
+ int semanticIndex;
+ };
+ struct Variable {
+ Variable() : type(Constant), offset(0), size(0), bindPoint(0) { }
+ VariableType type;
+ QByteArray name;
+ uint offset; // for cbuffer members
+ uint size; // for cbuffer members
+ int bindPoint; // for textures and samplers; for register-based APIs
+ };
+
+ QByteArray blob; // source or bytecode
+ Type type;
+ QVector<InputParameter> inputParameters;
+ QVector<Variable> variables;
+ };
+
+ virtual bool reflect(const QByteArray &src, ShaderInfo *result) = 0;
+
+Q_SIGNALS:
+ void textureChanged();
+ void logAndStatusChanged();
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::InputParameter &p);
+Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &v);
+#endif
+
+class Q_QUICK_PRIVATE_EXPORT QSGShaderEffectNode : public QSGVisitableNode
+{
+public:
+ enum DirtyShaderFlag {
+ DirtyShaderVertex = 0x01,
+ DirtyShaderFragment = 0x02,
+ DirtyShaderConstant = 0x04,
+ DirtyShaderTexture = 0x08,
+ DirtyShaderGeometry = 0x10,
+ DirtyShaderMesh = 0x20
+ };
+ Q_DECLARE_FLAGS(DirtyShaderFlags, DirtyShaderFlag)
+
+ enum CullMode { // must match ShaderEffect
+ NoCulling,
+ BackFaceCulling,
+ FrontFaceCulling
+ };
+
+ struct VariableData {
+ enum SpecialType { None, Unused, SubRect, Opacity, Matrix, Source };
+
+ QVariant value;
+ SpecialType specialType;
+ };
+
+ struct ShaderData {
+ ShaderData() : valid(false) { }
+ bool valid;
+ QSGGuiThreadShaderEffectManager::ShaderInfo shaderInfo;
+ QVector<VariableData> varData;
+ };
+
+ struct SyncData {
+ DirtyShaderFlags dirty;
+ CullMode cullMode;
+ bool blending;
+ bool supportsAtlasTextures;
+ ShaderData *vertexShader;
+ ShaderData *fragmentShader;
+ };
+
+ // Each ShaderEffect item has one node (render thread) and one manager (gui thread).
+ QSGShaderEffectNode(QSGGuiThreadShaderEffectManager *) { }
+
+ virtual QRectF normalizedTextureSubRect() const = 0;
+ virtual void sync(SyncData *syncData) = 0;
+
+ void accept(QSGNodeVisitorEx *visitor) override { if (visitor->visit(this)) visitor->visitChildren(this); visitor->endVisit(this); }
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSGShaderEffectNode::DirtyShaderFlags)
+
class Q_QUICK_PRIVATE_EXPORT QSGGlyphNode : public QSGVisitableNode
{
public:
diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp
index 0f0a7be3b2..27d0f01753 100644
--- a/src/quick/scenegraph/qsgcontext.cpp
+++ b/src/quick/scenegraph/qsgcontext.cpp
@@ -277,11 +277,29 @@ QSGRectangleNode *QSGContext::createRectangleNode(const QRectF &rect, const QCol
return node;
}
+/*!
+ Creates a new shader effect helper instance. This function is called on the
+ gui thread, unlike the others. This is necessary in order to provide
+ adaptable, backend-specific shader effect functionality to the gui thread too.
+ */
+QSGGuiThreadShaderEffectManager *QSGContext::createGuiThreadShaderEffectManager()
+{
+ return nullptr;
+}
/*!
- Creates a new animation driver.
+ Creates a new shader effect node. The default of returning nullptr is
+ valid as long as the backend does not claim SupportsShaderEffectNode or
+ ignoring ShaderEffect elements is acceptable.
*/
+QSGShaderEffectNode *QSGContext::createShaderEffectNode(QSGRenderContext *, QSGGuiThreadShaderEffectManager *)
+{
+ return nullptr;
+}
+/*!
+ Creates a new animation driver.
+ */
QAnimationDriver *QSGContext::createAnimationDriver(QObject *parent)
{
return new QSGAnimationDriver(parent);
@@ -296,6 +314,12 @@ QSize QSGContext::minimumFBOSize() const
return QSize(1, 1);
}
+/*!
+ Returns a pointer to the (presumably) global renderer interface.
+
+ \note This function may be called on the gui thread in order to get access
+ to QSGRendererInterface::graphicsAPI().
+ */
QSGRendererInterface *QSGContext::rendererInterface(QSGRenderContext *renderContext)
{
Q_UNUSED(renderContext);
diff --git a/src/quick/scenegraph/qsgcontext_p.h b/src/quick/scenegraph/qsgcontext_p.h
index 19a8636f19..2139377ebb 100644
--- a/src/quick/scenegraph/qsgcontext_p.h
+++ b/src/quick/scenegraph/qsgcontext_p.h
@@ -83,6 +83,8 @@ class QSGDistanceFieldGlyphCacheManager;
class QSGContext;
class QQuickPaintedItem;
class QSGRendererInterface;
+class QSGShaderEffectNode;
+class QSGGuiThreadShaderEffectManager;
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERLOOP)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_COMPILATION)
@@ -166,6 +168,9 @@ public:
virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode) = 0;
virtual QSGNinePatchNode *createNinePatchNode() = 0;
virtual QSGLayer *createLayer(QSGRenderContext *renderContext) = 0;
+ virtual QSGGuiThreadShaderEffectManager *createGuiThreadShaderEffectManager();
+ virtual QSGShaderEffectNode *createShaderEffectNode(QSGRenderContext *renderContext,
+ QSGGuiThreadShaderEffectManager *mgr);
virtual QAnimationDriver *createAnimationDriver(QObject *parent);
virtual QSize minimumFBOSize() const;
diff --git a/src/quick/scenegraph/qsgcontextplugin_p.h b/src/quick/scenegraph/qsgcontextplugin_p.h
index 779dca62fc..08c3d21408 100644
--- a/src/quick/scenegraph/qsgcontextplugin_p.h
+++ b/src/quick/scenegraph/qsgcontextplugin_p.h
@@ -65,7 +65,7 @@ class QSGRenderLoop;
struct Q_QUICK_PRIVATE_EXPORT QSGContextFactoryInterface : public QFactoryInterface
{
enum Flag {
- SupportsShaderEffectV2 = 0x01
+ SupportsShaderEffectNode = 0x01
};
Q_DECLARE_FLAGS(Flags, Flag)