diff options
30 files changed, 1625 insertions, 104 deletions
diff --git a/src/plugins/scenegraph/d3d12/d3d12.pro b/src/plugins/scenegraph/d3d12/d3d12.pro index 32bd6810e3..4ca62bc1a8 100644 --- a/src/plugins/scenegraph/d3d12/d3d12.pro +++ b/src/plugins/scenegraph/d3d12/d3d12.pro @@ -22,7 +22,8 @@ SOURCES += \ $$PWD/qsgd3d12imagenode.cpp \ $$PWD/qsgd3d12glyphnode.cpp \ $$PWD/qsgd3d12glyphcache.cpp \ - $$PWD/qsgd3d12layer.cpp + $$PWD/qsgd3d12layer.cpp \ + $$PWD/qsgd3d12shadereffectnode.cpp NO_PCH_SOURCES += \ $$PWD/qsgd3d12engine.cpp @@ -42,9 +43,10 @@ HEADERS += \ $$PWD/qsgd3d12imagenode_p.h \ $$PWD/qsgd3d12glyphnode_p.h \ $$PWD/qsgd3d12glyphcache_p.h \ - $$PWD/qsgd3d12layer_p.h + $$PWD/qsgd3d12layer_p.h \ + $$PWD/qsgd3d12shadereffectnode_p.h -LIBS += -ldxgi -ld3d12 +LIBS += -ldxgi -ld3d12 -ld3dcompiler include($$PWD/shaders/shaders.pri) diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp index ba1a88d34a..2762177e5d 100644 --- a/src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp +++ b/src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp @@ -63,7 +63,7 @@ QSGContext *QSGD3D12Adaptation::create(const QString &) const QSGContextFactoryInterface::Flags QSGD3D12Adaptation::flags(const QString &) const { - return QSGContextFactoryInterface::SupportsShaderEffectV2; + return QSGContextFactoryInterface::SupportsShaderEffectNode; } QSGRenderLoop *QSGD3D12Adaptation::createWindowManager() diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12context.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12context.cpp index d43dcd5997..9144794d87 100644 --- a/src/plugins/scenegraph/d3d12/qsgd3d12context.cpp +++ b/src/plugins/scenegraph/d3d12/qsgd3d12context.cpp @@ -43,6 +43,7 @@ #include "qsgd3d12imagenode_p.h" #include "qsgd3d12glyphnode_p.h" #include "qsgd3d12layer_p.h" +#include "qsgd3d12shadereffectnode_p.h" QT_BEGIN_NAMESPACE @@ -82,9 +83,23 @@ QSGNinePatchNode *QSGD3D12Context::createNinePatchNode() return nullptr; } -QSGLayer *QSGD3D12Context::createLayer(QSGRenderContext *rc) +QSGLayer *QSGD3D12Context::createLayer(QSGRenderContext *renderContext) { - return new QSGD3D12Layer(static_cast<QSGD3D12RenderContext *>(rc)); + QSGD3D12RenderContext *rc = static_cast<QSGD3D12RenderContext *>(renderContext); + return new QSGD3D12Layer(rc); +} + +QSGGuiThreadShaderEffectManager *QSGD3D12Context::createGuiThreadShaderEffectManager() +{ + return new QSGD3D12GuiThreadShaderEffectManager; +} + +QSGShaderEffectNode *QSGD3D12Context::createShaderEffectNode(QSGRenderContext *renderContext, + QSGGuiThreadShaderEffectManager *mgr) +{ + QSGD3D12RenderContext *rc = static_cast<QSGD3D12RenderContext *>(renderContext); + QSGD3D12GuiThreadShaderEffectManager *dmgr = static_cast<QSGD3D12GuiThreadShaderEffectManager *>(mgr); + return new QSGD3D12ShaderEffectNode(rc, dmgr); } QSize QSGD3D12Context::minimumFBOSize() const diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12context_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12context_p.h index c597ed90dd..56952519a1 100644 --- a/src/plugins/scenegraph/d3d12/qsgd3d12context_p.h +++ b/src/plugins/scenegraph/d3d12/qsgd3d12context_p.h @@ -66,7 +66,10 @@ public: QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override; QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode) override; QSGNinePatchNode *createNinePatchNode() override; - QSGLayer *createLayer(QSGRenderContext *rc) override; + QSGLayer *createLayer(QSGRenderContext *renderContext) override; + QSGGuiThreadShaderEffectManager *createGuiThreadShaderEffectManager() override; + QSGShaderEffectNode *createShaderEffectNode(QSGRenderContext *renderContext, + QSGGuiThreadShaderEffectManager *mgr) override; QSize minimumFBOSize() const override; QSurfaceFormat defaultSurfaceFormat() const override; QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext) override; diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode.cpp new file mode 100644 index 0000000000..49f517f877 --- /dev/null +++ b/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode.cpp @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgd3d12shadereffectnode_p.h" +#include "qsgd3d12rendercontext_p.h" +#include "qsgd3d12engine_p.h" +#include <QtCore/qfile.h> +#include <QtQml/qqmlfile.h> + +#include <d3d12shader.h> +#include <d3dcompiler.h> + +QT_BEGIN_NAMESPACE + +// NOTE: Avoid categorized logging. It is slow. + +#define DECLARE_DEBUG_VAR(variable) \ + static bool debug_ ## variable() \ + { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; } + +DECLARE_DEBUG_VAR(render) + +QSGD3D12ShaderEffectNode::QSGD3D12ShaderEffectNode(QSGD3D12RenderContext *rc, QSGD3D12GuiThreadShaderEffectManager *mgr) + : QSGShaderEffectNode(mgr), + m_rc(rc), + m_mgr(mgr) +{ + // ### no material yet, it will just crash + //setMaterial(&m_material); +} + +QRectF QSGD3D12ShaderEffectNode::normalizedTextureSubRect() const +{ + return QRectF(0, 0, 1, 1); + // ### +} + +void QSGD3D12ShaderEffectNode::sync(SyncData *syncData) +{ + if (Q_UNLIKELY(debug_render())) + qDebug() << "shadereffect node sync" << syncData->dirty; + + // ### +} + +QSGGuiThreadShaderEffectManager::ShaderType QSGD3D12GuiThreadShaderEffectManager::shaderType() const +{ + return HLSL; +} + +int QSGD3D12GuiThreadShaderEffectManager::shaderCompilationType() const +{ + return OfflineCompilation; +} + +int QSGD3D12GuiThreadShaderEffectManager::shaderSourceType() const +{ + return ShaderByteCode; +} + +bool QSGD3D12GuiThreadShaderEffectManager::hasSeparateSamplerAndTextureObjects() const +{ + return true; +} + +QString QSGD3D12GuiThreadShaderEffectManager::log() const +{ + return QString(); +} + +QSGGuiThreadShaderEffectManager::Status QSGD3D12GuiThreadShaderEffectManager::status() const +{ + return Compiled; +} + +struct RefGuard { + RefGuard(IUnknown *p) : p(p) { } + ~RefGuard() { p->Release(); } + IUnknown *p; +}; + +bool QSGD3D12GuiThreadShaderEffectManager::reflect(const QByteArray &src, ShaderInfo *result) +{ + const QString fn = QQmlFile::urlToLocalFileOrQrc(src); + QFile f(fn); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("ShaderEffect: Failed to read %s", qPrintable(fn)); + return false; + } + result->blob = f.readAll(); + f.close(); + + ID3D12ShaderReflection *reflector; + HRESULT hr = D3DReflect(result->blob.constData(), result->blob.size(), IID_PPV_ARGS(&reflector)); + if (FAILED(hr)) { + qWarning("D3D shader reflection failed: 0x%x", hr); + return false; + } + RefGuard rg(reflector); + + D3D12_SHADER_DESC shaderDesc; + reflector->GetDesc(&shaderDesc); + + const uint progType = (shaderDesc.Version & 0xFFFF0000) >> 16; + const uint major = (shaderDesc.Version & 0x000000F0) >> 4; + const uint minor = (shaderDesc.Version & 0x0000000F); + + switch (progType) { + case D3D12_SHVER_VERTEX_SHADER: + result->type = ShaderInfo::TypeVertex; + break; + case D3D12_SHVER_PIXEL_SHADER: + result->type = ShaderInfo::TypeFragment; + break; + default: + result->type = ShaderInfo::TypeOther; + qWarning("D3D shader is of unknown type 0x%x", shaderDesc.Version); + return false; + } + + if (major < 5) { + qWarning("D3D shader model version %u.%u is too low", major, minor); + return false; + } + + const int ieCount = shaderDesc.InputParameters; + const int cbufferCount = shaderDesc.ConstantBuffers; + const int boundResCount = shaderDesc.BoundResources; + + if (ieCount < 1) { + qWarning("Invalid shader: Not enough input parameters (%d)", ieCount); + return false; + } + if (cbufferCount < 1) { + qWarning("Invalid shader: Shader has no constant buffers"); + return false; + } + if (boundResCount < 1) { + qWarning("Invalid shader: No resources bound. Expected to have at least a constant buffer bound."); + return false; + } + + if (Q_UNLIKELY(debug_render())) + qDebug("Shader reflection size %d type %d v%u.%u input elems %d cbuffers %d boundres %d", + result->blob.size(), result->type, major, minor, ieCount, cbufferCount, boundResCount); + + for (int i = 0; i < ieCount; ++i) { + D3D12_SIGNATURE_PARAMETER_DESC desc; + if (FAILED(reflector->GetInputParameterDesc(i, &desc))) { + qWarning("D3D reflection: Failed to query input parameter %d", i); + return false; + } + if (desc.SystemValueType != D3D_NAME_UNDEFINED) + continue; + ShaderInfo::InputParameter param; + param.semanticName = QByteArray(desc.SemanticName); + param.semanticIndex = desc.SemanticIndex; + result->inputParameters.append(param); + } + + for (int i = 0; i < boundResCount; ++i) { + D3D12_SHADER_INPUT_BIND_DESC desc; + if (FAILED(reflector->GetResourceBindingDesc(i, &desc))) { + qWarning("D3D reflection: Failed to query resource binding %d", i); + continue; + } + bool gotCBuffer = false; + if (desc.Type == D3D_SIT_CBUFFER) { + ID3D12ShaderReflectionConstantBuffer *cbuf = reflector->GetConstantBufferByName(desc.Name); + D3D12_SHADER_BUFFER_DESC bufDesc; + if (FAILED(cbuf->GetDesc(&bufDesc))) { + qWarning("D3D reflection: Failed to query constant buffer description"); + continue; + } + if (gotCBuffer) { + qWarning("D3D reflection: Found more than one constant buffers. Only the first one is used."); + continue; + } + gotCBuffer = true; + for (uint cbIdx = 0; cbIdx < bufDesc.Variables; ++cbIdx) { + ID3D12ShaderReflectionVariable *cvar = cbuf->GetVariableByIndex(cbIdx); + D3D12_SHADER_VARIABLE_DESC varDesc; + if (FAILED(cvar->GetDesc(&varDesc))) { + qWarning("D3D reflection: Failed to query constant buffer variable %d", cbIdx); + return false; + } + ShaderInfo::Variable v; + v.type = ShaderInfo::Constant; + v.name = QByteArray(varDesc.Name); + v.offset = varDesc.StartOffset; + v.size = varDesc.Size; + result->variables.append(v); + } + } else if (desc.Type == D3D_SIT_TEXTURE) { + if (desc.Dimension != D3D_SRV_DIMENSION_TEXTURE2D) { + qWarning("D3D reflection: Texture %s is not a 2D texture, ignoring.", qPrintable(desc.Name)); + continue; + } + if (desc.NumSamples != (UINT) -1) { + qWarning("D3D reflection: Texture %s is multisample (%u), ignoring.", qPrintable(desc.Name), desc.NumSamples); + continue; + } + if (desc.BindCount != 1) { + qWarning("D3D reflection: Texture %s is an array, ignoring.", qPrintable(desc.Name)); + continue; + } + if (desc.Space != 0) { + qWarning("D3D reflection: Texture %s is not using register space 0, ignoring.", qPrintable(desc.Name)); + continue; + } + ShaderInfo::Variable v; + v.type = ShaderInfo::Texture; + v.name = QByteArray(desc.Name); + v.bindPoint = desc.BindPoint; + result->variables.append(v); + } else if (desc.Type == D3D_SIT_SAMPLER) { + if (desc.BindCount != 1) { + qWarning("D3D reflection: Sampler %s is an array, ignoring.", qPrintable(desc.Name)); + continue; + } + if (desc.Space != 0) { + qWarning("D3D reflection: Sampler %s is not using register space 0, ignoring.", qPrintable(desc.Name)); + continue; + } + ShaderInfo::Variable v; + v.type = ShaderInfo::Sampler; + v.name = QByteArray(desc.Name); + v.bindPoint = desc.BindPoint; + result->variables.append(v); + } else { + qWarning("D3D reflection: Resource binding %d has an unknown type of %d and will be ignored.", i, desc.Type); + continue; + } + } + + if (Q_UNLIKELY(debug_render())) { + for (int i = 0; i < result->inputParameters.count(); ++i) { + const ShaderInfo::InputParameter &p(result->inputParameters.at(i)); + qDebug() << "input" << i << p; + } + for (int i = 0; i < result->variables.count(); ++i) { + const ShaderInfo::Variable &v(result->variables.at(i)); + qDebug() << "var" << i << v; + } + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode_p.h new file mode 100644 index 0000000000..f88e028b35 --- /dev/null +++ b/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGD3D12SHADEREFFECTNODE_P_H +#define QSGD3D12SHADEREFFECTNODE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qsgadaptationlayer_p.h> +#include "qsgd3d12builtinmaterials_p.h" + +QT_BEGIN_NAMESPACE + +class QSGD3D12RenderContext; +class QSGD3D12GuiThreadShaderEffectManager; + +class QSGD3D12ShaderEffectNode : public QSGShaderEffectNode +{ +public: + QSGD3D12ShaderEffectNode(QSGD3D12RenderContext *rc, QSGD3D12GuiThreadShaderEffectManager *mgr); + + QRectF normalizedTextureSubRect() const override; + void sync(SyncData *syncData) override; + +private: + QSGD3D12RenderContext *m_rc; + QSGD3D12GuiThreadShaderEffectManager *m_mgr; +}; + +class QSGD3D12GuiThreadShaderEffectManager : public QSGGuiThreadShaderEffectManager +{ +public: + ShaderType shaderType() const override; + int shaderCompilationType() const override; + int shaderSourceType() const override; + + bool hasSeparateSamplerAndTextureObjects() const override; + + QString log() const override; + Status status() const override; + + bool reflect(const QByteArray &src, ShaderInfo *result) override; +}; + +QT_END_NAMESPACE + +#endif // QSGD3D12SHADEREFFECTNODE_P_H 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) diff --git a/tests/manual/nodetypes/Effects.qml b/tests/manual/nodetypes/Effects.qml new file mode 100644 index 0000000000..8af9a5eb95 --- /dev/null +++ b/tests/manual/nodetypes/Effects.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Use QtQuick 2.8 to get shaderType and the other new properties +import QtQuick 2.8 + +Item { + Rectangle { + color: "gray" + anchors.margins: 10 + anchors.fill: parent + Image { + id: image + source: "qrc:/qt.png" + } + ShaderEffectSource { + id: effectSource + sourceItem: image + hideSource: true + } + ShaderEffect { + width: image.width + height: image.height + anchors.centerIn: parent + + property variant source: effectSource + property real amplitude: 0.04 * 0.2 + property real frequency: 20 + property real time: 0 + + NumberAnimation on time { loops: Animation.Infinite; from: 0; to: Math.PI * 2; duration: 600 } + + property string glslFragmentShader: + "uniform sampler2D source;" + + "uniform highp float amplitude;" + + "uniform highp float frequency;" + + "uniform highp float time;" + + "uniform lowp float qt_Opacity;" + + "varying highp vec2 qt_TexCoord0;" + + "void main() {" + + " highp vec2 p = sin(time + frequency * qt_TexCoord0);" + + " gl_FragColor = texture2D(source, qt_TexCoord0 + amplitude * vec2(p.y, -p.x)) * qt_Opacity;" + + "}" + + property string hlslVertexShaderByteCode: "qrc:/vs_wobble.cso" + property string hlslPixelShaderByteCode: "qrc:/ps_wobble.cso" + + // This effect does not need a custom vertex shader but have one with HLSL just to test that path as well. + vertexShader: shaderType === ShaderEffect.GLSL ? "" + : (shaderType === ShaderEffect.HLSL ? hlslVertexShaderByteCode : "") + fragmentShader: shaderType === ShaderEffect.GLSL ? glslFragmentShader + : (shaderType === ShaderEffect.HLSL ? hlslPixelShaderByteCode : "") + } + } +} diff --git a/tests/manual/nodetypes/effects.hlsl b/tests/manual/nodetypes/effects.hlsl new file mode 100644 index 0000000000..203dbda7f2 --- /dev/null +++ b/tests/manual/nodetypes/effects.hlsl @@ -0,0 +1,32 @@ +cbuffer ConstantBuffer : register(b0) +{ + float4x4 qt_Matrix; + float qt_Opacity; + + float amplitude; + float frequency; + float time; +}; + +struct PSInput +{ + float4 position : SV_POSITION; + float2 coord : TEXCOORD0; +}; + +PSInput VS_Wobble(float4 position : POSITION, float2 coord : TEXCOORD0) +{ + PSInput result; + result.position = mul(qt_Matrix, position); + result.coord = coord; + return result; +} + +Texture2D source : register(t0); +SamplerState sourceSampler : register(s0); + +float4 PS_Wobble(PSInput input) : SV_TARGET +{ + float2 p = sin(time + frequency * input.coord); + return source.Sample(sourceSampler, input.coord + amplitude * float2(p.y, -p.x)) * qt_Opacity; +} diff --git a/tests/manual/nodetypes/hlslcompile.bat b/tests/manual/nodetypes/hlslcompile.bat new file mode 100644 index 0000000000..8f7ca86069 --- /dev/null +++ b/tests/manual/nodetypes/hlslcompile.bat @@ -0,0 +1,2 @@ +fxc /E VS_Wobble /T vs_5_0 /Fo vs_wobble.cso effects.hlsl +fxc /E PS_Wobble /T ps_5_0 /Fo ps_wobble.cso effects.hlsl diff --git a/tests/manual/nodetypes/main.qml b/tests/manual/nodetypes/main.qml index fe75e2d948..10f5840262 100644 --- a/tests/manual/nodetypes/main.qml +++ b/tests/manual/nodetypes/main.qml @@ -68,5 +68,8 @@ Item { if (event.key === Qt.Key_L) loader.source = "qrc:/Layers.qml"; + + if (event.key === Qt.Key_E) + loader.source = "qrc:/Effects.qml"; } } diff --git a/tests/manual/nodetypes/nodetypes.cpp b/tests/manual/nodetypes/nodetypes.cpp index aaa641f300..3ebae43c00 100644 --- a/tests/manual/nodetypes/nodetypes.cpp +++ b/tests/manual/nodetypes/nodetypes.cpp @@ -67,6 +67,7 @@ int main(int argc, char **argv) qDebug(" [T] - Text"); qDebug(" [A] - Render thread Animator"); qDebug(" [L] - Layers"); + qDebug(" [E] - Effects"); qDebug("\nPress S to stop the currently running test\n"); Helper helper; diff --git a/tests/manual/nodetypes/nodetypes.pro b/tests/manual/nodetypes/nodetypes.pro index a1633dcf22..51a6ac3e5a 100644 --- a/tests/manual/nodetypes/nodetypes.pro +++ b/tests/manual/nodetypes/nodetypes.pro @@ -4,4 +4,5 @@ SOURCES += nodetypes.cpp RESOURCES += nodetypes.qrc -OTHER_FILES += main.qml Rects.qml LotsOfRects.qml Images.qml Text.qml Animators.qml Layers.qml +OTHER_FILES += main.qml Rects.qml LotsOfRects.qml \ + Images.qml Text.qml Animators.qml Layers.qml Effects.qml effects.hlsl diff --git a/tests/manual/nodetypes/nodetypes.qrc b/tests/manual/nodetypes/nodetypes.qrc index b10fceef24..f6c007e3f3 100644 --- a/tests/manual/nodetypes/nodetypes.qrc +++ b/tests/manual/nodetypes/nodetypes.qrc @@ -7,8 +7,11 @@ <file>Text.qml</file> <file>Animators.qml</file> <file>Layers.qml</file> + <file>Effects.qml</file> <file>qt.png</file> <file>face-smile.png</file> <file>shadow.png</file> + <file>vs_wobble.cso</file> + <file>ps_wobble.cso</file> </qresource> </RCC> diff --git a/tests/manual/nodetypes/ps_wobble.cso b/tests/manual/nodetypes/ps_wobble.cso Binary files differnew file mode 100644 index 0000000000..4e5b6a27f4 --- /dev/null +++ b/tests/manual/nodetypes/ps_wobble.cso diff --git a/tests/manual/nodetypes/vs_wobble.cso b/tests/manual/nodetypes/vs_wobble.cso Binary files differnew file mode 100644 index 0000000000..f3a2596457 --- /dev/null +++ b/tests/manual/nodetypes/vs_wobble.cso |