aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@theqtcompany.com>2016-04-20 12:36:15 +0200
committerLaszlo Agocs <laszlo.agocs@theqtcompany.com>2016-04-29 09:36:33 +0000
commite88e2940598086b57e6c844afa2eca4153d0f528 (patch)
tree54dc790adf9b8b384cb7067cce848c32dd58057f /src/quick
parente4d56b01b30ad6c8482dab3dab6600676a9d6632 (diff)
Adaptable shader effect enablers
The D3D12 node implementation is mostly missing. The rest of the enablers should be in place now however. Importing QtQuick 2.8 provides new properties for ShaderEffect: - shaderType can be used to decide which language should be used - shaderCompilationType tells if compilation is runtime or offline - shaderSourceType tells if the vertex/fragmentShader properties refer to source strings or source files or pre-compiled bytecode files The last two are bitmasks. In practice however we will support only one approach per backend for now (runtime + source string for OpenGL, offline + bytecode for D3D12). In addition to QSGShaderEffectNode, introduce the QSGGuiThreadShaderEffectManager interface. This provides the gui thread bits for the above and performs shader reflection. Backends that use the new ShaderEffect system must provide both. For each ShaderEffect item there will be a manager (on the gui thread) and a node (on the render thread). Reflection is expected to be done via standard helper libs (d3d12shader + D3DReflect from d3dcompiler for the D3D12 backend), or via manually inspecting SPIR-V, or parsing the source (like the GL path does now), or by using some 3rd party library (not recommended). In any case we require that reflection is doable on the gui thread without dependencies to the actual graphics API. The ShaderEffect documentation is greatly extended, covering HLSL and the new properties. The test app uses manually compiled shaders on its HLSL path for now. This is because there is no story yet for build system integration for public use (the internal HLSL -> bytecode in C header rule is only suitable for the d3d12 plugin itself, apps need something different). Change-Id: Id112104906fbcb26b9902a35f19d8d509b340d1b Reviewed-by: Andy Nichols <andy.nichols@qt.io>
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)