aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/scenegraph/d3d12
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/scenegraph/d3d12')
-rw-r--r--src/plugins/scenegraph/d3d12/d3d12.json3
-rw-r--r--src/plugins/scenegraph/d3d12/d3d12.pro61
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp87
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12adaptation_p.h81
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials.cpp737
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials_p.h253
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12context.cpp142
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12context_p.h84
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12engine.cpp3251
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12engine_p.h394
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12engine_p_p.h454
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12glyphcache.cpp197
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12glyphcache_p.h88
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12glyphnode.cpp90
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12glyphnode_p.h74
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode.cpp123
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode_p.h82
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode.cpp72
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode_p.h74
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12layer.cpp363
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12layer_p.h118
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12material.cpp49
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12material_p.h96
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12painternode.cpp255
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12painternode_p.h120
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12publicnodes.cpp254
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12publicnodes_p.h136
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12rendercontext.cpp169
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12rendercontext_p.h90
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12renderer.cpp785
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12renderer_p.h137
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp534
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h130
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode.cpp1047
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode_p.h177
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12spritenode.cpp314
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12spritenode_p.h90
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12texture.cpp145
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12texture_p.h87
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp1183
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop_p.h129
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/flatcolor.hlsl27
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/mipmapgen.hlsl60
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/shadereffectdefault.hlsl27
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/shaders.pri141
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/smoothcolor.hlsl64
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/smoothtexture.hlsl77
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/sprite.hlsl43
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/stencilclip.hlsl26
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/tdr.hlsl11
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/textmask.hlsl104
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/texture.hlsl33
-rw-r--r--src/plugins/scenegraph/d3d12/shaders/vertexcolor.hlsl32
53 files changed, 13400 insertions, 0 deletions
diff --git a/src/plugins/scenegraph/d3d12/d3d12.json b/src/plugins/scenegraph/d3d12/d3d12.json
new file mode 100644
index 0000000000..c450a38556
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/d3d12.json
@@ -0,0 +1,3 @@
+{
+ "Keys": ["d3d12"]
+}
diff --git a/src/plugins/scenegraph/d3d12/d3d12.pro b/src/plugins/scenegraph/d3d12/d3d12.pro
new file mode 100644
index 0000000000..9cca5458ee
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/d3d12.pro
@@ -0,0 +1,61 @@
+TARGET = qsgd3d12backend
+
+QT += core-private gui-private qml-private quick-private
+
+PLUGIN_TYPE = scenegraph
+PLUGIN_CLASS_NAME = QSGD3D12Adaptation
+load(qt_plugin)
+
+QMAKE_TARGET_PRODUCT = "Qt Quick D3D12 Renderer (Qt $$QT_VERSION)"
+QMAKE_TARGET_DESCRIPTION = "Quick D3D12 Renderer for Qt."
+
+SOURCES += \
+ $$PWD/qsgd3d12adaptation.cpp \
+ $$PWD/qsgd3d12renderloop.cpp \
+ $$PWD/qsgd3d12threadedrenderloop.cpp \
+ $$PWD/qsgd3d12renderer.cpp \
+ $$PWD/qsgd3d12context.cpp \
+ $$PWD/qsgd3d12rendercontext.cpp \
+ $$PWD/qsgd3d12internalrectanglenode.cpp \
+ $$PWD/qsgd3d12material.cpp \
+ $$PWD/qsgd3d12builtinmaterials.cpp \
+ $$PWD/qsgd3d12texture.cpp \
+ $$PWD/qsgd3d12internalimagenode.cpp \
+ $$PWD/qsgd3d12glyphnode.cpp \
+ $$PWD/qsgd3d12glyphcache.cpp \
+ $$PWD/qsgd3d12layer.cpp \
+ $$PWD/qsgd3d12shadereffectnode.cpp \
+ $$PWD/qsgd3d12painternode.cpp \
+ $$PWD/qsgd3d12publicnodes.cpp \
+ $$PWD/qsgd3d12spritenode.cpp
+
+NO_PCH_SOURCES += \
+ $$PWD/qsgd3d12engine.cpp
+
+HEADERS += \
+ $$PWD/qsgd3d12adaptation_p.h \
+ $$PWD/qsgd3d12renderloop_p.h \
+ $$PWD/qsgd3d12threadedrenderloop_p.h \
+ $$PWD/qsgd3d12renderer_p.h \
+ $$PWD/qsgd3d12context_p.h \
+ $$PWD/qsgd3d12rendercontext_p.h \
+ $$PWD/qsgd3d12engine_p.h \
+ $$PWD/qsgd3d12engine_p_p.h \
+ $$PWD/qsgd3d12internalrectanglenode_p.h \
+ $$PWD/qsgd3d12material_p.h \
+ $$PWD/qsgd3d12builtinmaterials_p.h \
+ $$PWD/qsgd3d12texture_p.h \
+ $$PWD/qsgd3d12internalimagenode_p.h \
+ $$PWD/qsgd3d12glyphnode_p.h \
+ $$PWD/qsgd3d12glyphcache_p.h \
+ $$PWD/qsgd3d12layer_p.h \
+ $$PWD/qsgd3d12shadereffectnode_p.h \
+ $$PWD/qsgd3d12painternode_p.h \
+ $$PWD/qsgd3d12publicnodes_p.h \
+ $$PWD/qsgd3d12spritenode_p.h
+
+LIBS += -ldxgi -ld3d12 -ld3dcompiler -ldcomp
+
+include($$PWD/shaders/shaders.pri)
+
+OTHER_FILES += $$PWD/d3d12.json
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp
new file mode 100644
index 0000000000..b93da0ae01
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12adaptation.cpp
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12adaptation_p.h"
+#include "qsgd3d12renderloop_p.h"
+#include "qsgd3d12threadedrenderloop_p.h"
+#include "qsgd3d12context_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGD3D12Adaptation::QSGD3D12Adaptation(QObject *parent)
+ : QSGContextPlugin(parent)
+{
+}
+
+QStringList QSGD3D12Adaptation::keys() const
+{
+ return QStringList() << QLatin1String("d3d12");
+}
+
+QSGContext *QSGD3D12Adaptation::create(const QString &) const
+{
+ if (!contextInstance)
+ contextInstance = new QSGD3D12Context;
+
+ return contextInstance;
+}
+
+QSGContextFactoryInterface::Flags QSGD3D12Adaptation::flags(const QString &) const
+{
+ return QSGContextFactoryInterface::SupportsShaderEffectNode;
+}
+
+QSGRenderLoop *QSGD3D12Adaptation::createWindowManager()
+{
+ static bool threaded = false;
+ static bool envChecked = false;
+ if (!envChecked) {
+ envChecked = true;
+ threaded = qgetenv("QSG_RENDER_LOOP") == QByteArrayLiteral("threaded");
+ }
+
+ if (threaded)
+ return new QSGD3D12ThreadedRenderLoop;
+
+ return new QSGD3D12RenderLoop;
+}
+
+QSGD3D12Context *QSGD3D12Adaptation::contextInstance = nullptr;
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12adaptation_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12adaptation_p.h
new file mode 100644
index 0000000000..035c3408ff
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12adaptation_p.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** 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 QSGD3D12ADAPTATION_P_H
+#define QSGD3D12ADAPTATION_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/qsgcontextplugin_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Context;
+class QSGContext;
+class QSGRenderLoop;
+
+class QSGD3D12Adaptation : public QSGContextPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSGContextFactoryInterface" FILE "d3d12.json")
+
+public:
+ QSGD3D12Adaptation(QObject *parent = 0);
+
+ QStringList keys() const override;
+ QSGContext *create(const QString &key) const override;
+ QSGContextFactoryInterface::Flags flags(const QString &key) const override;
+ QSGRenderLoop *createWindowManager() override;
+
+private:
+ static QSGD3D12Context *contextInstance;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12ADAPTATION_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials.cpp
new file mode 100644
index 0000000000..3351486bc6
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials.cpp
@@ -0,0 +1,737 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12builtinmaterials_p.h"
+#include "qsgd3d12rendercontext_p.h"
+#include <QQuickWindow>
+#include <QtCore/qmath.h>
+#include <QtGui/private/qfixed_p.h>
+
+#include "vs_vertexcolor.hlslh"
+#include "ps_vertexcolor.hlslh"
+#include "vs_flatcolor.hlslh"
+#include "ps_flatcolor.hlslh"
+#include "vs_smoothcolor.hlslh"
+#include "ps_smoothcolor.hlslh"
+#include "vs_texture.hlslh"
+#include "ps_texture.hlslh"
+#include "vs_smoothtexture.hlslh"
+#include "ps_smoothtexture.hlslh"
+#include "vs_textmask.hlslh"
+#include "ps_textmask24.hlslh"
+#include "ps_textmask32.hlslh"
+#include "ps_textmask8.hlslh"
+#include "vs_styledtext.hlslh"
+#include "ps_styledtext.hlslh"
+#include "vs_outlinedtext.hlslh"
+#include "ps_outlinedtext.hlslh"
+
+QT_BEGIN_NAMESPACE
+
+// NB! In HLSL constant buffer data is packed into 4-byte boundaries and, more
+// importantly, it is packed so that it does not cross a 16-byte (float4)
+// boundary. Hence the need for padding in some cases.
+
+static inline QVector4D qsg_premultiply(const QVector4D &c, float globalOpacity)
+{
+ const float o = c.w() * globalOpacity;
+ return QVector4D(c.x() * o, c.y() * o, c.z() * o, o);
+}
+
+static inline QVector4D qsg_premultiply(const QColor &c, float globalOpacity)
+{
+ const float o = c.alphaF() * globalOpacity;
+ return QVector4D(c.redF() * o, c.greenF() * o, c.blueF() * o, o);
+}
+
+static inline int qsg_colorDiff(const QVector4D &a, const QVector4D &b)
+{
+ if (a.x() != b.x())
+ return a.x() > b.x() ? 1 : -1;
+ if (a.y() != b.y())
+ return a.y() > b.y() ? 1 : -1;
+ if (a.z() != b.z())
+ return a.z() > b.z() ? 1 : -1;
+ if (a.w() != b.w())
+ return a.w() > b.w() ? 1 : -1;
+ return 0;
+}
+
+QSGMaterialType QSGD3D12VertexColorMaterial::mtype;
+
+QSGMaterialType *QSGD3D12VertexColorMaterial::type() const
+{
+ return &QSGD3D12VertexColorMaterial::mtype;
+}
+
+int QSGD3D12VertexColorMaterial::compare(const QSGMaterial *other) const
+{
+ Q_UNUSED(other);
+ Q_ASSERT(other && type() == other->type());
+ // As the vertex color material has all its state in the vertex attributes
+ // defined by the geometry, all such materials will be equal.
+ return 0;
+}
+
+static const int VERTEX_COLOR_CB_SIZE_0 = 16 * sizeof(float); // float4x4
+static const int VERTEX_COLOR_CB_SIZE_1 = sizeof(float); // float
+static const int VERTEX_COLOR_CB_SIZE = VERTEX_COLOR_CB_SIZE_0 + VERTEX_COLOR_CB_SIZE_1;
+
+int QSGD3D12VertexColorMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(VERTEX_COLOR_CB_SIZE);
+}
+
+void QSGD3D12VertexColorMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->shaders.vs = g_VS_VertexColor;
+ pipelineState->shaders.vsSize = sizeof(g_VS_VertexColor);
+ pipelineState->shaders.ps = g_PS_VertexColor;
+ pipelineState->shaders.psSize = sizeof(g_PS_VertexColor);
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12VertexColorMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *,
+ ExtraState *,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = 0;
+ quint8 *p = constantBuffer;
+
+ if (state.isMatrixDirty()) {
+ memcpy(p, state.combinedMatrix().constData(), VERTEX_COLOR_CB_SIZE_0);
+ r |= UpdatedConstantBuffer;
+ }
+ p += VERTEX_COLOR_CB_SIZE_0;
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(p, &opacity, VERTEX_COLOR_CB_SIZE_1);
+ r |= UpdatedConstantBuffer;
+ }
+
+ return r;
+}
+
+QSGD3D12FlatColorMaterial::QSGD3D12FlatColorMaterial()
+ : m_color(QColor(255, 255, 255))
+{
+}
+
+QSGMaterialType QSGD3D12FlatColorMaterial::mtype;
+
+QSGMaterialType *QSGD3D12FlatColorMaterial::type() const
+{
+ return &QSGD3D12FlatColorMaterial::mtype;
+}
+
+int QSGD3D12FlatColorMaterial::compare(const QSGMaterial *other) const
+{
+ Q_ASSERT(other && type() == other->type());
+ const QSGD3D12FlatColorMaterial *o = static_cast<const QSGD3D12FlatColorMaterial *>(other);
+ return m_color.rgba() - o->color().rgba();
+}
+
+static const int FLAT_COLOR_CB_SIZE_0 = 16 * sizeof(float); // float4x4
+static const int FLAT_COLOR_CB_SIZE_1 = 4 * sizeof(float); // float4
+static const int FLAT_COLOR_CB_SIZE = FLAT_COLOR_CB_SIZE_0 + FLAT_COLOR_CB_SIZE_1;
+
+int QSGD3D12FlatColorMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(FLAT_COLOR_CB_SIZE);
+}
+
+void QSGD3D12FlatColorMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->shaders.vs = g_VS_FlatColor;
+ pipelineState->shaders.vsSize = sizeof(g_VS_FlatColor);
+ pipelineState->shaders.ps = g_PS_FlatColor;
+ pipelineState->shaders.psSize = sizeof(g_PS_FlatColor);
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12FlatColorMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *,
+ ExtraState *,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = 0;
+ quint8 *p = constantBuffer;
+
+ if (state.isMatrixDirty()) {
+ memcpy(p, state.combinedMatrix().constData(), FLAT_COLOR_CB_SIZE_0);
+ r |= UpdatedConstantBuffer;
+ }
+ p += FLAT_COLOR_CB_SIZE_0;
+
+ const QVector4D color = qsg_premultiply(m_color, state.opacity());
+ const float f[] = { color.x(), color.y(), color.z(), color.w() };
+ if (state.isOpacityDirty() || memcmp(p, f, FLAT_COLOR_CB_SIZE_1)) {
+ memcpy(p, f, FLAT_COLOR_CB_SIZE_1);
+ r |= UpdatedConstantBuffer;
+ }
+
+ return r;
+}
+
+void QSGD3D12FlatColorMaterial::setColor(const QColor &color)
+{
+ m_color = color;
+ setFlag(Blending, m_color.alpha() != 0xFF);
+}
+
+QSGD3D12SmoothColorMaterial::QSGD3D12SmoothColorMaterial()
+{
+ setFlag(RequiresFullMatrixExceptTranslate, true);
+ setFlag(Blending, true);
+}
+
+QSGMaterialType QSGD3D12SmoothColorMaterial::mtype;
+
+QSGMaterialType *QSGD3D12SmoothColorMaterial::type() const
+{
+ return &QSGD3D12SmoothColorMaterial::mtype;
+}
+
+int QSGD3D12SmoothColorMaterial::compare(const QSGMaterial *other) const
+{
+ Q_UNUSED(other);
+ Q_ASSERT(other && type() == other->type());
+ return 0;
+}
+
+static const int SMOOTH_COLOR_CB_SIZE_0 = 16 * sizeof(float); // float4x4
+static const int SMOOTH_COLOR_CB_SIZE_1 = sizeof(float); // float
+static const int SMOOTH_COLOR_CB_SIZE_2 = 2 * sizeof(float); // float2
+static const int SMOOTH_COLOR_CB_SIZE = SMOOTH_COLOR_CB_SIZE_0 + SMOOTH_COLOR_CB_SIZE_1 + SMOOTH_COLOR_CB_SIZE_2;
+
+int QSGD3D12SmoothColorMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(SMOOTH_COLOR_CB_SIZE);
+}
+
+void QSGD3D12SmoothColorMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->shaders.vs = g_VS_SmoothColor;
+ pipelineState->shaders.vsSize = sizeof(g_VS_SmoothColor);
+ pipelineState->shaders.ps = g_PS_SmoothColor;
+ pipelineState->shaders.psSize = sizeof(g_PS_SmoothColor);
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12SmoothColorMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *,
+ ExtraState *,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = 0;
+ quint8 *p = constantBuffer;
+
+ if (state.isMatrixDirty()) {
+ memcpy(p, state.combinedMatrix().constData(), SMOOTH_COLOR_CB_SIZE_0);
+ r |= UpdatedConstantBuffer;
+ }
+ p += SMOOTH_COLOR_CB_SIZE_0;
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(p, &opacity, SMOOTH_COLOR_CB_SIZE_1);
+ r |= UpdatedConstantBuffer;
+ }
+ p += SMOOTH_COLOR_CB_SIZE_1;
+
+ if (state.isMatrixDirty()) {
+ const QRect viewport = state.viewportRect();
+ const float v[] = { 2.0f / viewport.width(), 2.0f / viewport.height() };
+ memcpy(p, v, SMOOTH_COLOR_CB_SIZE_2);
+ r |= UpdatedConstantBuffer;
+ }
+
+ return r;
+}
+
+QSGMaterialType QSGD3D12TextureMaterial::mtype;
+
+QSGMaterialType *QSGD3D12TextureMaterial::type() const
+{
+ return &QSGD3D12TextureMaterial::mtype;
+}
+
+int QSGD3D12TextureMaterial::compare(const QSGMaterial *other) const
+{
+ Q_ASSERT(other && type() == other->type());
+ const QSGD3D12TextureMaterial *o = static_cast<const QSGD3D12TextureMaterial *>(other);
+ if (int diff = m_texture->textureId() - o->texture()->textureId())
+ return diff;
+ return int(m_filtering) - int(o->m_filtering);
+}
+
+static const int TEXTURE_CB_SIZE_0 = 16 * sizeof(float); // float4x4
+static const int TEXTURE_CB_SIZE_1 = sizeof(float); // float
+static const int TEXTURE_CB_SIZE = TEXTURE_CB_SIZE_0 + TEXTURE_CB_SIZE_1;
+
+int QSGD3D12TextureMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(TEXTURE_CB_SIZE);
+}
+
+void QSGD3D12TextureMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->shaders.vs = g_VS_Texture;
+ pipelineState->shaders.vsSize = sizeof(g_VS_Texture);
+ pipelineState->shaders.ps = g_PS_Texture;
+ pipelineState->shaders.psSize = sizeof(g_PS_Texture);
+
+ pipelineState->shaders.rootSig.textureViewCount = 1;
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12TextureMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = 0;
+ quint8 *p = constantBuffer;
+
+ if (state.isMatrixDirty()) {
+ memcpy(p, state.combinedMatrix().constData(), TEXTURE_CB_SIZE_0);
+ r |= UpdatedConstantBuffer;
+ }
+ p += TEXTURE_CB_SIZE_0;
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(p, &opacity, TEXTURE_CB_SIZE_1);
+ r |= UpdatedConstantBuffer;
+ }
+
+ Q_ASSERT(m_texture);
+ m_texture->setFiltering(m_filtering);
+ m_texture->setMipmapFiltering(m_mipmap_filtering);
+ m_texture->setHorizontalWrapMode(m_horizontal_wrap);
+ m_texture->setVerticalWrapMode(m_vertical_wrap);
+
+ QSGD3D12TextureView &tv(pipelineState->shaders.rootSig.textureViews[0]);
+ if (m_filtering == QSGTexture::Linear)
+ tv.filter = m_mipmap_filtering == QSGTexture::Linear
+ ? QSGD3D12TextureView::FilterLinear : QSGD3D12TextureView::FilterMinMagLinearMipNearest;
+ else
+ tv.filter = m_mipmap_filtering == QSGTexture::Linear
+ ? QSGD3D12TextureView::FilterMinMagNearestMipLinear : QSGD3D12TextureView::FilterNearest;
+ tv.addressModeHoriz = m_horizontal_wrap == QSGTexture::ClampToEdge ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap;
+ tv.addressModeVert = m_vertical_wrap == QSGTexture::ClampToEdge ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap;
+
+ m_texture->bind();
+
+ return r;
+}
+
+void QSGD3D12TextureMaterial::setTexture(QSGTexture *texture)
+{
+ m_texture = texture;
+ setFlag(Blending, m_texture ? m_texture->hasAlphaChannel() : false);
+}
+
+QSGD3D12SmoothTextureMaterial::QSGD3D12SmoothTextureMaterial()
+{
+ setFlag(RequiresFullMatrixExceptTranslate, true);
+ setFlag(Blending, true);
+}
+
+QSGMaterialType QSGD3D12SmoothTextureMaterial::mtype;
+
+QSGMaterialType *QSGD3D12SmoothTextureMaterial::type() const
+{
+ return &QSGD3D12SmoothTextureMaterial::mtype;
+}
+
+int QSGD3D12SmoothTextureMaterial::compare(const QSGMaterial *other) const
+{
+ Q_ASSERT(other && type() == other->type());
+ const QSGD3D12SmoothTextureMaterial *o = static_cast<const QSGD3D12SmoothTextureMaterial *>(other);
+ if (int diff = m_texture->textureId() - o->texture()->textureId())
+ return diff;
+ return int(m_filtering) - int(o->m_filtering);
+}
+
+static const int SMOOTH_TEXTURE_CB_SIZE_0 = 16 * sizeof(float); // float4x4
+static const int SMOOTH_TEXTURE_CB_SIZE_1 = sizeof(float); // float
+static const int SMOOTH_TEXTURE_CB_SIZE_2 = 2 * sizeof(float); // float2
+static const int SMOOTH_TEXTURE_CB_SIZE = SMOOTH_TEXTURE_CB_SIZE_0 + SMOOTH_TEXTURE_CB_SIZE_1 + SMOOTH_TEXTURE_CB_SIZE_2;
+
+int QSGD3D12SmoothTextureMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(SMOOTH_TEXTURE_CB_SIZE);
+}
+
+void QSGD3D12SmoothTextureMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->shaders.vs = g_VS_SmoothTexture;
+ pipelineState->shaders.vsSize = sizeof(g_VS_SmoothTexture);
+ pipelineState->shaders.ps = g_PS_SmoothTexture;
+ pipelineState->shaders.psSize = sizeof(g_PS_SmoothTexture);
+
+ pipelineState->shaders.rootSig.textureViewCount = 1;
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12SmoothTextureMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = 0;
+ quint8 *p = constantBuffer;
+
+ if (state.isMatrixDirty()) {
+ memcpy(p, state.combinedMatrix().constData(), SMOOTH_TEXTURE_CB_SIZE_0);
+ r |= UpdatedConstantBuffer;
+ }
+ p += SMOOTH_TEXTURE_CB_SIZE_0;
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(p, &opacity, SMOOTH_TEXTURE_CB_SIZE_1);
+ r |= UpdatedConstantBuffer;
+ }
+ p += SMOOTH_TEXTURE_CB_SIZE_1;
+
+ if (state.isMatrixDirty()) {
+ const QRect viewport = state.viewportRect();
+ const float v[] = { 2.0f / viewport.width(), 2.0f / viewport.height() };
+ memcpy(p, v, SMOOTH_TEXTURE_CB_SIZE_2);
+ r |= UpdatedConstantBuffer;
+ }
+
+ Q_ASSERT(m_texture);
+ m_texture->setFiltering(m_filtering);
+ m_texture->setMipmapFiltering(m_mipmap_filtering);
+ m_texture->setHorizontalWrapMode(m_horizontal_wrap);
+ m_texture->setVerticalWrapMode(m_vertical_wrap);
+
+ QSGD3D12TextureView &tv(pipelineState->shaders.rootSig.textureViews[0]);
+ if (m_filtering == QSGTexture::Linear)
+ tv.filter = m_mipmap_filtering == QSGTexture::Linear
+ ? QSGD3D12TextureView::FilterLinear : QSGD3D12TextureView::FilterMinMagLinearMipNearest;
+ else
+ tv.filter = m_mipmap_filtering == QSGTexture::Linear
+ ? QSGD3D12TextureView::FilterMinMagNearestMipLinear : QSGD3D12TextureView::FilterNearest;
+ tv.addressModeHoriz = m_horizontal_wrap == QSGTexture::ClampToEdge ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap;
+ tv.addressModeVert = m_vertical_wrap == QSGTexture::ClampToEdge ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap;
+
+ m_texture->bind();
+
+ return r;
+}
+
+QSGD3D12TextMaterial::QSGD3D12TextMaterial(StyleType styleType, QSGD3D12RenderContext *rc,
+ const QRawFont &font, QFontEngine::GlyphFormat glyphFormat)
+ : m_styleType(styleType),
+ m_font(font),
+ m_rc(rc)
+{
+ setFlag(Blending, true);
+
+ QRawFontPrivate *fontD = QRawFontPrivate::get(m_font);
+ if (QFontEngine *fontEngine = fontD->fontEngine) {
+ if (glyphFormat == QFontEngine::Format_None)
+ glyphFormat = fontEngine->glyphFormat != QFontEngine::Format_None
+ ? fontEngine->glyphFormat : QFontEngine::Format_A32;
+
+ QSGD3D12Engine *d3dengine = rc->engine();
+ const float devicePixelRatio = d3dengine->windowDevicePixelRatio();
+ QTransform glyphCacheTransform = QTransform::fromScale(devicePixelRatio, devicePixelRatio);
+ if (!fontEngine->supportsTransformation(glyphCacheTransform))
+ glyphCacheTransform = QTransform();
+
+ m_glyphCache = fontEngine->glyphCache(d3dengine, glyphFormat, glyphCacheTransform);
+ if (!m_glyphCache || int(m_glyphCache->glyphFormat()) != glyphFormat) {
+ m_glyphCache = new QSGD3D12GlyphCache(d3dengine, glyphFormat, glyphCacheTransform);
+ fontEngine->setGlyphCache(d3dengine, m_glyphCache.data());
+ rc->registerFontengineForCleanup(fontEngine);
+ }
+ }
+}
+
+QSGMaterialType QSGD3D12TextMaterial::mtype[QSGD3D12TextMaterial::NTextMaterialTypes];
+
+QSGMaterialType *QSGD3D12TextMaterial::type() const
+{
+ // Format_A32 has special blend settings and therefore two materials with
+ // the same style but different formats where one is A32 are treated as
+ // different. This way the renderer can manage the pipeline state properly.
+ const int matStyle = m_styleType * 2;
+ const int matFormat = glyphCache()->glyphFormat() != QFontEngine::Format_A32 ? 0 : 1;
+ return &QSGD3D12TextMaterial::mtype[matStyle + matFormat];
+}
+
+int QSGD3D12TextMaterial::compare(const QSGMaterial *other) const
+{
+ Q_ASSERT(other && type() == other->type());
+ const QSGD3D12TextMaterial *o = static_cast<const QSGD3D12TextMaterial *>(other);
+ if (m_styleType != o->m_styleType)
+ return m_styleType - o->m_styleType;
+ if (m_glyphCache != o->m_glyphCache)
+ return m_glyphCache.data() < o->m_glyphCache.data() ? -1 : 1;
+ if (m_styleShift != o->m_styleShift)
+ return m_styleShift.y() - o->m_styleShift.y();
+ int styleColorDiff = qsg_colorDiff(m_styleColor, o->m_styleColor);
+ if (styleColorDiff)
+ return styleColorDiff;
+ return qsg_colorDiff(m_color, o->m_color);
+}
+
+static const int TEXT_CB_SIZE_0 = 16 * sizeof(float); // float4x4 mvp
+static const int TEXT_CB_SIZE_1 = 2 * sizeof(float); // float2 textureScale
+static const int TEXT_CB_SIZE_2 = sizeof(float); // float dpr
+static const int TEXT_CB_SIZE_3 = sizeof(float); // float color
+static const int TEXT_CB_SIZE_4 = 4 * sizeof(float); // float4 colorVec
+static const int TEXT_CB_SIZE_5 = 2 * sizeof(float); // float2 shift
+static const int TEXT_CB_SIZE_5_PADDING = 2 * sizeof(float); // float2 padding (the next float4 would cross the 16-byte boundary)
+static const int TEXT_CB_SIZE_6 = 4 * sizeof(float); // float4 styleColor
+static const int TEXT_CB_SIZE = TEXT_CB_SIZE_0 + TEXT_CB_SIZE_1 + TEXT_CB_SIZE_2 + TEXT_CB_SIZE_3
+ + TEXT_CB_SIZE_4 + TEXT_CB_SIZE_5 + TEXT_CB_SIZE_5_PADDING + TEXT_CB_SIZE_6;
+
+int QSGD3D12TextMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(TEXT_CB_SIZE);
+}
+
+void QSGD3D12TextMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ if (m_styleType == Normal) {
+ pipelineState->shaders.vs = g_VS_TextMask;
+ pipelineState->shaders.vsSize = sizeof(g_VS_TextMask);
+ switch (glyphCache()->glyphFormat()) {
+ case QFontEngine::Format_A32:
+ pipelineState->shaders.ps = g_PS_TextMask24;
+ pipelineState->shaders.psSize = sizeof(g_PS_TextMask24);
+ break;
+ case QFontEngine::Format_ARGB:
+ pipelineState->shaders.ps = g_PS_TextMask32;
+ pipelineState->shaders.psSize = sizeof(g_PS_TextMask32);
+ break;
+ default:
+ pipelineState->shaders.ps = g_PS_TextMask8;
+ pipelineState->shaders.psSize = sizeof(g_PS_TextMask8);
+ break;
+ }
+ } else if (m_styleType == Outlined) {
+ pipelineState->shaders.vs = g_VS_OutlinedText;
+ pipelineState->shaders.vsSize = sizeof(g_VS_OutlinedText);
+ pipelineState->shaders.ps = g_PS_OutlinedText;
+ pipelineState->shaders.psSize = sizeof(g_PS_OutlinedText);
+ } else {
+ pipelineState->shaders.vs = g_VS_StyledText;
+ pipelineState->shaders.vsSize = sizeof(g_VS_StyledText);
+ pipelineState->shaders.ps = g_PS_StyledText;
+ pipelineState->shaders.psSize = sizeof(g_PS_StyledText);
+ }
+
+ pipelineState->shaders.rootSig.textureViewCount = 1;
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12TextMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = 0;
+ quint8 *p = constantBuffer;
+
+ if (glyphCache()->glyphFormat() == QFontEngine::Format_A32) {
+ // can freely change the state due to the way type() works
+ pipelineState->blend = QSGD3D12PipelineState::BlendColor;
+ extraState->blendFactor = m_color;
+ r |= UpdatedBlendFactor; // must be set always as this affects the command list
+ }
+
+ if (state.isMatrixDirty()) {
+ memcpy(p, state.combinedMatrix().constData(), TEXT_CB_SIZE_0);
+ r |= UpdatedConstantBuffer;
+ }
+ p += TEXT_CB_SIZE_0;
+
+ const QSize sz = glyphCache()->currentSize();
+ const float textureScale[] = { 1.0f / sz.width(), 1.0f / sz.height() };
+ if (state.isCachedMaterialDataDirty() || memcmp(p, textureScale, TEXT_CB_SIZE_1)) {
+ memcpy(p, textureScale, TEXT_CB_SIZE_1);
+ r |= UpdatedConstantBuffer;
+ }
+ p += TEXT_CB_SIZE_1;
+
+ const float dpr = m_rc->engine()->windowDevicePixelRatio();
+ if (state.isCachedMaterialDataDirty() || memcmp(p, &dpr, TEXT_CB_SIZE_2)) {
+ memcpy(p, &dpr, TEXT_CB_SIZE_2);
+ r |= UpdatedConstantBuffer;
+ }
+ p += TEXT_CB_SIZE_2;
+
+ if (glyphCache()->glyphFormat() == QFontEngine::Format_A32) {
+ const QVector4D color = qsg_premultiply(m_color, state.opacity());
+ const float alpha = color.w();
+ if (state.isOpacityDirty() || memcmp(p, &alpha, TEXT_CB_SIZE_3)) {
+ memcpy(p, &alpha, TEXT_CB_SIZE_3);
+ r |= UpdatedConstantBuffer;
+ }
+ } else if (glyphCache()->glyphFormat() == QFontEngine::Format_ARGB) {
+ const float opacity = m_color.w() * state.opacity();
+ if (state.isOpacityDirty() || memcmp(p, &opacity, TEXT_CB_SIZE_3)) {
+ memcpy(p, &opacity, TEXT_CB_SIZE_3);
+ r |= UpdatedConstantBuffer;
+ }
+ } else {
+ const QVector4D color = qsg_premultiply(m_color, state.opacity());
+ const float f[] = { color.x(), color.y(), color.z(), color.w() };
+ if (state.isOpacityDirty() || memcmp(p, f, TEXT_CB_SIZE_4)) {
+ memcpy(p + TEXT_CB_SIZE_3, f, TEXT_CB_SIZE_4);
+ r |= UpdatedConstantBuffer;
+ }
+ }
+ p += TEXT_CB_SIZE_3 + TEXT_CB_SIZE_4;
+
+ if (m_styleType == Styled) {
+ const float f[] = { m_styleShift.x(), m_styleShift.y() };
+ if (state.isCachedMaterialDataDirty() || memcmp(p, f, TEXT_CB_SIZE_5)) {
+ memcpy(p, f, TEXT_CB_SIZE_5);
+ r |= UpdatedConstantBuffer;
+ }
+ }
+ p += TEXT_CB_SIZE_5 + TEXT_CB_SIZE_5_PADDING;
+
+ if (m_styleType == Styled || m_styleType == Outlined) {
+ const QVector4D color = qsg_premultiply(m_styleColor, state.opacity());
+ const float f[] = { color.x(), color.y(), color.z(), color.w() };
+ if (state.isOpacityDirty() || memcmp(p, f, TEXT_CB_SIZE_6)) {
+ memcpy(p, f, TEXT_CB_SIZE_6);
+ r |= UpdatedConstantBuffer;
+ }
+ }
+
+ QSGD3D12TextureView &tv(pipelineState->shaders.rootSig.textureViews[0]);
+ tv.filter = QSGD3D12TextureView::FilterNearest;
+ tv.addressModeHoriz = QSGD3D12TextureView::AddressClamp;
+ tv.addressModeVert = QSGD3D12TextureView::AddressClamp;
+
+ glyphCache()->useTexture();
+
+ return r;
+}
+
+void QSGD3D12TextMaterial::populate(const QPointF &p,
+ const QVector<quint32> &glyphIndexes,
+ const QVector<QPointF> &glyphPositions,
+ QSGGeometry *geometry,
+ QRectF *boundingRect,
+ QPointF *baseLine,
+ const QMargins &margins)
+{
+ Q_ASSERT(m_font.isValid());
+ QVector<QFixedPoint> fixedPointPositions;
+ const int glyphPositionsSize = glyphPositions.size();
+ fixedPointPositions.reserve(glyphPositionsSize);
+ for (int i=0; i < glyphPositionsSize; ++i)
+ fixedPointPositions.append(QFixedPoint::fromPointF(glyphPositions.at(i)));
+
+ QSGD3D12GlyphCache *cache = glyphCache();
+ QRawFontPrivate *fontD = QRawFontPrivate::get(m_font);
+ cache->populate(fontD->fontEngine, glyphIndexes.size(), glyphIndexes.constData(),
+ fixedPointPositions.data());
+ cache->fillInPendingGlyphs();
+
+ int margin = fontD->fontEngine->glyphMargin(cache->glyphFormat());
+
+ float glyphCacheScaleX = cache->transform().m11();
+ float glyphCacheScaleY = cache->transform().m22();
+ float glyphCacheInverseScaleX = 1.0 / glyphCacheScaleX;
+ float glyphCacheInverseScaleY = 1.0 / glyphCacheScaleY;
+
+ Q_ASSERT(geometry->indexType() == QSGGeometry::UnsignedShortType);
+ geometry->allocate(glyphIndexes.size() * 4, glyphIndexes.size() * 6);
+ QVector4D *vp = reinterpret_cast<QVector4D *>(geometry->vertexDataAsTexturedPoint2D());
+ Q_ASSERT(geometry->sizeOfVertex() == sizeof(QVector4D));
+ ushort *ip = geometry->indexDataAsUShort();
+
+ QPointF position(p.x(), p.y() - m_font.ascent());
+ bool supportsSubPixelPositions = fontD->fontEngine->supportsSubPixelPositions();
+ for (int i = 0; i < glyphIndexes.size(); ++i) {
+ QFixed subPixelPosition;
+ if (supportsSubPixelPositions)
+ subPixelPosition = fontD->fontEngine->subPixelPositionForX(QFixed::fromReal(glyphPositions.at(i).x()));
+
+ QTextureGlyphCache::GlyphAndSubPixelPosition glyph(glyphIndexes.at(i), subPixelPosition);
+ const QTextureGlyphCache::Coord &c = cache->coords.value(glyph);
+
+ QPointF glyphPosition = glyphPositions.at(i) + position;
+ float x = (qFloor(glyphPosition.x() * glyphCacheScaleX) * glyphCacheInverseScaleX)
+ + (c.baseLineX * glyphCacheInverseScaleX) - margin;
+ float y = (qRound(glyphPosition.y() * glyphCacheScaleY) * glyphCacheInverseScaleY)
+ - (c.baseLineY * glyphCacheInverseScaleY) - margin;
+
+ float w = c.w * glyphCacheInverseScaleX;
+ float h = c.h * glyphCacheInverseScaleY;
+
+ *boundingRect |= QRectF(x + margin, y + margin, w, h);
+
+ float cx1 = x - margins.left();
+ float cx2 = x + w + margins.right();
+ float cy1 = y - margins.top();
+ float cy2 = y + h + margins.bottom();
+
+ float tx1 = c.x - margins.left();
+ float tx2 = c.x + c.w + margins.right();
+ float ty1 = c.y - margins.top();
+ float ty2 = c.y + c.h + margins.bottom();
+
+ if (baseLine->isNull())
+ *baseLine = glyphPosition;
+
+ vp[4 * i + 0] = QVector4D(cx1, cy1, tx1, ty1);
+ vp[4 * i + 1] = QVector4D(cx2, cy1, tx2, ty1);
+ vp[4 * i + 2] = QVector4D(cx1, cy2, tx1, ty2);
+ vp[4 * i + 3] = QVector4D(cx2, cy2, tx2, ty2);
+
+ int o = i * 4;
+ ip[6 * i + 0] = o + 0;
+ ip[6 * i + 1] = o + 2;
+ ip[6 * i + 2] = o + 3;
+ ip[6 * i + 3] = o + 3;
+ ip[6 * i + 4] = o + 1;
+ ip[6 * i + 5] = o + 0;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials_p.h
new file mode 100644
index 0000000000..8e488f8cd1
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12builtinmaterials_p.h
@@ -0,0 +1,253 @@
+/****************************************************************************
+**
+** 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 QSGD3D12BUILTINMATERIALS_P_H
+#define QSGD3D12BUILTINMATERIALS_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 "qsgd3d12material_p.h"
+#include "qsgd3d12glyphcache_p.h"
+#include <private/qsgtexture_p.h>
+#include <QRawFont>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12RenderContext;
+
+class QSGD3D12VertexColorMaterial : public QSGD3D12Material
+{
+public:
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+private:
+ static QSGMaterialType mtype;
+};
+
+class QSGD3D12FlatColorMaterial : public QSGD3D12Material
+{
+public:
+ QSGD3D12FlatColorMaterial();
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+ void setColor(const QColor &color);
+ QColor color() const { return m_color; }
+
+private:
+ static QSGMaterialType mtype;
+ QColor m_color;
+};
+
+class QSGD3D12SmoothColorMaterial : public QSGD3D12Material
+{
+public:
+ QSGD3D12SmoothColorMaterial();
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+private:
+ static QSGMaterialType mtype;
+};
+
+class QSGD3D12TextureMaterial : public QSGD3D12Material
+{
+public:
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+ void setTexture(QSGTexture *texture);
+ QSGTexture *texture() const { return m_texture; }
+
+ void setMipmapFiltering(QSGTexture::Filtering filter) { m_mipmap_filtering = filter; }
+ QSGTexture::Filtering mipmapFiltering() const { return m_mipmap_filtering; }
+
+ void setFiltering(QSGTexture::Filtering filter) { m_filtering = filter; }
+ QSGTexture::Filtering filtering() const { return m_filtering; }
+
+ void setHorizontalWrapMode(QSGTexture::WrapMode hwrap) { m_horizontal_wrap = hwrap; }
+ QSGTexture::WrapMode horizontalWrapMode() const { return m_horizontal_wrap; }
+
+ void setVerticalWrapMode(QSGTexture::WrapMode vwrap) { m_vertical_wrap = vwrap; }
+ QSGTexture::WrapMode verticalWrapMode() const { return m_vertical_wrap; }
+
+private:
+ static QSGMaterialType mtype;
+
+ QSGTexture *m_texture = nullptr;
+ QSGTexture::Filtering m_filtering = QSGTexture::Nearest;
+ QSGTexture::Filtering m_mipmap_filtering = QSGTexture::None;
+ QSGTexture::WrapMode m_horizontal_wrap = QSGTexture::ClampToEdge;
+ QSGTexture::WrapMode m_vertical_wrap = QSGTexture::ClampToEdge;
+};
+
+class QSGD3D12SmoothTextureMaterial : public QSGD3D12Material
+{
+public:
+ QSGD3D12SmoothTextureMaterial();
+
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+ void setTexture(QSGTexture *texture) { m_texture = texture; }
+ QSGTexture *texture() const { return m_texture; }
+
+ void setMipmapFiltering(QSGTexture::Filtering filter) { m_mipmap_filtering = filter; }
+ QSGTexture::Filtering mipmapFiltering() const { return m_mipmap_filtering; }
+
+ void setFiltering(QSGTexture::Filtering filter) { m_filtering = filter; }
+ QSGTexture::Filtering filtering() const { return m_filtering; }
+
+ void setHorizontalWrapMode(QSGTexture::WrapMode hwrap) { m_horizontal_wrap = hwrap; }
+ QSGTexture::WrapMode horizontalWrapMode() const { return m_horizontal_wrap; }
+
+ void setVerticalWrapMode(QSGTexture::WrapMode vwrap) { m_vertical_wrap = vwrap; }
+ QSGTexture::WrapMode verticalWrapMode() const { return m_vertical_wrap; }
+
+private:
+ static QSGMaterialType mtype;
+
+ QSGTexture *m_texture = nullptr;
+ QSGTexture::Filtering m_filtering = QSGTexture::Nearest;
+ QSGTexture::Filtering m_mipmap_filtering = QSGTexture::None;
+ QSGTexture::WrapMode m_horizontal_wrap = QSGTexture::ClampToEdge;
+ QSGTexture::WrapMode m_vertical_wrap = QSGTexture::ClampToEdge;
+};
+
+class QSGD3D12TextMaterial : public QSGD3D12Material
+{
+public:
+ enum StyleType {
+ Normal,
+ Styled,
+ Outlined,
+
+ NStyleTypes
+ };
+ QSGD3D12TextMaterial(StyleType styleType, QSGD3D12RenderContext *rc, const QRawFont &font,
+ QFontEngine::GlyphFormat glyphFormat = QFontEngine::Format_None);
+
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+ void setColor(const QColor &c) { m_color = QVector4D(c.redF(), c.greenF(), c.blueF(), c.alphaF()); }
+ void setColor(const QVector4D &color) { m_color = color; }
+ const QVector4D &color() const { return m_color; }
+
+ void setStyleShift(const QVector2D &shift) { m_styleShift = shift; }
+ const QVector2D &styleShift() const { return m_styleShift; }
+
+ void setStyleColor(const QColor &c) { m_styleColor = QVector4D(c.redF(), c.greenF(), c.blueF(), c.alphaF()); }
+ void setStyleColor(const QVector4D &color) { m_styleColor = color; }
+ const QVector4D &styleColor() const { return m_styleColor; }
+
+ void populate(const QPointF &position,
+ const QVector<quint32> &glyphIndexes, const QVector<QPointF> &glyphPositions,
+ QSGGeometry *geometry, QRectF *boundingRect, QPointF *baseLine,
+ const QMargins &margins = QMargins(0, 0, 0, 0));
+
+ QSGD3D12GlyphCache *glyphCache() const { return static_cast<QSGD3D12GlyphCache *>(m_glyphCache.data()); }
+
+private:
+ static const int NTextMaterialTypes = NStyleTypes * 2;
+ static QSGMaterialType mtype[NTextMaterialTypes];
+ StyleType m_styleType;
+ QSGD3D12RenderContext *m_rc;
+ QVector4D m_color;
+ QVector2D m_styleShift;
+ QVector4D m_styleColor;
+ QRawFont m_font;
+ QExplicitlySharedDataPointer<QFontEngineGlyphCache> m_glyphCache;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12BUILTINMATERIALS_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12context.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12context.cpp
new file mode 100644
index 0000000000..9b88af995d
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12context.cpp
@@ -0,0 +1,142 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12context_p.h"
+#include "qsgd3d12rendercontext_p.h"
+#include "qsgd3d12internalrectanglenode_p.h"
+#include "qsgd3d12internalimagenode_p.h"
+#include "qsgd3d12glyphnode_p.h"
+#include "qsgd3d12layer_p.h"
+#include "qsgd3d12shadereffectnode_p.h"
+#include "qsgd3d12painternode_p.h"
+#include "qsgd3d12publicnodes_p.h"
+#include "qsgd3d12spritenode_p.h"
+#include <QtQuick/qquickwindow.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGRenderContext *QSGD3D12Context::createRenderContext()
+{
+ return new QSGD3D12RenderContext(this);
+}
+
+QSGInternalRectangleNode *QSGD3D12Context::createInternalRectangleNode()
+{
+ return new QSGD3D12InternalRectangleNode;
+}
+
+QSGInternalImageNode *QSGD3D12Context::createInternalImageNode()
+{
+ return new QSGD3D12InternalImageNode;
+}
+
+QSGPainterNode *QSGD3D12Context::createPainterNode(QQuickPaintedItem *item)
+{
+ return new QSGD3D12PainterNode(item);
+}
+
+QSGGlyphNode *QSGD3D12Context::createGlyphNode(QSGRenderContext *renderContext, bool preferNativeGlyphNode)
+{
+ Q_UNUSED(preferNativeGlyphNode);
+ // ### distance field text rendering is not supported atm
+
+ QSGD3D12RenderContext *rc = static_cast<QSGD3D12RenderContext *>(renderContext);
+ return new QSGD3D12GlyphNode(rc);
+}
+
+QSGLayer *QSGD3D12Context::createLayer(QSGRenderContext *renderContext)
+{
+ 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
+{
+ return QSize(16, 16);
+}
+
+QSurfaceFormat QSGD3D12Context::defaultSurfaceFormat() const
+{
+ QSurfaceFormat format = QSurfaceFormat::defaultFormat();
+
+ if (QQuickWindow::hasDefaultAlphaBuffer())
+ format.setAlphaBufferSize(8);
+
+ return format;
+}
+
+QSGRendererInterface *QSGD3D12Context::rendererInterface(QSGRenderContext *renderContext)
+{
+ return static_cast<QSGD3D12RenderContext *>(renderContext);
+}
+
+QSGRectangleNode *QSGD3D12Context::createRectangleNode()
+{
+ return new QSGD3D12RectangleNode;
+}
+
+QSGImageNode *QSGD3D12Context::createImageNode()
+{
+ return new QSGD3D12ImageNode;
+}
+
+QSGNinePatchNode *QSGD3D12Context::createNinePatchNode()
+{
+ return new QSGD3D12NinePatchNode;
+}
+
+QSGSpriteNode *QSGD3D12Context::createSpriteNode()
+{
+ return new QSGD3D12SpriteNode;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12context_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12context_p.h
new file mode 100644
index 0000000000..70cc606b52
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12context_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** 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 QSGD3D12CONTEXT_P_H
+#define QSGD3D12CONTEXT_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/qsgcontext_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Context : public QSGContext
+{
+public:
+ QSGD3D12Context(QObject *parent = 0) : QSGContext(parent) { }
+
+ QSGRenderContext *createRenderContext() override;
+ QSGInternalRectangleNode *createInternalRectangleNode() override;
+ QSGInternalImageNode *createInternalImageNode() override;
+ QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override;
+ QSGGlyphNode *createGlyphNode(QSGRenderContext *renderContext, bool preferNativeGlyphNode) 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;
+ QSGRectangleNode *createRectangleNode() override;
+ QSGImageNode *createImageNode() override;
+ QSGNinePatchNode *createNinePatchNode() override;
+ QSGSpriteNode *createSpriteNode() override;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12CONTEXT_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12engine.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12engine.cpp
new file mode 100644
index 0000000000..a318ce23f7
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12engine.cpp
@@ -0,0 +1,3251 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12engine_p.h"
+#include "qsgd3d12engine_p_p.h"
+#include "cs_mipmapgen.hlslh"
+#include <QString>
+#include <QColor>
+#include <QLoggingCategory>
+#include <qmath.h>
+#include <qalgorithms.h>
+
+// Comment out to disable DeviceLossTester functionality in order to reduce
+// code size and improve startup perf a tiny bit.
+#define DEVLOSS_TEST
+
+#ifdef DEVLOSS_TEST
+#include "cs_tdr.hlslh"
+#endif
+
+#ifdef Q_OS_WINRT
+#include <QtCore/private/qeventdispatcher_winrt_p.h>
+#include <functional>
+#include <windows.ui.xaml.h>
+#include <windows.ui.xaml.media.dxinterop.h>
+#endif
+
+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)
+DECLARE_DEBUG_VAR(descheap)
+DECLARE_DEBUG_VAR(buffer)
+DECLARE_DEBUG_VAR(texture)
+
+// Except for system info on startup.
+Q_LOGGING_CATEGORY(QSG_LOG_INFO_GENERAL, "qt.scenegraph.general")
+
+
+// Any changes to the defaults below must be reflected in adaptations.qdoc as
+// well and proven by qmlbench or similar.
+
+static const int DEFAULT_SWAP_CHAIN_BUFFER_COUNT = 3;
+static const int DEFAULT_FRAME_IN_FLIGHT_COUNT = 2;
+static const int DEFAULT_WAITABLE_SWAP_CHAIN_MAX_LATENCY = 0;
+
+static const int MAX_DRAW_CALLS_PER_LIST = 4096;
+
+static const int MAX_CACHED_ROOTSIG = 16;
+static const int MAX_CACHED_PSO = 64;
+
+static const int GPU_CBVSRVUAV_DESCRIPTORS = 512;
+
+static const DXGI_FORMAT RT_COLOR_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
+
+static const int BUCKETS_PER_HEAP = 8; // must match freeMap
+static const int DESCRIPTORS_PER_BUCKET = 32; // the bit map (freeMap) is quint32
+static const int MAX_DESCRIPTORS_PER_HEAP = BUCKETS_PER_HEAP * DESCRIPTORS_PER_BUCKET;
+
+D3D12_CPU_DESCRIPTOR_HANDLE QSGD3D12CPUDescriptorHeapManager::allocate(D3D12_DESCRIPTOR_HEAP_TYPE type)
+{
+ D3D12_CPU_DESCRIPTOR_HANDLE h = {};
+ for (Heap &heap : m_heaps) {
+ if (heap.type == type) {
+ for (int bucket = 0; bucket < _countof(heap.freeMap); ++bucket)
+ if (heap.freeMap[bucket]) {
+ uint freePos = qCountTrailingZeroBits(heap.freeMap[bucket]);
+ heap.freeMap[bucket] &= ~(1UL << freePos);
+ if (Q_UNLIKELY(debug_descheap()))
+ qDebug("descriptor handle heap %p type %x reserve in bucket %d index %d", &heap, type, bucket, freePos);
+ freePos += bucket * DESCRIPTORS_PER_BUCKET;
+ h = heap.start;
+ h.ptr += freePos * heap.handleSize;
+ return h;
+ }
+ }
+ }
+
+ Heap heap;
+ heap.type = type;
+ heap.handleSize = m_handleSizes[type];
+
+ D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
+ heapDesc.NumDescriptors = MAX_DESCRIPTORS_PER_HEAP;
+ heapDesc.Type = type;
+ // The heaps created here are _never_ shader-visible.
+
+ HRESULT hr = m_device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&heap.heap));
+ if (FAILED(hr)) {
+ qWarning("Failed to create heap with type 0x%x: %x", type, hr);
+ return h;
+ }
+
+ heap.start = heap.heap->GetCPUDescriptorHandleForHeapStart();
+
+ if (Q_UNLIKELY(debug_descheap()))
+ qDebug("new descriptor heap, type %x, start %llu", type, heap.start.ptr);
+
+ heap.freeMap[0] = 0xFFFFFFFE;
+ for (int i = 1; i < _countof(heap.freeMap); ++i)
+ heap.freeMap[i] = 0xFFFFFFFF;
+
+ h = heap.start;
+
+ m_heaps.append(heap);
+
+ return h;
+}
+
+void QSGD3D12CPUDescriptorHeapManager::release(D3D12_CPU_DESCRIPTOR_HANDLE handle, D3D12_DESCRIPTOR_HEAP_TYPE type)
+{
+ for (Heap &heap : m_heaps) {
+ if (heap.type == type
+ && handle.ptr >= heap.start.ptr
+ && handle.ptr < heap.start.ptr + heap.handleSize * MAX_DESCRIPTORS_PER_HEAP) {
+ unsigned long pos = (handle.ptr - heap.start.ptr) / heap.handleSize;
+ const int bucket = pos / DESCRIPTORS_PER_BUCKET;
+ const int indexInBucket = pos - bucket * DESCRIPTORS_PER_BUCKET;
+ heap.freeMap[bucket] |= 1UL << indexInBucket;
+ if (Q_UNLIKELY(debug_descheap()))
+ qDebug("free descriptor handle heap %p type %x bucket %d index %d", &heap, type, bucket, indexInBucket);
+ return;
+ }
+ }
+ qWarning("QSGD3D12CPUDescriptorHeapManager: Attempted to release untracked descriptor handle %llu of type %d", handle.ptr, type);
+}
+
+void QSGD3D12CPUDescriptorHeapManager::initialize(ID3D12Device *device)
+{
+ m_device = device;
+
+ for (int i = 0; i < D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES; ++i)
+ m_handleSizes[i] = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE(i));
+}
+
+void QSGD3D12CPUDescriptorHeapManager::releaseResources()
+{
+ for (Heap &heap : m_heaps)
+ heap.heap = nullptr;
+
+ m_heaps.clear();
+
+ m_device = nullptr;
+}
+
+// One device per process, one everything else (engine) per window.
+Q_GLOBAL_STATIC(QSGD3D12DeviceManager, deviceManager)
+
+static void getHardwareAdapter(IDXGIFactory1 *factory, IDXGIAdapter1 **outAdapter)
+{
+ const D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_0;
+ ComPtr<IDXGIAdapter1> adapter;
+ DXGI_ADAPTER_DESC1 desc;
+
+ for (int adapterIndex = 0; factory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+ DXGI_ADAPTER_DESC1 desc;
+ adapter->GetDesc1(&desc);
+ const QString name = QString::fromUtf16((char16_t *) desc.Description);
+ qCDebug(QSG_LOG_INFO_GENERAL, "Adapter %d: '%s' (flags 0x%x)", adapterIndex, qPrintable(name), desc.Flags);
+ }
+
+ if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX")) {
+ const int adapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX");
+ if (SUCCEEDED(factory->EnumAdapters1(adapterIndex, &adapter))) {
+ adapter->GetDesc1(&desc);
+ const QString name = QString::fromUtf16((char16_t *) desc.Description);
+ HRESULT hr = D3D12CreateDevice(adapter.Get(), fl, _uuidof(ID3D12Device), nullptr);
+ if (SUCCEEDED(hr)) {
+ qCDebug(QSG_LOG_INFO_GENERAL, "Using requested adapter '%s'", qPrintable(name));
+ *outAdapter = adapter.Detach();
+ return;
+ } else {
+ qWarning("Failed to create device for requested adapter '%s': 0x%x", qPrintable(name), hr);
+ }
+ }
+ }
+
+ for (int adapterIndex = 0; factory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+ adapter->GetDesc1(&desc);
+ if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
+ continue;
+
+ if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), fl, _uuidof(ID3D12Device), nullptr))) {
+ const QString name = QString::fromUtf16((char16_t *) desc.Description);
+ qCDebug(QSG_LOG_INFO_GENERAL, "Using adapter '%s'", qPrintable(name));
+ break;
+ }
+ }
+
+ *outAdapter = adapter.Detach();
+}
+
+ID3D12Device *QSGD3D12DeviceManager::ref()
+{
+ ensureCreated();
+ m_ref.ref();
+ return m_device.Get();
+}
+
+void QSGD3D12DeviceManager::unref()
+{
+ if (!m_ref.deref()) {
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("destroying d3d device");
+ m_device = nullptr;
+ m_factory = nullptr;
+ }
+}
+
+void QSGD3D12DeviceManager::deviceLossDetected()
+{
+ for (DeviceLossObserver *observer : qAsConst(m_observers))
+ observer->deviceLost();
+
+ // Nothing else to do here. All windows are expected to release their
+ // resources and call unref() in response immediately.
+}
+
+IDXGIFactory4 *QSGD3D12DeviceManager::dxgi()
+{
+ ensureCreated();
+ return m_factory.Get();
+}
+
+void QSGD3D12DeviceManager::ensureCreated()
+{
+ if (m_device)
+ return;
+
+ HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(&m_factory));
+ if (FAILED(hr)) {
+ qWarning("Failed to create DXGI: 0x%x", hr);
+ return;
+ }
+
+ ComPtr<IDXGIAdapter1> adapter;
+ getHardwareAdapter(m_factory.Get(), &adapter);
+
+ bool warp = true;
+ if (adapter) {
+ HRESULT hr = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
+ if (SUCCEEDED(hr))
+ warp = false;
+ else
+ qWarning("Failed to create device: 0x%x", hr);
+ }
+
+ if (warp) {
+ qCDebug(QSG_LOG_INFO_GENERAL, "Using WARP");
+ m_factory->EnumWarpAdapter(IID_PPV_ARGS(&adapter));
+ HRESULT hr = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
+ if (FAILED(hr)) {
+ qWarning("Failed to create WARP device: 0x%x", hr);
+ return;
+ }
+ }
+
+ ComPtr<IDXGIAdapter3> adapter3;
+ if (SUCCEEDED(adapter.As(&adapter3))) {
+ DXGI_QUERY_VIDEO_MEMORY_INFO vidMemInfo;
+ if (SUCCEEDED(adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &vidMemInfo))) {
+ qCDebug(QSG_LOG_INFO_GENERAL, "Video memory info: LOCAL: Budget %llu KB CurrentUsage %llu KB AvailableForReservation %llu KB CurrentReservation %llu KB",
+ vidMemInfo.Budget / 1024, vidMemInfo.CurrentUsage / 1024,
+ vidMemInfo.AvailableForReservation / 1024, vidMemInfo.CurrentReservation / 1024);
+ }
+ if (SUCCEEDED(adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &vidMemInfo))) {
+ qCDebug(QSG_LOG_INFO_GENERAL, "Video memory info: NON-LOCAL: Budget %llu KB CurrentUsage %llu KB AvailableForReservation %llu KB CurrentReservation %llu KB",
+ vidMemInfo.Budget / 1024, vidMemInfo.CurrentUsage / 1024,
+ vidMemInfo.AvailableForReservation / 1024, vidMemInfo.CurrentReservation / 1024);
+ }
+ }
+}
+
+void QSGD3D12DeviceManager::registerDeviceLossObserver(DeviceLossObserver *observer)
+{
+ if (!m_observers.contains(observer))
+ m_observers.append(observer);
+}
+
+QSGD3D12Engine::QSGD3D12Engine()
+{
+ d = new QSGD3D12EnginePrivate;
+}
+
+QSGD3D12Engine::~QSGD3D12Engine()
+{
+ d->waitGPU();
+ d->releaseResources();
+ delete d;
+}
+
+bool QSGD3D12Engine::attachToWindow(WId window, const QSize &size, float dpr, int surfaceFormatSamples, bool alpha)
+{
+ if (d->isInitialized()) {
+ qWarning("QSGD3D12Engine: Cannot attach active engine to window");
+ return false;
+ }
+
+ d->initialize(window, size, dpr, surfaceFormatSamples, alpha);
+ return d->isInitialized();
+}
+
+void QSGD3D12Engine::releaseResources()
+{
+ d->releaseResources();
+}
+
+bool QSGD3D12Engine::hasResources() const
+{
+ // An explicit releaseResources() or a device loss results in initialized == false.
+ return d->isInitialized();
+}
+
+void QSGD3D12Engine::setWindowSize(const QSize &size, float dpr)
+{
+ d->setWindowSize(size, dpr);
+}
+
+WId QSGD3D12Engine::window() const
+{
+ return d->currentWindow();
+}
+
+QSize QSGD3D12Engine::windowSize() const
+{
+ return d->currentWindowSize();
+}
+
+float QSGD3D12Engine::windowDevicePixelRatio() const
+{
+ return d->currentWindowDpr();
+}
+
+uint QSGD3D12Engine::windowSamples() const
+{
+ return d->currentWindowSamples();
+}
+
+void QSGD3D12Engine::beginFrame()
+{
+ d->beginFrame();
+}
+
+void QSGD3D12Engine::endFrame()
+{
+ d->endFrame();
+}
+
+void QSGD3D12Engine::beginLayer()
+{
+ d->beginLayer();
+}
+
+void QSGD3D12Engine::endLayer()
+{
+ d->endLayer();
+}
+
+void QSGD3D12Engine::invalidateCachedFrameState()
+{
+ d->invalidateCachedFrameState();
+}
+
+void QSGD3D12Engine::restoreFrameState(bool minimal)
+{
+ d->restoreFrameState(minimal);
+}
+
+void QSGD3D12Engine::finalizePipeline(const QSGD3D12PipelineState &pipelineState)
+{
+ d->finalizePipeline(pipelineState);
+}
+
+uint QSGD3D12Engine::genBuffer()
+{
+ return d->genBuffer();
+}
+
+void QSGD3D12Engine::releaseBuffer(uint id)
+{
+ d->releaseBuffer(id);
+}
+
+void QSGD3D12Engine::resetBuffer(uint id, const quint8 *data, int size)
+{
+ d->resetBuffer(id, data, size);
+}
+
+void QSGD3D12Engine::markBufferDirty(uint id, int offset, int size)
+{
+ d->markBufferDirty(id, offset, size);
+}
+
+void QSGD3D12Engine::queueViewport(const QRect &rect)
+{
+ d->queueViewport(rect);
+}
+
+void QSGD3D12Engine::queueScissor(const QRect &rect)
+{
+ d->queueScissor(rect);
+}
+
+void QSGD3D12Engine::queueSetRenderTarget(uint id)
+{
+ d->queueSetRenderTarget(id);
+}
+
+void QSGD3D12Engine::queueClearRenderTarget(const QColor &color)
+{
+ d->queueClearRenderTarget(color);
+}
+
+void QSGD3D12Engine::queueClearDepthStencil(float depthValue, quint8 stencilValue, ClearFlags which)
+{
+ d->queueClearDepthStencil(depthValue, stencilValue, which);
+}
+
+void QSGD3D12Engine::queueSetBlendFactor(const QVector4D &factor)
+{
+ d->queueSetBlendFactor(factor);
+}
+
+void QSGD3D12Engine::queueSetStencilRef(quint32 ref)
+{
+ d->queueSetStencilRef(ref);
+}
+
+void QSGD3D12Engine::queueDraw(const DrawParams &params)
+{
+ d->queueDraw(params);
+}
+
+void QSGD3D12Engine::present()
+{
+ d->present();
+}
+
+void QSGD3D12Engine::waitGPU()
+{
+ d->waitGPU();
+}
+
+uint QSGD3D12Engine::genTexture()
+{
+ return d->genTexture();
+}
+
+void QSGD3D12Engine::createTexture(uint id, const QSize &size, QImage::Format format, TextureCreateFlags flags)
+{
+ d->createTexture(id, size, format, flags);
+}
+
+void QSGD3D12Engine::queueTextureResize(uint id, const QSize &size)
+{
+ d->queueTextureResize(id, size);
+}
+
+void QSGD3D12Engine::queueTextureUpload(uint id, const QImage &image, const QPoint &dstPos, TextureUploadFlags flags)
+{
+ d->queueTextureUpload(id, QVector<QImage>() << image, QVector<QPoint>() << dstPos, flags);
+}
+
+void QSGD3D12Engine::queueTextureUpload(uint id, const QVector<QImage> &images, const QVector<QPoint> &dstPos,
+ TextureUploadFlags flags)
+{
+ d->queueTextureUpload(id, images, dstPos, flags);
+}
+
+void QSGD3D12Engine::releaseTexture(uint id)
+{
+ d->releaseTexture(id);
+}
+
+void QSGD3D12Engine::useTexture(uint id)
+{
+ d->useTexture(id);
+}
+
+uint QSGD3D12Engine::genRenderTarget()
+{
+ return d->genRenderTarget();
+}
+
+void QSGD3D12Engine::createRenderTarget(uint id, const QSize &size, const QVector4D &clearColor, uint samples)
+{
+ d->createRenderTarget(id, size, clearColor, samples);
+}
+
+void QSGD3D12Engine::releaseRenderTarget(uint id)
+{
+ d->releaseRenderTarget(id);
+}
+
+void QSGD3D12Engine::useRenderTargetAsTexture(uint id)
+{
+ d->useRenderTargetAsTexture(id);
+}
+
+uint QSGD3D12Engine::activeRenderTarget() const
+{
+ return d->activeRenderTarget();
+}
+
+QImage QSGD3D12Engine::executeAndWaitReadbackRenderTarget(uint id)
+{
+ return d->executeAndWaitReadbackRenderTarget(id);
+}
+
+void QSGD3D12Engine::simulateDeviceLoss()
+{
+ d->simulateDeviceLoss();
+}
+
+void *QSGD3D12Engine::getResource(QQuickWindow *, QSGRendererInterface::Resource resource) const
+{
+ return d->getResource(resource);
+}
+
+static inline quint32 alignedSize(quint32 size, quint32 byteAlign)
+{
+ return (size + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+quint32 QSGD3D12Engine::alignedConstantBufferSize(quint32 size)
+{
+ return alignedSize(size, D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
+}
+
+QSGD3D12Format QSGD3D12Engine::toDXGIFormat(QSGGeometry::Type sgtype, int tupleSize, int *size)
+{
+ QSGD3D12Format format = FmtUnknown;
+
+ static const QSGD3D12Format formatMap_ub[] = { FmtUnknown,
+ FmtUNormByte,
+ FmtUNormByte2,
+ FmtUnknown,
+ FmtUNormByte4 };
+
+ static const QSGD3D12Format formatMap_f[] = { FmtUnknown,
+ FmtFloat,
+ FmtFloat2,
+ FmtFloat3,
+ FmtFloat4 };
+
+ switch (sgtype) {
+ case QSGGeometry::UnsignedByteType:
+ format = formatMap_ub[tupleSize];
+ if (size)
+ *size = tupleSize;
+ break;
+ case QSGGeometry::FloatType:
+ format = formatMap_f[tupleSize];
+ if (size)
+ *size = sizeof(float) * tupleSize;
+ break;
+
+ case QSGGeometry::UnsignedShortType:
+ format = FmtUnsignedShort;
+ if (size)
+ *size = sizeof(ushort) * tupleSize;
+ break;
+ case QSGGeometry::UnsignedIntType:
+ format = FmtUnsignedInt;
+ if (size)
+ *size = sizeof(uint) * tupleSize;
+ break;
+
+ case QSGGeometry::ByteType:
+ case QSGGeometry::IntType:
+ case QSGGeometry::ShortType:
+ qWarning("no mapping for GL type 0x%x", sgtype);
+ break;
+
+ default:
+ qWarning("unknown GL type 0x%x", sgtype);
+ break;
+ }
+
+ return format;
+}
+
+int QSGD3D12Engine::mipMapLevels(const QSize &size)
+{
+ return ceil(log2(qMax(size.width(), size.height()))) + 1;
+}
+
+inline static bool isPowerOfTwo(int x)
+{
+ // Assumption: x >= 1
+ return x == (x & -x);
+}
+
+QSize QSGD3D12Engine::mipMapAdjustedSourceSize(const QSize &size)
+{
+ if (size.isEmpty())
+ return size;
+
+ QSize adjustedSize = size;
+
+ // ### for now only power-of-two sizes are mipmap-capable
+ if (!isPowerOfTwo(size.width()))
+ adjustedSize.setWidth(qNextPowerOfTwo(size.width()));
+ if (!isPowerOfTwo(size.height()))
+ adjustedSize.setHeight(qNextPowerOfTwo(size.height()));
+
+ return adjustedSize;
+}
+
+void QSGD3D12EnginePrivate::releaseResources()
+{
+ if (!initialized)
+ return;
+
+ mipmapper.releaseResources();
+ devLossTest.releaseResources();
+
+ frameCommandList = nullptr;
+ copyCommandList = nullptr;
+
+ copyCommandAllocator = nullptr;
+ for (int i = 0; i < frameInFlightCount; ++i) {
+ frameCommandAllocator[i] = nullptr;
+ pframeData[i].gpuCbvSrvUavHeap = nullptr;
+ delete frameFence[i];
+ }
+
+ defaultDS = nullptr;
+ for (int i = 0; i < swapChainBufferCount; ++i) {
+ backBufferRT[i] = nullptr;
+ defaultRT[i] = nullptr;
+ }
+
+ psoCache.clear();
+ rootSigCache.clear();
+ buffers.clear();
+ textures.clear();
+ renderTargets.clear();
+
+ cpuDescHeapManager.releaseResources();
+
+ commandQueue = nullptr;
+ copyCommandQueue = nullptr;
+
+#ifndef Q_OS_WINRT
+ dcompTarget = nullptr;
+ dcompVisual = nullptr;
+ dcompDevice = nullptr;
+#endif
+
+ swapChain = nullptr;
+
+ delete presentFence;
+ textureUploadFence = nullptr;
+
+ deviceManager()->unref();
+
+ initialized = false;
+
+ // 'window' must be kept, may just be a device loss
+}
+
+void QSGD3D12EnginePrivate::initialize(WId w, const QSize &size, float dpr, int surfaceFormatSamples, bool alpha)
+{
+ if (initialized)
+ return;
+
+ window = w;
+ windowSize = size;
+ windowDpr = dpr;
+ windowSamples = qMax(1, surfaceFormatSamples); // may be -1 or 0, whereas windowSamples is uint and >= 1
+ windowAlpha = alpha;
+
+ swapChainBufferCount = qMin(qEnvironmentVariableIntValue("QT_D3D_BUFFER_COUNT"), MAX_SWAP_CHAIN_BUFFER_COUNT);
+ if (swapChainBufferCount < 2)
+ swapChainBufferCount = DEFAULT_SWAP_CHAIN_BUFFER_COUNT;
+
+ frameInFlightCount = qMin(qEnvironmentVariableIntValue("QT_D3D_FRAME_COUNT"), MAX_FRAME_IN_FLIGHT_COUNT);
+ if (frameInFlightCount < 1)
+ frameInFlightCount = DEFAULT_FRAME_IN_FLIGHT_COUNT;
+
+ static const char *latReqEnvVar = "QT_D3D_WAITABLE_SWAP_CHAIN_MAX_LATENCY";
+ if (!qEnvironmentVariableIsSet(latReqEnvVar))
+ waitableSwapChainMaxLatency = DEFAULT_WAITABLE_SWAP_CHAIN_MAX_LATENCY;
+ else
+ waitableSwapChainMaxLatency = qBound(0, qEnvironmentVariableIntValue(latReqEnvVar), 16);
+
+ if (qEnvironmentVariableIsSet("QSG_INFO"))
+ const_cast<QLoggingCategory &>(QSG_LOG_INFO_GENERAL()).setEnabled(QtDebugMsg, true);
+
+ qCDebug(QSG_LOG_INFO_GENERAL, "d3d12 engine init. swap chain buffer count %d, max frames prepared without blocking %d",
+ swapChainBufferCount, frameInFlightCount);
+ if (waitableSwapChainMaxLatency)
+ qCDebug(QSG_LOG_INFO_GENERAL, "Swap chain frame latency waitable object enabled. Frame latency is %d", waitableSwapChainMaxLatency);
+
+ const bool debugLayer = qEnvironmentVariableIntValue("QT_D3D_DEBUG") != 0;
+ if (debugLayer) {
+ qCDebug(QSG_LOG_INFO_GENERAL, "Enabling debug layer");
+#if !defined(Q_OS_WINRT) || !defined(NDEBUG)
+ ComPtr<ID3D12Debug> debugController;
+ if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
+ debugController->EnableDebugLayer();
+#else
+ qCDebug(QSG_LOG_INFO_GENERAL, "Using DebugInterface will not allow certification to pass");
+#endif
+ }
+
+ QSGD3D12DeviceManager *dev = deviceManager();
+ device = dev->ref();
+ dev->registerDeviceLossObserver(this);
+
+ if (debugLayer) {
+ ComPtr<ID3D12InfoQueue> infoQueue;
+ if (SUCCEEDED(device->QueryInterface(IID_PPV_ARGS(&infoQueue)))) {
+ infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
+ infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
+ const bool breakOnWarning = qEnvironmentVariableIntValue("QT_D3D_DEBUG_BREAK_ON_WARNING") != 0;
+ infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, breakOnWarning);
+ D3D12_INFO_QUEUE_FILTER filter = {};
+ D3D12_MESSAGE_ID suppressedMessages[] = {
+ // When using a render target other than the default one we
+ // have no way to know the custom clear color, if there is one.
+ D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE
+ };
+ filter.DenyList.NumIDs = _countof(suppressedMessages);
+ filter.DenyList.pIDList = suppressedMessages;
+ // setting the filter would enable Info messages which we don't need
+ D3D12_MESSAGE_SEVERITY infoSev = D3D12_MESSAGE_SEVERITY_INFO;
+ filter.DenyList.NumSeverities = 1;
+ filter.DenyList.pSeverityList = &infoSev;
+ infoQueue->PushStorageFilter(&filter);
+ }
+ }
+
+ D3D12_COMMAND_QUEUE_DESC queueDesc = {};
+ queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
+ if (FAILED(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue)))) {
+ qWarning("Failed to create command queue");
+ return;
+ }
+
+ queueDesc.Type = D3D12_COMMAND_LIST_TYPE_COPY;
+ if (FAILED(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&copyCommandQueue)))) {
+ qWarning("Failed to create copy command queue");
+ return;
+ }
+
+#ifndef Q_OS_WINRT
+ HWND hwnd = reinterpret_cast<HWND>(w);
+
+ if (windowAlpha) {
+ // Go through DirectComposition for semi-transparent windows since the
+ // traditional approaches won't fly with flip model swapchains.
+ HRESULT hr = DCompositionCreateDevice(nullptr, IID_PPV_ARGS(&dcompDevice));
+ if (SUCCEEDED(hr)) {
+ hr = dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget);
+ if (SUCCEEDED(hr)) {
+ hr = dcompDevice->CreateVisual(&dcompVisual);
+ if (FAILED(hr)) {
+ qWarning("Failed to create DirectComposition visual: 0x%x", hr);
+ windowAlpha = false;
+ }
+ } else {
+ qWarning("Failed to create DirectComposition target: 0x%x", hr);
+ windowAlpha = false;
+ }
+ } else {
+ qWarning("Failed to create DirectComposition device: 0x%x", hr);
+ windowAlpha = false;
+ }
+ }
+
+ if (windowAlpha) {
+ DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
+ swapChainDesc.Width = windowSize.width() * windowDpr;
+ swapChainDesc.Height = windowSize.height() * windowDpr;
+ swapChainDesc.Format = RT_COLOR_FORMAT;
+ swapChainDesc.SampleDesc.Count = 1;
+ swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swapChainDesc.BufferCount = swapChainBufferCount;
+ swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
+ if (waitableSwapChainMaxLatency)
+ swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
+
+ ComPtr<IDXGISwapChain1> baseSwapChain;
+ HRESULT hr = dev->dxgi()->CreateSwapChainForComposition(commandQueue.Get(), &swapChainDesc, nullptr, &baseSwapChain);
+ if (SUCCEEDED(hr)) {
+ if (SUCCEEDED(baseSwapChain.As(&swapChain))) {
+ hr = dcompVisual->SetContent(swapChain.Get());
+ if (SUCCEEDED(hr)) {
+ hr = dcompTarget->SetRoot(dcompVisual.Get());
+ if (FAILED(hr)) {
+ qWarning("SetRoot failed for DirectComposition target: 0x%x", hr);
+ windowAlpha = false;
+ }
+ } else {
+ qWarning("SetContent failed for DirectComposition visual: 0x%x", hr);
+ windowAlpha = false;
+ }
+ } else {
+ qWarning("Failed to cast swap chain");
+ windowAlpha = false;
+ }
+ } else {
+ qWarning("Failed to create swap chain for composition: 0x%x", hr);
+ windowAlpha = false;
+ }
+ }
+
+ if (!windowAlpha) {
+ DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
+ swapChainDesc.BufferCount = swapChainBufferCount;
+ swapChainDesc.BufferDesc.Width = windowSize.width() * windowDpr;
+ swapChainDesc.BufferDesc.Height = windowSize.height() * windowDpr;
+ swapChainDesc.BufferDesc.Format = RT_COLOR_FORMAT;
+ swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // D3D12 requires the flip model
+ swapChainDesc.OutputWindow = hwnd;
+ swapChainDesc.SampleDesc.Count = 1; // Flip does not support MSAA so no choice here
+ swapChainDesc.Windowed = TRUE;
+ if (waitableSwapChainMaxLatency)
+ swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
+
+ ComPtr<IDXGISwapChain> baseSwapChain;
+ HRESULT hr = dev->dxgi()->CreateSwapChain(commandQueue.Get(), &swapChainDesc, &baseSwapChain);
+ if (FAILED(hr)) {
+ qWarning("Failed to create swap chain: 0x%x", hr);
+ return;
+ }
+ if (FAILED(baseSwapChain.As(&swapChain))) {
+ qWarning("Failed to cast swap chain");
+ return;
+ }
+ }
+
+ dev->dxgi()->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER);
+#else
+ DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
+ swapChainDesc.Width = windowSize.width() * windowDpr;
+ swapChainDesc.Height = windowSize.height() * windowDpr;
+ swapChainDesc.Format = RT_COLOR_FORMAT;
+ swapChainDesc.SampleDesc.Count = 1;
+ swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swapChainDesc.BufferCount = swapChainBufferCount;
+ swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
+ if (waitableSwapChainMaxLatency)
+ swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
+
+ ComPtr<IDXGISwapChain1> baseSwapChain;
+ HRESULT hr = dev->dxgi()->CreateSwapChainForComposition(commandQueue.Get(), &swapChainDesc, nullptr, &baseSwapChain);
+ if (FAILED(hr)) {
+ qWarning("Failed to create swap chain for composition: 0x%x", hr);
+ return;
+ }
+ if (FAILED(baseSwapChain.As(&swapChain))) {
+ qWarning("Failed to cast swap chain");
+ return;
+ }
+
+ // The winrt platform plugin returns an ISwapChainPanel* from winId().
+ ComPtr<ABI::Windows::UI::Xaml::Controls::ISwapChainPanel> swapChainPanel
+ = reinterpret_cast<ABI::Windows::UI::Xaml::Controls::ISwapChainPanel *>(window);
+ ComPtr<ISwapChainPanelNative> swapChainPanelNative;
+ if (FAILED(swapChainPanel.As(&swapChainPanelNative))) {
+ qWarning("Failed to cast swap chain panel to native");
+ return;
+ }
+ hr = QEventDispatcherWinRT::runOnXamlThread([this, &swapChainPanelNative]() {
+ return swapChainPanelNative->SetSwapChain(swapChain.Get());
+ });
+ if (FAILED(hr)) {
+ qWarning("Failed to set swap chain on panel: 0x%x", hr);
+ return;
+ }
+#endif
+
+ if (waitableSwapChainMaxLatency) {
+ if (FAILED(swapChain->SetMaximumFrameLatency(waitableSwapChainMaxLatency)))
+ qWarning("Failed to set maximum frame latency to %d", waitableSwapChainMaxLatency);
+ swapEvent = swapChain->GetFrameLatencyWaitableObject();
+ }
+
+ for (int i = 0; i < frameInFlightCount; ++i) {
+ if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&frameCommandAllocator[i])))) {
+ qWarning("Failed to create command allocator");
+ return;
+ }
+ }
+
+ if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COPY, IID_PPV_ARGS(&copyCommandAllocator)))) {
+ qWarning("Failed to create copy command allocator");
+ return;
+ }
+
+ for (int i = 0; i < frameInFlightCount; ++i) {
+ if (!createCbvSrvUavHeap(i, GPU_CBVSRVUAV_DESCRIPTORS))
+ return;
+ }
+
+ cpuDescHeapManager.initialize(device);
+
+ setupDefaultRenderTargets();
+
+ if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, frameCommandAllocator[0].Get(),
+ nullptr, IID_PPV_ARGS(&frameCommandList)))) {
+ qWarning("Failed to create command list");
+ return;
+ }
+ // created in recording state, close it for now
+ frameCommandList->Close();
+
+ if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COPY, copyCommandAllocator.Get(),
+ nullptr, IID_PPV_ARGS(&copyCommandList)))) {
+ qWarning("Failed to create copy command list");
+ return;
+ }
+ copyCommandList->Close();
+
+ frameIndex = 0;
+
+ presentFence = createCPUWaitableFence();
+ for (int i = 0; i < frameInFlightCount; ++i)
+ frameFence[i] = createCPUWaitableFence();
+
+ if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&textureUploadFence)))) {
+ qWarning("Failed to create fence");
+ return;
+ }
+
+ psoCache.setMaxCost(MAX_CACHED_PSO);
+ rootSigCache.setMaxCost(MAX_CACHED_ROOTSIG);
+
+ if (!mipmapper.initialize(this))
+ return;
+
+ if (!devLossTest.initialize(this))
+ return;
+
+ currentRenderTarget = 0;
+
+ initialized = true;
+}
+
+bool QSGD3D12EnginePrivate::createCbvSrvUavHeap(int pframeIndex, int descriptorCount)
+{
+ D3D12_DESCRIPTOR_HEAP_DESC gpuDescHeapDesc = {};
+ gpuDescHeapDesc.NumDescriptors = descriptorCount;
+ gpuDescHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
+ gpuDescHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
+
+ if (FAILED(device->CreateDescriptorHeap(&gpuDescHeapDesc, IID_PPV_ARGS(&pframeData[pframeIndex].gpuCbvSrvUavHeap)))) {
+ qWarning("Failed to create shader-visible CBV-SRV-UAV heap");
+ return false;
+ }
+
+ pframeData[pframeIndex].gpuCbvSrvUavHeapSize = descriptorCount;
+
+ return true;
+}
+
+DXGI_SAMPLE_DESC QSGD3D12EnginePrivate::makeSampleDesc(DXGI_FORMAT format, uint samples)
+{
+ DXGI_SAMPLE_DESC sampleDesc;
+ sampleDesc.Count = 1;
+ sampleDesc.Quality = 0;
+
+ if (samples > 1) {
+ D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {};
+ msaaInfo.Format = format;
+ msaaInfo.SampleCount = samples;
+ if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaInfo, sizeof(msaaInfo)))) {
+ if (msaaInfo.NumQualityLevels > 0) {
+ sampleDesc.Count = samples;
+ sampleDesc.Quality = msaaInfo.NumQualityLevels - 1;
+ } else {
+ qWarning("No quality levels for multisampling with sample count %d", samples);
+ }
+ } else {
+ qWarning("Failed to query multisample quality levels for sample count %d", samples);
+ }
+ }
+
+ return sampleDesc;
+}
+
+ID3D12Resource *QSGD3D12EnginePrivate::createColorBuffer(D3D12_CPU_DESCRIPTOR_HANDLE viewHandle, const QSize &size,
+ const QVector4D &clearColor, uint samples)
+{
+ D3D12_CLEAR_VALUE clearValue = {};
+ clearValue.Format = RT_COLOR_FORMAT;
+ clearValue.Color[0] = clearColor.x();
+ clearValue.Color[1] = clearColor.y();
+ clearValue.Color[2] = clearColor.z();
+ clearValue.Color[3] = clearColor.w();
+
+ D3D12_HEAP_PROPERTIES heapProp = {};
+ heapProp.Type = D3D12_HEAP_TYPE_DEFAULT;
+
+ D3D12_RESOURCE_DESC rtDesc = {};
+ rtDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+ rtDesc.Width = size.width();
+ rtDesc.Height = size.height();
+ rtDesc.DepthOrArraySize = 1;
+ rtDesc.MipLevels = 1;
+ rtDesc.Format = RT_COLOR_FORMAT;
+ rtDesc.SampleDesc = makeSampleDesc(rtDesc.Format, samples);
+ rtDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
+
+ ID3D12Resource *resource = nullptr;
+ if (FAILED(device->CreateCommittedResource(&heapProp, D3D12_HEAP_FLAG_NONE, &rtDesc,
+ D3D12_RESOURCE_STATE_RENDER_TARGET, &clearValue, IID_PPV_ARGS(&resource)))) {
+ qWarning("Failed to create offscreen render target of size %dx%d", size.width(), size.height());
+ return nullptr;
+ }
+
+ device->CreateRenderTargetView(resource, nullptr, viewHandle);
+
+ return resource;
+}
+
+ID3D12Resource *QSGD3D12EnginePrivate::createDepthStencil(D3D12_CPU_DESCRIPTOR_HANDLE viewHandle, const QSize &size, uint samples)
+{
+ D3D12_CLEAR_VALUE depthClearValue = {};
+ depthClearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ depthClearValue.DepthStencil.Depth = 1.0f;
+ depthClearValue.DepthStencil.Stencil = 0;
+
+ D3D12_HEAP_PROPERTIES heapProp = {};
+ heapProp.Type = D3D12_HEAP_TYPE_DEFAULT;
+
+ D3D12_RESOURCE_DESC bufDesc = {};
+ bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+ bufDesc.Width = size.width();
+ bufDesc.Height = size.height();
+ bufDesc.DepthOrArraySize = 1;
+ bufDesc.MipLevels = 1;
+ bufDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ bufDesc.SampleDesc = makeSampleDesc(bufDesc.Format, samples);
+ bufDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+ bufDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
+
+ ID3D12Resource *resource = nullptr;
+ if (FAILED(device->CreateCommittedResource(&heapProp, D3D12_HEAP_FLAG_NONE, &bufDesc,
+ D3D12_RESOURCE_STATE_DEPTH_WRITE, &depthClearValue, IID_PPV_ARGS(&resource)))) {
+ qWarning("Failed to create depth-stencil buffer of size %dx%d", size.width(), size.height());
+ return nullptr;
+ }
+
+ D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {};
+ depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ depthStencilDesc.ViewDimension = bufDesc.SampleDesc.Count <= 1 ? D3D12_DSV_DIMENSION_TEXTURE2D : D3D12_DSV_DIMENSION_TEXTURE2DMS;
+
+ device->CreateDepthStencilView(resource, &depthStencilDesc, viewHandle);
+
+ return resource;
+}
+
+void QSGD3D12EnginePrivate::setupDefaultRenderTargets()
+{
+ for (int i = 0; i < swapChainBufferCount; ++i) {
+ if (FAILED(swapChain->GetBuffer(i, IID_PPV_ARGS(&backBufferRT[i])))) {
+ qWarning("Failed to get buffer %d from swap chain", i);
+ return;
+ }
+ defaultRTV[i] = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
+ if (windowSamples == 1) {
+ defaultRT[i] = backBufferRT[i];
+ device->CreateRenderTargetView(defaultRT[i].Get(), nullptr, defaultRTV[i]);
+ } else {
+ const QSize size(windowSize.width() * windowDpr, windowSize.height() * windowDpr);
+ // Not optimal if the user called setClearColor, but there's so
+ // much we can do. The debug layer warning is suppressed so we're good to go.
+ const QColor cc(Qt::white);
+ const QVector4D clearColor(cc.redF(), cc.greenF(), cc.blueF(), cc.alphaF());
+ ID3D12Resource *msaaRT = createColorBuffer(defaultRTV[i], size, clearColor, windowSamples);
+ if (msaaRT)
+ defaultRT[i].Attach(msaaRT);
+ }
+ }
+
+ defaultDSV = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
+ const QSize size(windowSize.width() * windowDpr, windowSize.height() * windowDpr);
+ ID3D12Resource *ds = createDepthStencil(defaultDSV, size, windowSamples);
+ if (ds)
+ defaultDS.Attach(ds);
+
+ presentFrameIndex = 0;
+}
+
+void QSGD3D12EnginePrivate::setWindowSize(const QSize &size, float dpr)
+{
+ if (!initialized || (windowSize == size && windowDpr == dpr))
+ return;
+
+ waitGPU();
+
+ windowSize = size;
+ windowDpr = dpr;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug() << "resize" << size << dpr;
+
+ // Clear these, otherwise resizing will fail.
+ defaultDS = nullptr;
+ cpuDescHeapManager.release(defaultDSV, D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
+ for (int i = 0; i < swapChainBufferCount; ++i) {
+ backBufferRT[i] = nullptr;
+ defaultRT[i] = nullptr;
+ cpuDescHeapManager.release(defaultRTV[i], D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
+ }
+
+ const int w = windowSize.width() * windowDpr;
+ const int h = windowSize.height() * windowDpr;
+ HRESULT hr = swapChain->ResizeBuffers(swapChainBufferCount, w, h, RT_COLOR_FORMAT,
+ waitableSwapChainMaxLatency ? DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT : 0);
+ if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
+ deviceManager()->deviceLossDetected();
+ return;
+ } else if (FAILED(hr)) {
+ qWarning("Failed to resize buffers: 0x%x", hr);
+ return;
+ }
+
+ setupDefaultRenderTargets();
+}
+
+void QSGD3D12EnginePrivate::deviceLost()
+{
+ qWarning("D3D device lost, will attempt to reinitialize");
+
+ // Release all resources. This is important because otherwise reinitialization may fail.
+ releaseResources();
+
+ // Now in uninitialized state (but 'window' is still valid). Will recreate
+ // all the resources on the next beginFrame().
+}
+
+QSGD3D12CPUWaitableFence *QSGD3D12EnginePrivate::createCPUWaitableFence() const
+{
+ QSGD3D12CPUWaitableFence *f = new QSGD3D12CPUWaitableFence;
+ HRESULT hr = device->CreateFence(f->value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&f->fence));
+ if (FAILED(hr)) {
+ qWarning("Failed to create fence: 0x%x", hr);
+ return f;
+ }
+ f->event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ return f;
+}
+
+void QSGD3D12EnginePrivate::waitForGPU(QSGD3D12CPUWaitableFence *f) const
+{
+ const UINT64 newValue = f->value.fetchAndAddAcquire(1) + 1;
+ commandQueue->Signal(f->fence.Get(), newValue);
+ if (f->fence->GetCompletedValue() < newValue) {
+ HRESULT hr = f->fence->SetEventOnCompletion(newValue, f->event);
+ if (FAILED(hr)) {
+ qWarning("SetEventOnCompletion failed: 0x%x", hr);
+ return;
+ }
+ WaitForSingleObject(f->event, INFINITE);
+ }
+}
+
+void QSGD3D12EnginePrivate::transitionResource(ID3D12Resource *resource, ID3D12GraphicsCommandList *commandList,
+ D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) const
+{
+ D3D12_RESOURCE_BARRIER barrier;
+ barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+ barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+ barrier.Transition.pResource = resource;
+ barrier.Transition.StateBefore = before;
+ barrier.Transition.StateAfter = after;
+ barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+
+ commandList->ResourceBarrier(1, &barrier);
+}
+
+void QSGD3D12EnginePrivate::resolveMultisampledTarget(ID3D12Resource *msaa,
+ ID3D12Resource *resolve,
+ D3D12_RESOURCE_STATES resolveUsage,
+ ID3D12GraphicsCommandList *commandList) const
+{
+ D3D12_RESOURCE_BARRIER barriers[2];
+ for (int i = 0; i < _countof(barriers); ++i) {
+ barriers[i].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+ barriers[i].Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+ barriers[i].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+ }
+
+ barriers[0].Transition.pResource = msaa;
+ barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
+ barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_SOURCE;
+ barriers[1].Transition.pResource = resolve;
+ barriers[1].Transition.StateBefore = resolveUsage;
+ barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_DEST;
+ commandList->ResourceBarrier(2, barriers);
+
+ commandList->ResolveSubresource(resolve, 0, msaa, 0, RT_COLOR_FORMAT);
+
+ barriers[0].Transition.pResource = msaa;
+ barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_SOURCE;
+ barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
+ barriers[1].Transition.pResource = resolve;
+ barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_DEST;
+ barriers[1].Transition.StateAfter = resolveUsage;
+ commandList->ResourceBarrier(2, barriers);
+}
+
+void QSGD3D12EnginePrivate::uavBarrier(ID3D12Resource *resource, ID3D12GraphicsCommandList *commandList) const
+{
+ D3D12_RESOURCE_BARRIER barrier = {};
+ barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
+ barrier.UAV.pResource = resource;
+
+ commandList->ResourceBarrier(1, &barrier);
+}
+
+ID3D12Resource *QSGD3D12EnginePrivate::createBuffer(int size)
+{
+ ID3D12Resource *buf;
+
+ D3D12_HEAP_PROPERTIES uploadHeapProp = {};
+ uploadHeapProp.Type = D3D12_HEAP_TYPE_UPLOAD;
+
+ D3D12_RESOURCE_DESC bufDesc = {};
+ bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+ bufDesc.Width = size;
+ bufDesc.Height = 1;
+ bufDesc.DepthOrArraySize = 1;
+ bufDesc.MipLevels = 1;
+ bufDesc.Format = DXGI_FORMAT_UNKNOWN;
+ bufDesc.SampleDesc.Count = 1;
+ bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+
+ HRESULT hr = device->CreateCommittedResource(&uploadHeapProp, D3D12_HEAP_FLAG_NONE, &bufDesc,
+ D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&buf));
+ if (FAILED(hr))
+ qWarning("Failed to create buffer resource: 0x%x", hr);
+
+ return buf;
+}
+
+void QSGD3D12EnginePrivate::ensureBuffer(Buffer *buf)
+{
+ Buffer::InFlightData &bfd(buf->d[currentPFrameIndex]);
+ // Only enlarge, never shrink
+ const bool newBufferNeeded = bfd.buffer ? (buf->cpuDataRef.size > bfd.resourceSize) : true;
+ if (newBufferNeeded) {
+ // Round it up and overallocate a little bit so that a subsequent
+ // buffer contents rebuild with a slightly larger total size does
+ // not lead to creating a new buffer.
+ const quint32 sz = alignedSize(buf->cpuDataRef.size, 4096);
+ if (Q_UNLIKELY(debug_buffer()))
+ qDebug("new buffer[pf=%d] of size %d (actual data size %d)", currentPFrameIndex, sz, buf->cpuDataRef.size);
+ bfd.buffer.Attach(createBuffer(sz));
+ bfd.resourceSize = sz;
+ }
+ // Cache the actual data size in the per-in-flight-frame data as well.
+ bfd.dataSize = buf->cpuDataRef.size;
+}
+
+void QSGD3D12EnginePrivate::updateBuffer(Buffer *buf)
+{
+ if (buf->cpuDataRef.dirty.isEmpty())
+ return;
+
+ Buffer::InFlightData &bfd(buf->d[currentPFrameIndex]);
+ quint8 *p = nullptr;
+ const D3D12_RANGE readRange = { 0, 0 };
+ if (FAILED(bfd.buffer->Map(0, &readRange, reinterpret_cast<void **>(&p)))) {
+ qWarning("Map failed for buffer of size %d", buf->cpuDataRef.size);
+ return;
+ }
+ for (const auto &r : qAsConst(buf->cpuDataRef.dirty)) {
+ if (Q_UNLIKELY(debug_buffer()))
+ qDebug("%p o %d s %d", buf, r.first, r.second);
+ memcpy(p + r.first, buf->cpuDataRef.p + r.first, r.second);
+ }
+ bfd.buffer->Unmap(0, nullptr);
+ buf->cpuDataRef.dirty.clear();
+}
+
+void QSGD3D12EnginePrivate::ensureDevice()
+{
+ if (!initialized && window)
+ initialize(window, windowSize, windowDpr, windowSamples, windowAlpha);
+}
+
+void QSGD3D12EnginePrivate::beginFrame()
+{
+ if (inFrame && !activeLayers)
+ qFatal("beginFrame called again without an endFrame, frame index was %d", frameIndex);
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug() << "***** begin frame, logical" << frameIndex << "present" << presentFrameIndex << "layer" << activeLayers;
+
+ if (inFrame && activeLayers) {
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("frame %d already in progress", frameIndex);
+ if (!currentLayerDepth) {
+ // There are layers and the real frame preparation starts now. Prepare for present.
+ beginFrameDraw();
+ }
+ return;
+ }
+
+ inFrame = true;
+
+ // The device may have been lost. This is the point to attempt to start
+ // again from scratch. Except when it is not. Operations that can happen
+ // out of frame (e.g. textures, render targets) may trigger reinit earlier
+ // than beginFrame.
+ ensureDevice();
+
+ // Wait for a buffer to be available for Present, if the waitable event is in use.
+ if (waitableSwapChainMaxLatency)
+ WaitForSingleObject(swapEvent, INFINITE);
+
+ // Block if needed. With 2 frames in flight frame N waits for frame N - 2, but not N - 1, to finish.
+ currentPFrameIndex = frameIndex % frameInFlightCount;
+ if (frameIndex >= frameInFlightCount) {
+ ID3D12Fence *fence = frameFence[currentPFrameIndex]->fence.Get();
+ HANDLE event = frameFence[currentPFrameIndex]->event;
+ // Frame fence values start from 1, hence the +1.
+ const quint64 inFlightFenceValue = frameIndex - frameInFlightCount + 1;
+ if (fence->GetCompletedValue() < inFlightFenceValue) {
+ fence->SetEventOnCompletion(inFlightFenceValue, event);
+ WaitForSingleObject(event, INFINITE);
+ }
+ frameCommandAllocator[currentPFrameIndex]->Reset();
+ }
+
+ PersistentFrameData &pfd(pframeData[currentPFrameIndex]);
+ pfd.cbvSrvUavNextFreeDescriptorIndex = 0;
+
+ for (Buffer &b : buffers) {
+ if (b.entryInUse())
+ b.d[currentPFrameIndex].dirty.clear();
+ }
+
+ if (frameIndex >= frameInFlightCount - 1) {
+ // Now sync the buffer changes from the previous, potentially still in
+ // flight, frames. This is done by taking the ranges dirtied in those
+ // frames and adding them to the global CPU-side buffer's dirty list,
+ // as if this frame changed those ranges. (however, dirty ranges
+ // inherited this way are not added to this frame's persistent
+ // per-frame dirty list because the next frame after this one should
+ // inherit this frame's genuine changes only, the rest will come from
+ // the earlier ones)
+ for (int delta = frameInFlightCount - 1; delta >= 1; --delta) {
+ const int prevPFrameIndex = (frameIndex - delta) % frameInFlightCount;
+ PersistentFrameData &prevFrameData(pframeData[prevPFrameIndex]);
+ for (uint id : qAsConst(prevFrameData.buffersUsedInFrame)) {
+ Buffer &b(buffers[id - 1]);
+ if (b.d[currentPFrameIndex].buffer && b.d[currentPFrameIndex].dataSize == b.cpuDataRef.size) {
+ if (Q_UNLIKELY(debug_buffer()))
+ qDebug() << "frame" << frameIndex << "takes dirty" << b.d[prevPFrameIndex].dirty
+ << "from frame" << frameIndex - delta << "for buffer" << id;
+ for (const auto &range : qAsConst(b.d[prevPFrameIndex].dirty))
+ addDirtyRange(&b.cpuDataRef.dirty, range.first, range.second, b.cpuDataRef.size);
+ } else {
+ if (Q_UNLIKELY(debug_buffer()))
+ qDebug() << "frame" << frameIndex << "makes all dirty from frame" << frameIndex - delta
+ << "for buffer" << id;
+ addDirtyRange(&b.cpuDataRef.dirty, 0, b.cpuDataRef.size, b.cpuDataRef.size);
+ }
+ }
+ }
+ }
+
+ if (frameIndex >= frameInFlightCount) {
+ // Do some texture upload bookkeeping.
+ const quint64 finishedFrameIndex = frameIndex - frameInFlightCount; // we know since we just blocked for this
+ // pfd conveniently refers to the same slot that was used by that frame
+ if (!pfd.pendingTextureUploads.isEmpty()) {
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("Removing texture upload data for frame %d", finishedFrameIndex);
+ for (uint id : qAsConst(pfd.pendingTextureUploads)) {
+ const int idx = id - 1;
+ Texture &t(textures[idx]);
+ // fenceValue is 0 when the previous frame cleared it, skip in
+ // this case. Skip also when fenceValue > the value it was when
+ // adding the last GPU wait - this is the case when more
+ // uploads were queued for the same texture in the meantime.
+ if (t.fenceValue && t.fenceValue == t.lastWaitFenceValue) {
+ t.fenceValue = 0;
+ t.lastWaitFenceValue = 0;
+ t.stagingBuffers.clear();
+ t.stagingHeaps.clear();
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("Cleaned staging data for texture %u", id);
+ }
+ }
+ pfd.pendingTextureUploads.clear();
+ if (!pfd.pendingTextureMipMap.isEmpty()) {
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug() << "cleaning mipmap generation data for " << pfd.pendingTextureMipMap;
+ // no special cleanup is needed as mipmap generation uses the frame's resources
+ pfd.pendingTextureMipMap.clear();
+ }
+ bool hasPending = false;
+ for (int delta = 1; delta < frameInFlightCount; ++delta) {
+ const PersistentFrameData &prevFrameData(pframeData[(frameIndex - delta) % frameInFlightCount]);
+ if (!prevFrameData.pendingTextureUploads.isEmpty()) {
+ hasPending = true;
+ break;
+ }
+ }
+ if (!hasPending) {
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("no more pending textures");
+ copyCommandAllocator->Reset();
+ }
+ }
+
+ // Do the deferred deletes.
+ if (!pfd.deleteQueue.isEmpty()) {
+ for (PersistentFrameData::DeleteQueueEntry &e : pfd.deleteQueue) {
+ e.res = nullptr;
+ e.descHeap = nullptr;
+ if (e.cpuDescriptorPtr) {
+ D3D12_CPU_DESCRIPTOR_HANDLE h = { e.cpuDescriptorPtr };
+ cpuDescHeapManager.release(h, e.descHeapType);
+ }
+ }
+ pfd.deleteQueue.clear();
+ }
+ // Deferred deletes issued outside a begin-endFrame go to the next
+ // frame's out-of-frame delete queue as these cannot be executed in the
+ // next beginFrame, only in next + frameInFlightCount. Move to the
+ // normal queue if this is the next beginFrame.
+ if (!pfd.outOfFrameDeleteQueue.isEmpty()) {
+ pfd.deleteQueue = pfd.outOfFrameDeleteQueue;
+ pfd.outOfFrameDeleteQueue.clear();
+ }
+
+ // Mark released texture, buffer, etc. slots free.
+ if (!pfd.pendingReleases.isEmpty()) {
+ for (const auto &pr : qAsConst(pfd.pendingReleases)) {
+ Q_ASSERT(pr.id);
+ if (pr.type == PersistentFrameData::PendingRelease::TypeTexture) {
+ Texture &t(textures[pr.id - 1]);
+ Q_ASSERT(t.entryInUse());
+ t.flags &= ~RenderTarget::EntryInUse; // createTexture() can now reuse this entry
+ t.texture = nullptr;
+ } else if (pr.type == PersistentFrameData::PendingRelease::TypeBuffer) {
+ Buffer &b(buffers[pr.id - 1]);
+ Q_ASSERT(b.entryInUse());
+ b.flags &= ~Buffer::EntryInUse;
+ for (int i = 0; i < frameInFlightCount; ++i)
+ b.d[i].buffer = nullptr;
+ } else {
+ qFatal("Corrupt pending release list, type %d", pr.type);
+ }
+ }
+ pfd.pendingReleases.clear();
+ }
+ if (!pfd.outOfFramePendingReleases.isEmpty()) {
+ pfd.pendingReleases = pfd.outOfFramePendingReleases;
+ pfd.outOfFramePendingReleases.clear();
+ }
+ }
+
+ pfd.buffersUsedInFrame.clear();
+
+ beginDrawCalls();
+
+ // Prepare for present if this is a frame without layers.
+ if (!activeLayers)
+ beginFrameDraw();
+}
+
+void QSGD3D12EnginePrivate::beginDrawCalls()
+{
+ frameCommandList->Reset(frameCommandAllocator[frameIndex % frameInFlightCount].Get(), nullptr);
+ commandList = frameCommandList.Get();
+ invalidateCachedFrameState();
+}
+
+void QSGD3D12EnginePrivate::invalidateCachedFrameState()
+{
+ tframeData.drawingMode = QSGGeometry::DrawingMode(-1);
+ tframeData.currentIndexBuffer = 0;
+ tframeData.activeTextureCount = 0;
+ tframeData.drawCount = 0;
+ tframeData.lastPso = nullptr;
+ tframeData.lastRootSig = nullptr;
+ tframeData.descHeapSet = false;
+}
+
+void QSGD3D12EnginePrivate::restoreFrameState(bool minimal)
+{
+ queueSetRenderTarget(currentRenderTarget);
+ if (!minimal) {
+ queueViewport(tframeData.viewport);
+ queueScissor(tframeData.scissor);
+ queueSetBlendFactor(tframeData.blendFactor);
+ queueSetStencilRef(tframeData.stencilRef);
+ }
+ finalizePipeline(tframeData.pipelineState);
+}
+
+void QSGD3D12EnginePrivate::beginFrameDraw()
+{
+ if (windowSamples == 1)
+ transitionResource(defaultRT[presentFrameIndex % swapChainBufferCount].Get(), commandList,
+ D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
+}
+
+void QSGD3D12EnginePrivate::endFrame()
+{
+ if (!inFrame)
+ qFatal("endFrame called without beginFrame, frame index %d", frameIndex);
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("***** end frame");
+
+ endDrawCalls(true);
+
+ commandQueue->Signal(frameFence[frameIndex % frameInFlightCount]->fence.Get(), frameIndex + 1);
+ ++frameIndex;
+
+ inFrame = false;
+}
+
+void QSGD3D12EnginePrivate::endDrawCalls(bool lastInFrame)
+{
+ PersistentFrameData &pfd(pframeData[currentPFrameIndex]);
+
+ // Now is the time to sync all the changed areas in the buffers.
+ if (Q_UNLIKELY(debug_buffer()))
+ qDebug() << "buffers used in drawcall set" << pfd.buffersUsedInDrawCallSet;
+ for (uint id : qAsConst(pfd.buffersUsedInDrawCallSet))
+ updateBuffer(&buffers[id - 1]);
+
+ pfd.buffersUsedInFrame += pfd.buffersUsedInDrawCallSet;
+ pfd.buffersUsedInDrawCallSet.clear();
+
+ // Add a wait on the 3D queue for the relevant texture uploads on the copy queue.
+ if (!pfd.pendingTextureUploads.isEmpty()) {
+ quint64 topFenceValue = 0;
+ for (uint id : qAsConst(pfd.pendingTextureUploads)) {
+ const int idx = id - 1;
+ Texture &t(textures[idx]);
+ Q_ASSERT(t.fenceValue);
+ // skip if already added a Wait in the previous frame
+ if (t.lastWaitFenceValue == t.fenceValue)
+ continue;
+ t.lastWaitFenceValue = t.fenceValue;
+ if (t.fenceValue > topFenceValue)
+ topFenceValue = t.fenceValue;
+ if (t.mipmap())
+ pfd.pendingTextureMipMap.insert(id);
+ }
+ if (topFenceValue) {
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("added wait for texture fence %llu", topFenceValue);
+ commandQueue->Wait(textureUploadFence.Get(), topFenceValue);
+ // Generate mipmaps after the wait, when necessary.
+ if (!pfd.pendingTextureMipMap.isEmpty()) {
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug() << "starting mipmap generation for" << pfd.pendingTextureMipMap;
+ for (uint id : qAsConst(pfd.pendingTextureMipMap))
+ mipmapper.queueGenerate(textures[id - 1]);
+ }
+ }
+ }
+
+ if (lastInFrame) {
+ // Resolve and transition the backbuffer for present, if needed.
+ const int idx = presentFrameIndex % swapChainBufferCount;
+ if (windowSamples == 1) {
+ transitionResource(defaultRT[idx].Get(), commandList,
+ D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
+ } else {
+ if (Q_UNLIKELY(debug_render())) {
+ const D3D12_RESOURCE_DESC desc = defaultRT[idx]->GetDesc();
+ qDebug("added resolve for multisampled render target (count %d, quality %d)",
+ desc.SampleDesc.Count, desc.SampleDesc.Quality);
+ }
+ resolveMultisampledTarget(defaultRT[idx].Get(), backBufferRT[idx].Get(),
+ D3D12_RESOURCE_STATE_PRESENT, commandList);
+ }
+
+ if (activeLayers) {
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("this frame had %d layers", activeLayers);
+ activeLayers = 0;
+ }
+ }
+
+ // Go!
+ HRESULT hr = frameCommandList->Close();
+ if (FAILED(hr)) {
+ qWarning("Failed to close command list: 0x%x", hr);
+ if (hr == E_INVALIDARG)
+ qWarning("Invalid arguments. Some of the commands in the list is invalid in some way.");
+ }
+
+ ID3D12CommandList *commandLists[] = { frameCommandList.Get() };
+ commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
+
+ commandList = nullptr;
+}
+
+void QSGD3D12EnginePrivate::beginLayer()
+{
+ if (inFrame && !activeLayers)
+ qFatal("Layer rendering cannot be started while a frame is active");
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("===== beginLayer active %d depth %d (inFrame=%d)", activeLayers, currentLayerDepth, inFrame);
+
+ ++activeLayers;
+ ++currentLayerDepth;
+
+ // Do an early beginFrame. With multiple layers this results in
+ // beginLayer - beginFrame - endLayer - beginLayer - beginFrame - endLayer - ... - (*) beginFrame - endFrame
+ // where (*) denotes the start of the preparation of the actual, non-layer frame.
+
+ if (activeLayers == 1)
+ beginFrame();
+}
+
+void QSGD3D12EnginePrivate::endLayer()
+{
+ if (!inFrame || !activeLayers || !currentLayerDepth)
+ qFatal("Mismatched endLayer");
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("===== endLayer active %d depth %d", activeLayers, currentLayerDepth);
+
+ --currentLayerDepth;
+
+ // Do not touch activeLayers. It remains valid until endFrame.
+}
+
+// Root signature:
+// [0] CBV - always present
+// [1] table with one SRV per texture (must be a table since root descriptor SRVs cannot be textures) - optional
+// one static sampler per texture - optional
+//
+// SRVs can be created freely via QSGD3D12CPUDescriptorHeapManager and stored
+// in QSGD3D12TextureView. The engine will copy them onto a dedicated,
+// shader-visible CBV-SRV-UAV heap in the correct order.
+
+void QSGD3D12EnginePrivate::finalizePipeline(const QSGD3D12PipelineState &pipelineState)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ tframeData.pipelineState = pipelineState;
+
+ RootSigCacheEntry *cachedRootSig = rootSigCache[pipelineState.shaders.rootSig];
+ if (!cachedRootSig) {
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("NEW ROOTSIG");
+
+ cachedRootSig = new RootSigCacheEntry;
+
+ D3D12_ROOT_PARAMETER rootParams[4];
+ int rootParamCount = 0;
+
+ rootParams[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
+ rootParams[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+ rootParams[0].Descriptor.ShaderRegister = 0; // b0
+ rootParams[0].Descriptor.RegisterSpace = 0;
+ ++rootParamCount;
+
+ D3D12_DESCRIPTOR_RANGE tvDescRange;
+ if (pipelineState.shaders.rootSig.textureViewCount > 0) {
+ rootParams[rootParamCount].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+ rootParams[rootParamCount].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
+ rootParams[rootParamCount].DescriptorTable.NumDescriptorRanges = 1;
+ tvDescRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
+ tvDescRange.NumDescriptors = pipelineState.shaders.rootSig.textureViewCount;
+ tvDescRange.BaseShaderRegister = 0; // t0, t1, ...
+ tvDescRange.RegisterSpace = 0;
+ tvDescRange.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
+ rootParams[rootParamCount].DescriptorTable.pDescriptorRanges = &tvDescRange;
+ ++rootParamCount;
+ }
+
+ Q_ASSERT(rootParamCount <= _countof(rootParams));
+ D3D12_ROOT_SIGNATURE_DESC desc;
+ desc.NumParameters = rootParamCount;
+ desc.pParameters = rootParams;
+ // Mixing up samplers and resource views in QSGD3D12TextureView means
+ // that the number of static samplers has to match the number of
+ // textures. This is not really ideal in general but works for Quick's use cases.
+ // The shaders can still choose to declare and use fewer samplers, if they want to.
+ desc.NumStaticSamplers = pipelineState.shaders.rootSig.textureViewCount;
+ D3D12_STATIC_SAMPLER_DESC staticSamplers[8];
+ int sdIdx = 0;
+ Q_ASSERT(pipelineState.shaders.rootSig.textureViewCount <= _countof(staticSamplers));
+ for (int i = 0; i < pipelineState.shaders.rootSig.textureViewCount; ++i) {
+ const QSGD3D12TextureView &tv(pipelineState.shaders.rootSig.textureViews[i]);
+ D3D12_STATIC_SAMPLER_DESC sd = {};
+ sd.Filter = D3D12_FILTER(tv.filter);
+ sd.AddressU = D3D12_TEXTURE_ADDRESS_MODE(tv.addressModeHoriz);
+ sd.AddressV = D3D12_TEXTURE_ADDRESS_MODE(tv.addressModeVert);
+ sd.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+ sd.MinLOD = 0.0f;
+ sd.MaxLOD = D3D12_FLOAT32_MAX;
+ sd.ShaderRegister = sdIdx; // t0, t1, ...
+ sd.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
+ staticSamplers[sdIdx++] = sd;
+ }
+ desc.pStaticSamplers = staticSamplers;
+ desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
+
+ ComPtr<ID3DBlob> signature;
+ ComPtr<ID3DBlob> error;
+ if (FAILED(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error))) {
+ QByteArray msg(static_cast<const char *>(error->GetBufferPointer()), error->GetBufferSize());
+ qWarning("Failed to serialize root signature: %s", qPrintable(msg));
+ return;
+ }
+ if (FAILED(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(),
+ IID_PPV_ARGS(&cachedRootSig->rootSig)))) {
+ qWarning("Failed to create root signature");
+ return;
+ }
+
+ rootSigCache.insert(pipelineState.shaders.rootSig, cachedRootSig);
+ }
+
+ PSOCacheEntry *cachedPso = psoCache[pipelineState];
+ if (!cachedPso) {
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("NEW PSO");
+
+ cachedPso = new PSOCacheEntry;
+
+ D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
+
+ D3D12_INPUT_ELEMENT_DESC inputElements[QSGD3D12_MAX_INPUT_ELEMENTS];
+ int ieIdx = 0;
+ for (int i = 0; i < pipelineState.inputElementCount; ++i) {
+ const QSGD3D12InputElement &ie(pipelineState.inputElements[i]);
+ D3D12_INPUT_ELEMENT_DESC ieDesc = {};
+ ieDesc.SemanticName = ie.semanticName;
+ ieDesc.SemanticIndex = ie.semanticIndex;
+ ieDesc.Format = DXGI_FORMAT(ie.format);
+ ieDesc.InputSlot = ie.slot;
+ ieDesc.AlignedByteOffset = ie.offset;
+ ieDesc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("input [%d]: %s %d 0x%x %d", ieIdx, ie.semanticName, ie.offset, ie.format, ie.slot);
+ inputElements[ieIdx++] = ieDesc;
+ }
+
+ psoDesc.InputLayout = { inputElements, UINT(ieIdx) };
+
+ psoDesc.pRootSignature = cachedRootSig->rootSig.Get();
+
+ D3D12_SHADER_BYTECODE vshader;
+ vshader.pShaderBytecode = pipelineState.shaders.vs;
+ vshader.BytecodeLength = pipelineState.shaders.vsSize;
+ D3D12_SHADER_BYTECODE pshader;
+ pshader.pShaderBytecode = pipelineState.shaders.ps;
+ pshader.BytecodeLength = pipelineState.shaders.psSize;
+
+ psoDesc.VS = vshader;
+ psoDesc.PS = pshader;
+
+ D3D12_RASTERIZER_DESC rastDesc = {};
+ rastDesc.FillMode = D3D12_FILL_MODE_SOLID;
+ rastDesc.CullMode = D3D12_CULL_MODE(pipelineState.cullMode);
+ rastDesc.FrontCounterClockwise = pipelineState.frontCCW;
+ rastDesc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
+ rastDesc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
+ rastDesc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
+ rastDesc.DepthClipEnable = TRUE;
+
+ psoDesc.RasterizerState = rastDesc;
+
+ D3D12_BLEND_DESC blendDesc = {};
+ if (pipelineState.blend == QSGD3D12PipelineState::BlendNone) {
+ D3D12_RENDER_TARGET_BLEND_DESC noBlendDesc = {};
+ noBlendDesc.RenderTargetWriteMask = pipelineState.colorWrite ? D3D12_COLOR_WRITE_ENABLE_ALL : 0;
+ blendDesc.RenderTarget[0] = noBlendDesc;
+ } else if (pipelineState.blend == QSGD3D12PipelineState::BlendPremul) {
+ const D3D12_RENDER_TARGET_BLEND_DESC premulBlendDesc = {
+ TRUE, FALSE,
+ D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD,
+ D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD,
+ D3D12_LOGIC_OP_NOOP,
+ UINT8(pipelineState.colorWrite ? D3D12_COLOR_WRITE_ENABLE_ALL : 0)
+ };
+ blendDesc.RenderTarget[0] = premulBlendDesc;
+ } else if (pipelineState.blend == QSGD3D12PipelineState::BlendColor) {
+ const D3D12_RENDER_TARGET_BLEND_DESC colorBlendDesc = {
+ TRUE, FALSE,
+ D3D12_BLEND_BLEND_FACTOR, D3D12_BLEND_INV_SRC_COLOR, D3D12_BLEND_OP_ADD,
+ D3D12_BLEND_BLEND_FACTOR, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD,
+ D3D12_LOGIC_OP_NOOP,
+ UINT8(pipelineState.colorWrite ? D3D12_COLOR_WRITE_ENABLE_ALL : 0)
+ };
+ blendDesc.RenderTarget[0] = colorBlendDesc;
+ }
+ psoDesc.BlendState = blendDesc;
+
+ psoDesc.DepthStencilState.DepthEnable = pipelineState.depthEnable;
+ psoDesc.DepthStencilState.DepthWriteMask = pipelineState.depthWrite ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
+ psoDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC(pipelineState.depthFunc);
+
+ psoDesc.DepthStencilState.StencilEnable = pipelineState.stencilEnable;
+ psoDesc.DepthStencilState.StencilReadMask = psoDesc.DepthStencilState.StencilWriteMask = 0xFF;
+ D3D12_DEPTH_STENCILOP_DESC stencilOpDesc = {
+ D3D12_STENCIL_OP(pipelineState.stencilFailOp),
+ D3D12_STENCIL_OP(pipelineState.stencilDepthFailOp),
+ D3D12_STENCIL_OP(pipelineState.stencilPassOp),
+ D3D12_COMPARISON_FUNC(pipelineState.stencilFunc)
+ };
+ psoDesc.DepthStencilState.FrontFace = psoDesc.DepthStencilState.BackFace = stencilOpDesc;
+
+ psoDesc.SampleMask = UINT_MAX;
+ psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE(pipelineState.topologyType);
+ psoDesc.NumRenderTargets = 1;
+ psoDesc.RTVFormats[0] = RT_COLOR_FORMAT;
+ psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ psoDesc.SampleDesc = defaultRT[0]->GetDesc().SampleDesc;
+
+ HRESULT hr = device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&cachedPso->pso));
+ if (FAILED(hr)) {
+ qWarning("Failed to create graphics pipeline state");
+ return;
+ }
+
+ psoCache.insert(pipelineState, cachedPso);
+ }
+
+ if (cachedPso->pso.Get() != tframeData.lastPso) {
+ tframeData.lastPso = cachedPso->pso.Get();
+ commandList->SetPipelineState(tframeData.lastPso);
+ }
+
+ if (cachedRootSig->rootSig.Get() != tframeData.lastRootSig) {
+ tframeData.lastRootSig = cachedRootSig->rootSig.Get();
+ commandList->SetGraphicsRootSignature(tframeData.lastRootSig);
+ }
+
+ if (pipelineState.shaders.rootSig.textureViewCount > 0)
+ setDescriptorHeaps();
+}
+
+void QSGD3D12EnginePrivate::setDescriptorHeaps(bool force)
+{
+ if (force || !tframeData.descHeapSet) {
+ tframeData.descHeapSet = true;
+ ID3D12DescriptorHeap *heaps[] = { pframeData[currentPFrameIndex].gpuCbvSrvUavHeap.Get() };
+ commandList->SetDescriptorHeaps(_countof(heaps), heaps);
+ }
+}
+
+void QSGD3D12EnginePrivate::queueViewport(const QRect &rect)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ tframeData.viewport = rect;
+ const D3D12_VIEWPORT viewport = { float(rect.x()), float(rect.y()), float(rect.width()), float(rect.height()), 0, 1 };
+ commandList->RSSetViewports(1, &viewport);
+}
+
+void QSGD3D12EnginePrivate::queueScissor(const QRect &rect)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ tframeData.scissor = rect;
+ const D3D12_RECT scissorRect = { rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + rect.height() };
+ commandList->RSSetScissorRects(1, &scissorRect);
+}
+
+void QSGD3D12EnginePrivate::queueSetRenderTarget(uint id)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle;
+ D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle;
+
+ if (!id) {
+ rtvHandle = defaultRTV[presentFrameIndex % swapChainBufferCount];
+ dsvHandle = defaultDSV;
+ } else {
+ const int idx = id - 1;
+ Q_ASSERT(idx < renderTargets.count() && renderTargets[idx].entryInUse());
+ RenderTarget &rt(renderTargets[idx]);
+ rtvHandle = rt.rtv;
+ dsvHandle = rt.dsv;
+ if (!(rt.flags & RenderTarget::NeedsReadBarrier)) {
+ rt.flags |= RenderTarget::NeedsReadBarrier;
+ if (!(rt.flags & RenderTarget::Multisample))
+ transitionResource(rt.color.Get(), commandList, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
+ D3D12_RESOURCE_STATE_RENDER_TARGET);
+ }
+ }
+
+ commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);
+
+ currentRenderTarget = id;
+}
+
+void QSGD3D12EnginePrivate::queueClearRenderTarget(const QColor &color)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ const float clearColor[] = { float(color.redF()), float(color.blueF()), float(color.greenF()), float(color.alphaF()) };
+ D3D12_CPU_DESCRIPTOR_HANDLE rtv = !currentRenderTarget
+ ? defaultRTV[presentFrameIndex % swapChainBufferCount]
+ : renderTargets[currentRenderTarget - 1].rtv;
+ commandList->ClearRenderTargetView(rtv, clearColor, 0, nullptr);
+}
+
+void QSGD3D12EnginePrivate::queueClearDepthStencil(float depthValue, quint8 stencilValue, QSGD3D12Engine::ClearFlags which)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ D3D12_CPU_DESCRIPTOR_HANDLE dsv = !currentRenderTarget
+ ? defaultDSV
+ : renderTargets[currentRenderTarget - 1].dsv;
+ commandList->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAGS(int(which)), depthValue, stencilValue, 0, nullptr);
+}
+
+void QSGD3D12EnginePrivate::queueSetBlendFactor(const QVector4D &factor)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ tframeData.blendFactor = factor;
+ const float f[4] = { factor.x(), factor.y(), factor.z(), factor.w() };
+ commandList->OMSetBlendFactor(f);
+}
+
+void QSGD3D12EnginePrivate::queueSetStencilRef(quint32 ref)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ tframeData.stencilRef = ref;
+ commandList->OMSetStencilRef(ref);
+}
+
+void QSGD3D12EnginePrivate::queueDraw(const QSGD3D12Engine::DrawParams &params)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ const bool skip = tframeData.scissor.isEmpty();
+
+ PersistentFrameData &pfd(pframeData[currentPFrameIndex]);
+
+ pfd.buffersUsedInDrawCallSet.insert(params.vertexBuf);
+ const int vertexBufIdx = params.vertexBuf - 1;
+ Q_ASSERT(params.vertexBuf && vertexBufIdx < buffers.count() && buffers[vertexBufIdx].entryInUse());
+ pfd.buffersUsedInDrawCallSet.insert(params.constantBuf);
+ const int constantBufIdx = params.constantBuf - 1;
+ Q_ASSERT(params.constantBuf && constantBufIdx < buffers.count() && buffers[constantBufIdx].entryInUse());
+ int indexBufIdx = -1;
+ if (params.indexBuf) {
+ pfd.buffersUsedInDrawCallSet.insert(params.indexBuf);
+ indexBufIdx = params.indexBuf - 1;
+ Q_ASSERT(indexBufIdx < buffers.count() && buffers[indexBufIdx].entryInUse());
+ }
+
+ // Ensure buffers are created but do not copy the data here, leave that to endDrawCalls().
+ ensureBuffer(&buffers[vertexBufIdx]);
+ ensureBuffer(&buffers[constantBufIdx]);
+ if (indexBufIdx >= 0)
+ ensureBuffer(&buffers[indexBufIdx]);
+
+ // Set the CBV.
+ if (!skip && params.cboOffset >= 0) {
+ ID3D12Resource *cbuf = buffers[constantBufIdx].d[currentPFrameIndex].buffer.Get();
+ if (cbuf)
+ commandList->SetGraphicsRootConstantBufferView(0, cbuf->GetGPUVirtualAddress() + params.cboOffset);
+ }
+
+ // Set up vertex and index buffers.
+ ID3D12Resource *vbuf = buffers[vertexBufIdx].d[currentPFrameIndex].buffer.Get();
+ ID3D12Resource *ibuf = indexBufIdx >= 0 && params.startIndexIndex >= 0
+ ? buffers[indexBufIdx].d[currentPFrameIndex].buffer.Get() : nullptr;
+
+ if (!skip && params.mode != tframeData.drawingMode) {
+ D3D_PRIMITIVE_TOPOLOGY topology;
+ switch (params.mode) {
+ case QSGGeometry::DrawPoints:
+ topology = D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
+ break;
+ case QSGGeometry::DrawLines:
+ topology = D3D_PRIMITIVE_TOPOLOGY_LINELIST;
+ break;
+ case QSGGeometry::DrawLineStrip:
+ topology = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
+ break;
+ case QSGGeometry::DrawTriangles:
+ topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ break;
+ case QSGGeometry::DrawTriangleStrip:
+ topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+ break;
+ default:
+ qFatal("Unsupported drawing mode 0x%x", params.mode);
+ break;
+ }
+ commandList->IASetPrimitiveTopology(topology);
+ tframeData.drawingMode = params.mode;
+ }
+
+ if (!skip) {
+ D3D12_VERTEX_BUFFER_VIEW vbv;
+ vbv.BufferLocation = vbuf->GetGPUVirtualAddress() + params.vboOffset;
+ vbv.SizeInBytes = params.vboSize;
+ vbv.StrideInBytes = params.vboStride;
+
+ // must be set after the topology
+ commandList->IASetVertexBuffers(0, 1, &vbv);
+ }
+
+ if (!skip && params.startIndexIndex >= 0 && ibuf && tframeData.currentIndexBuffer != params.indexBuf) {
+ tframeData.currentIndexBuffer = params.indexBuf;
+ D3D12_INDEX_BUFFER_VIEW ibv;
+ ibv.BufferLocation = ibuf->GetGPUVirtualAddress();
+ ibv.SizeInBytes = buffers[indexBufIdx].cpuDataRef.size;
+ ibv.Format = DXGI_FORMAT(params.indexFormat);
+ commandList->IASetIndexBuffer(&ibv);
+ }
+
+ // Copy the SRVs to a drawcall-dedicated area of the shader-visible descriptor heap.
+ Q_ASSERT(tframeData.activeTextureCount == tframeData.pipelineState.shaders.rootSig.textureViewCount);
+ if (tframeData.activeTextureCount > 0) {
+ if (!skip) {
+ ensureGPUDescriptorHeap(tframeData.activeTextureCount);
+ const uint stride = cpuDescHeapManager.handleSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ D3D12_CPU_DESCRIPTOR_HANDLE dst = pfd.gpuCbvSrvUavHeap->GetCPUDescriptorHandleForHeapStart();
+ dst.ptr += pfd.cbvSrvUavNextFreeDescriptorIndex * stride;
+ for (int i = 0; i < tframeData.activeTextureCount; ++i) {
+ const TransientFrameData::ActiveTexture &t(tframeData.activeTextures[i]);
+ Q_ASSERT(t.id);
+ const int idx = t.id - 1;
+ const bool isTex = t.type == TransientFrameData::ActiveTexture::TypeTexture;
+ device->CopyDescriptorsSimple(1, dst, isTex ? textures[idx].srv : renderTargets[idx].srv,
+ D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ dst.ptr += stride;
+ }
+
+ D3D12_GPU_DESCRIPTOR_HANDLE gpuAddr = pfd.gpuCbvSrvUavHeap->GetGPUDescriptorHandleForHeapStart();
+ gpuAddr.ptr += pfd.cbvSrvUavNextFreeDescriptorIndex * stride;
+ commandList->SetGraphicsRootDescriptorTable(1, gpuAddr);
+
+ pfd.cbvSrvUavNextFreeDescriptorIndex += tframeData.activeTextureCount;
+ }
+ tframeData.activeTextureCount = 0;
+ }
+
+ // Add the draw call.
+ if (!skip) {
+ ++tframeData.drawCount;
+ if (params.startIndexIndex >= 0)
+ commandList->DrawIndexedInstanced(params.count, 1, params.startIndexIndex, 0, 0);
+ else
+ commandList->DrawInstanced(params.count, 1, 0, 0);
+ }
+
+ if (tframeData.drawCount == MAX_DRAW_CALLS_PER_LIST) {
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("Limit of %d draw calls reached, executing command list", MAX_DRAW_CALLS_PER_LIST);
+ // submit the command list
+ endDrawCalls();
+ // start a new one
+ beginDrawCalls();
+ // prepare for the upcoming drawcalls
+ restoreFrameState();
+ }
+}
+
+void QSGD3D12EnginePrivate::ensureGPUDescriptorHeap(int cbvSrvUavDescriptorCount)
+{
+ PersistentFrameData &pfd(pframeData[currentPFrameIndex]);
+ int newSize = pfd.gpuCbvSrvUavHeapSize;
+ while (pfd.cbvSrvUavNextFreeDescriptorIndex + cbvSrvUavDescriptorCount > newSize)
+ newSize *= 2;
+ if (newSize != pfd.gpuCbvSrvUavHeapSize) {
+ if (Q_UNLIKELY(debug_descheap()))
+ qDebug("Out of space for SRVs, creating new CBV-SRV-UAV descriptor heap with descriptor count %d", newSize);
+ deferredDelete(pfd.gpuCbvSrvUavHeap);
+ createCbvSrvUavHeap(currentPFrameIndex, newSize);
+ setDescriptorHeaps(true);
+ pfd.cbvSrvUavNextFreeDescriptorIndex = 0;
+ }
+}
+
+void QSGD3D12EnginePrivate::present()
+{
+ if (!initialized)
+ return;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("--- present with vsync ---");
+
+ // This call will not block the CPU unless at least 3 buffers are queued,
+ // unless the waitable frame latency event is enabled. Then the latency of
+ // 3 is changed to whatever value desired, and blocking happens in
+ // beginFrame. If none of these hold, the fence-based wait in beginFrame
+ // throttles. Vsync (interval 1) is always enabled.
+ HRESULT hr = swapChain->Present(1, 0);
+ if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
+ deviceManager()->deviceLossDetected();
+ return;
+ } else if (FAILED(hr)) {
+ qWarning("Present failed: 0x%x", hr);
+ return;
+ }
+
+#ifndef Q_OS_WINRT
+ if (dcompDevice)
+ dcompDevice->Commit();
+#endif
+
+ ++presentFrameIndex;
+}
+
+void QSGD3D12EnginePrivate::waitGPU()
+{
+ if (!initialized)
+ return;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("--- blocking wait for GPU ---");
+
+ waitForGPU(presentFence);
+}
+
+template<class T> uint newId(T *tbl)
+{
+ uint id = 0;
+ for (int i = 0; i < tbl->count(); ++i) {
+ if (!(*tbl)[i].entryInUse()) {
+ id = i + 1;
+ break;
+ }
+ }
+
+ if (!id) {
+ tbl->resize(tbl->size() + 1);
+ id = tbl->count();
+ }
+
+ (*tbl)[id - 1].flags = 0x01; // reset flags and set EntryInUse
+
+ return id;
+}
+
+template<class T> void syncEntryFlags(T *e, int flag, bool b)
+{
+ if (b)
+ e->flags |= flag;
+ else
+ e->flags &= ~flag;
+}
+
+uint QSGD3D12EnginePrivate::genBuffer()
+{
+ return newId(&buffers);
+}
+
+void QSGD3D12EnginePrivate::releaseBuffer(uint id)
+{
+ if (!id || !initialized)
+ return;
+
+ const int idx = id - 1;
+ Q_ASSERT(idx < buffers.count());
+
+ if (Q_UNLIKELY(debug_buffer()))
+ qDebug("releasing buffer %u", id);
+
+ Buffer &b(buffers[idx]);
+ if (!b.entryInUse())
+ return;
+
+ // Do not null out and do not mark the entry reusable yet.
+ // Do that only when the frames potentially in flight have finished for sure.
+
+ for (int i = 0; i < frameInFlightCount; ++i) {
+ if (b.d[i].buffer)
+ deferredDelete(b.d[i].buffer);
+ }
+
+ QSet<PersistentFrameData::PendingRelease> *pendingReleasesSet = inFrame
+ ? &pframeData[currentPFrameIndex].pendingReleases
+ : &pframeData[(currentPFrameIndex + 1) % frameInFlightCount].outOfFramePendingReleases;
+
+ pendingReleasesSet->insert(PersistentFrameData::PendingRelease(PersistentFrameData::PendingRelease::TypeBuffer, id));
+}
+
+void QSGD3D12EnginePrivate::resetBuffer(uint id, const quint8 *data, int size)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ Q_ASSERT(id);
+ const int idx = id - 1;
+ Q_ASSERT(idx < buffers.count() && buffers[idx].entryInUse());
+ Buffer &b(buffers[idx]);
+
+ if (Q_UNLIKELY(debug_buffer()))
+ qDebug("reset buffer %u, size %d", id, size);
+
+ b.cpuDataRef.p = data;
+ b.cpuDataRef.size = size;
+
+ b.cpuDataRef.dirty.clear();
+ b.d[currentPFrameIndex].dirty.clear();
+
+ if (size > 0) {
+ const QPair<int, int> range = qMakePair(0, size);
+ b.cpuDataRef.dirty.append(range);
+ b.d[currentPFrameIndex].dirty.append(range);
+ }
+}
+
+void QSGD3D12EnginePrivate::addDirtyRange(DirtyList *dirty, int offset, int size, int bufferSize)
+{
+ // Bail out when the dirty list already spans the entire buffer.
+ if (!dirty->isEmpty()) {
+ if (dirty->at(0).first == 0 && dirty->at(0).second == bufferSize)
+ return;
+ }
+
+ const QPair<int, int> range = qMakePair(offset, size);
+ if (!dirty->contains(range))
+ dirty->append(range);
+}
+
+void QSGD3D12EnginePrivate::markBufferDirty(uint id, int offset, int size)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ Q_ASSERT(id);
+ const int idx = id - 1;
+ Q_ASSERT(idx < buffers.count() && buffers[idx].entryInUse());
+ Buffer &b(buffers[idx]);
+
+ addDirtyRange(&b.cpuDataRef.dirty, offset, size, b.cpuDataRef.size);
+ addDirtyRange(&b.d[currentPFrameIndex].dirty, offset, size, b.cpuDataRef.size);
+}
+
+uint QSGD3D12EnginePrivate::genTexture()
+{
+ const uint id = newId(&textures);
+ textures[id - 1].fenceValue = 0;
+ return id;
+}
+
+static inline DXGI_FORMAT textureFormat(QImage::Format format, bool wantsAlpha, bool mipmap, bool force32bit,
+ QImage::Format *imageFormat, int *bytesPerPixel)
+{
+ DXGI_FORMAT f = DXGI_FORMAT_R8G8B8A8_UNORM;
+ QImage::Format convFormat = format;
+ int bpp = 4;
+
+ if (!mipmap) {
+ switch (format) {
+ case QImage::Format_Grayscale8:
+ case QImage::Format_Indexed8:
+ case QImage::Format_Alpha8:
+ if (!force32bit) {
+ f = DXGI_FORMAT_R8_UNORM;
+ bpp = 1;
+ } else {
+ convFormat = QImage::Format_RGBA8888;
+ }
+ break;
+ case QImage::Format_RGB32:
+ f = DXGI_FORMAT_B8G8R8A8_UNORM;
+ break;
+ case QImage::Format_ARGB32:
+ f = DXGI_FORMAT_B8G8R8A8_UNORM;
+ convFormat = wantsAlpha ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
+ break;
+ case QImage::Format_ARGB32_Premultiplied:
+ f = DXGI_FORMAT_B8G8R8A8_UNORM;
+ convFormat = wantsAlpha ? format : QImage::Format_RGB32;
+ break;
+ default:
+ convFormat = wantsAlpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBX8888;
+ break;
+ }
+ } else {
+ // Mipmap generation needs unordered access and BGRA is not an option for that. Stick to RGBA.
+ convFormat = wantsAlpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBX8888;
+ }
+
+ if (imageFormat)
+ *imageFormat = convFormat;
+
+ if (bytesPerPixel)
+ *bytesPerPixel = bpp;
+
+ return f;
+}
+
+static inline QImage::Format imageFormatForTexture(DXGI_FORMAT format)
+{
+ QImage::Format f = QImage::Format_Invalid;
+
+ switch (format) {
+ case DXGI_FORMAT_R8G8B8A8_UNORM:
+ f = QImage::Format_RGBA8888_Premultiplied;
+ break;
+ case DXGI_FORMAT_B8G8R8A8_UNORM:
+ f = QImage::Format_ARGB32_Premultiplied;
+ break;
+ case DXGI_FORMAT_R8_UNORM:
+ f = QImage::Format_Grayscale8;
+ break;
+ default:
+ break;
+ }
+
+ return f;
+}
+
+void QSGD3D12EnginePrivate::createTexture(uint id, const QSize &size, QImage::Format format,
+ QSGD3D12Engine::TextureCreateFlags createFlags)
+{
+ ensureDevice();
+
+ Q_ASSERT(id);
+ const int idx = id - 1;
+ Q_ASSERT(idx < textures.count() && textures[idx].entryInUse());
+ Texture &t(textures[idx]);
+
+ syncEntryFlags(&t, Texture::Alpha, createFlags & QSGD3D12Engine::TextureWithAlpha);
+ syncEntryFlags(&t, Texture::MipMap, createFlags & QSGD3D12Engine::TextureWithMipMaps);
+
+ const QSize adjustedSize = !t.mipmap() ? size : QSGD3D12Engine::mipMapAdjustedSourceSize(size);
+
+ D3D12_HEAP_PROPERTIES defaultHeapProp = {};
+ defaultHeapProp.Type = D3D12_HEAP_TYPE_DEFAULT;
+
+ D3D12_RESOURCE_DESC textureDesc = {};
+ textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+ textureDesc.Width = adjustedSize.width();
+ textureDesc.Height = adjustedSize.height();
+ textureDesc.DepthOrArraySize = 1;
+ textureDesc.MipLevels = !t.mipmap() ? 1 : QSGD3D12Engine::mipMapLevels(adjustedSize);
+ textureDesc.Format = textureFormat(format, t.alpha(), t.mipmap(),
+ createFlags.testFlag(QSGD3D12Engine::TextureAlways32Bit),
+ nullptr, nullptr);
+ textureDesc.SampleDesc.Count = 1;
+ textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+ if (t.mipmap())
+ textureDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
+
+ HRESULT hr = device->CreateCommittedResource(&defaultHeapProp, D3D12_HEAP_FLAG_NONE, &textureDesc,
+ D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&t.texture));
+ if (FAILED(hr)) {
+ qWarning("Failed to create texture resource: 0x%x", hr);
+ return;
+ }
+
+ t.srv = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+
+ D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
+ srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
+ srvDesc.Format = textureDesc.Format;
+ srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
+ srvDesc.Texture2D.MipLevels = textureDesc.MipLevels;
+
+ device->CreateShaderResourceView(t.texture.Get(), &srvDesc, t.srv);
+
+ if (t.mipmap()) {
+ // Mipmap generation will need an UAV for each level that needs to be generated.
+ t.mipUAVs.clear();
+ for (int level = 1; level < textureDesc.MipLevels; ++level) {
+ D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
+ uavDesc.Format = textureDesc.Format;
+ uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
+ uavDesc.Texture2D.MipSlice = level;
+ D3D12_CPU_DESCRIPTOR_HANDLE h = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ device->CreateUnorderedAccessView(t.texture.Get(), nullptr, &uavDesc, h);
+ t.mipUAVs.append(h);
+ }
+ }
+
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("created texture %u, size %dx%d, miplevels %d", id, adjustedSize.width(), adjustedSize.height(), textureDesc.MipLevels);
+}
+
+void QSGD3D12EnginePrivate::queueTextureResize(uint id, const QSize &size)
+{
+ Q_ASSERT(id);
+ const int idx = id - 1;
+ Q_ASSERT(idx < textures.count() && textures[idx].entryInUse());
+ Texture &t(textures[idx]);
+
+ if (!t.texture) {
+ qWarning("Cannot resize non-created texture %u", id);
+ return;
+ }
+
+ if (t.mipmap()) {
+ qWarning("Cannot resize mipmapped texture %u", id);
+ return;
+ }
+
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("resizing texture %u, size %dx%d", id, size.width(), size.height());
+
+ D3D12_RESOURCE_DESC textureDesc = t.texture->GetDesc();
+ textureDesc.Width = size.width();
+ textureDesc.Height = size.height();
+
+ D3D12_HEAP_PROPERTIES defaultHeapProp = {};
+ defaultHeapProp.Type = D3D12_HEAP_TYPE_DEFAULT;
+
+ ComPtr<ID3D12Resource> oldTexture = t.texture;
+ deferredDelete(t.texture);
+
+ HRESULT hr = device->CreateCommittedResource(&defaultHeapProp, D3D12_HEAP_FLAG_NONE, &textureDesc,
+ D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&t.texture));
+ if (FAILED(hr)) {
+ qWarning("Failed to create resized texture resource: 0x%x", hr);
+ return;
+ }
+
+ deferredDelete(t.srv, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ t.srv = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+
+ D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
+ srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
+ srvDesc.Format = textureDesc.Format;
+ srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
+ srvDesc.Texture2D.MipLevels = textureDesc.MipLevels;
+
+ device->CreateShaderResourceView(t.texture.Get(), &srvDesc, t.srv);
+
+ D3D12_TEXTURE_COPY_LOCATION dstLoc;
+ dstLoc.pResource = t.texture.Get();
+ dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+ dstLoc.SubresourceIndex = 0;
+
+ D3D12_TEXTURE_COPY_LOCATION srcLoc;
+ srcLoc.pResource = oldTexture.Get();
+ srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+ srcLoc.SubresourceIndex = 0;
+
+ copyCommandList->Reset(copyCommandAllocator.Get(), nullptr);
+
+ copyCommandList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
+
+ copyCommandList->Close();
+ ID3D12CommandList *commandLists[] = { copyCommandList.Get() };
+ copyCommandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
+
+ t.fenceValue = nextTextureUploadFenceValue.fetchAndAddAcquire(1) + 1;
+ copyCommandQueue->Signal(textureUploadFence.Get(), t.fenceValue);
+
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("submitted old content copy for texture %u on the copy queue, fence %llu", id, t.fenceValue);
+}
+
+void QSGD3D12EnginePrivate::queueTextureUpload(uint id, const QVector<QImage> &images, const QVector<QPoint> &dstPos,
+ QSGD3D12Engine::TextureUploadFlags flags)
+{
+ Q_ASSERT(id);
+ Q_ASSERT(images.count() == dstPos.count());
+ if (images.isEmpty())
+ return;
+
+ const int idx = id - 1;
+ Q_ASSERT(idx < textures.count() && textures[idx].entryInUse());
+ Texture &t(textures[idx]);
+ Q_ASSERT(t.texture);
+
+ // When mipmapping is not in use, image can be smaller than the size passed
+ // to createTexture() and dstPos can specify a non-zero destination position.
+
+ if (t.mipmap() && (images.count() != 1 || dstPos.count() != 1 || !dstPos[0].isNull())) {
+ qWarning("Mipmapped textures (%u) do not support partial uploads", id);
+ return;
+ }
+
+ // Make life simpler by disallowing queuing a new mipmapped upload before the previous one finishes.
+ if (t.mipmap() && t.fenceValue) {
+ qWarning("Attempted to queue mipmapped texture upload (%u) while a previous upload is still in progress", id);
+ return;
+ }
+
+ t.fenceValue = nextTextureUploadFenceValue.fetchAndAddAcquire(1) + 1;
+
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("adding upload for texture %u on the copy queue, fence %llu", id, t.fenceValue);
+
+ D3D12_RESOURCE_DESC textureDesc = t.texture->GetDesc();
+ const QSize adjustedTextureSize(textureDesc.Width, textureDesc.Height);
+
+ int totalSize = 0;
+ for (const QImage &image : images) {
+ int bytesPerPixel;
+ textureFormat(image.format(), t.alpha(), t.mipmap(),
+ flags.testFlag(QSGD3D12Engine::TextureUploadAlways32Bit),
+ nullptr, &bytesPerPixel);
+ const int w = !t.mipmap() ? image.width() : adjustedTextureSize.width();
+ const int h = !t.mipmap() ? image.height() : adjustedTextureSize.height();
+ const int stride = alignedSize(w * bytesPerPixel, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+ totalSize += alignedSize(h * stride, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
+ }
+
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("%d sub-uploads, heap size %d bytes", images.count(), totalSize);
+
+ // Instead of individual committed resources for each upload buffer,
+ // allocate only once and use placed resources.
+ D3D12_HEAP_PROPERTIES uploadHeapProp = {};
+ uploadHeapProp.Type = D3D12_HEAP_TYPE_UPLOAD;
+ D3D12_HEAP_DESC uploadHeapDesc = {};
+ uploadHeapDesc.SizeInBytes = totalSize;
+ uploadHeapDesc.Properties = uploadHeapProp;
+ uploadHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
+
+ Texture::StagingHeap sheap;
+ if (FAILED(device->CreateHeap(&uploadHeapDesc, IID_PPV_ARGS(&sheap.heap)))) {
+ qWarning("Failed to create texture upload heap of size %d", totalSize);
+ return;
+ }
+ t.stagingHeaps.append(sheap);
+
+ copyCommandList->Reset(copyCommandAllocator.Get(), nullptr);
+
+ int placedOffset = 0;
+ for (int i = 0; i < images.count(); ++i) {
+ QImage::Format convFormat;
+ int bytesPerPixel;
+ textureFormat(images[i].format(), t.alpha(), t.mipmap(),
+ flags.testFlag(QSGD3D12Engine::TextureUploadAlways32Bit),
+ &convFormat, &bytesPerPixel);
+ if (Q_UNLIKELY(debug_texture() && i == 0))
+ qDebug("source image format %d, target format %d, bpp %d", images[i].format(), convFormat, bytesPerPixel);
+
+ QImage convImage = images[i].format() == convFormat ? images[i] : images[i].convertToFormat(convFormat);
+
+ if (t.mipmap() && adjustedTextureSize != convImage.size())
+ convImage = convImage.scaled(adjustedTextureSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+
+ const int stride = alignedSize(convImage.width() * bytesPerPixel, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+
+ D3D12_RESOURCE_DESC bufDesc = {};
+ bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+ bufDesc.Width = stride * convImage.height();
+ bufDesc.Height = 1;
+ bufDesc.DepthOrArraySize = 1;
+ bufDesc.MipLevels = 1;
+ bufDesc.Format = DXGI_FORMAT_UNKNOWN;
+ bufDesc.SampleDesc.Count = 1;
+ bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+
+ Texture::StagingBuffer sbuf;
+ if (FAILED(device->CreatePlacedResource(sheap.heap.Get(), placedOffset,
+ &bufDesc, D3D12_RESOURCE_STATE_GENERIC_READ,
+ nullptr, IID_PPV_ARGS(&sbuf.buffer)))) {
+ qWarning("Failed to create texture upload buffer");
+ return;
+ }
+
+ quint8 *p = nullptr;
+ const D3D12_RANGE readRange = { 0, 0 };
+ if (FAILED(sbuf.buffer->Map(0, &readRange, reinterpret_cast<void **>(&p)))) {
+ qWarning("Map failed (texture upload buffer)");
+ return;
+ }
+ for (int y = 0, ye = convImage.height(); y < ye; ++y) {
+ memcpy(p, convImage.constScanLine(y), convImage.width() * bytesPerPixel);
+ p += stride;
+ }
+ sbuf.buffer->Unmap(0, nullptr);
+
+ D3D12_TEXTURE_COPY_LOCATION dstLoc;
+ dstLoc.pResource = t.texture.Get();
+ dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+ dstLoc.SubresourceIndex = 0;
+
+ D3D12_TEXTURE_COPY_LOCATION srcLoc;
+ srcLoc.pResource = sbuf.buffer.Get();
+ srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
+ srcLoc.PlacedFootprint.Offset = 0;
+ srcLoc.PlacedFootprint.Footprint.Format = textureDesc.Format;
+ srcLoc.PlacedFootprint.Footprint.Width = convImage.width();
+ srcLoc.PlacedFootprint.Footprint.Height = convImage.height();
+ srcLoc.PlacedFootprint.Footprint.Depth = 1;
+ srcLoc.PlacedFootprint.Footprint.RowPitch = stride;
+
+ copyCommandList->CopyTextureRegion(&dstLoc, dstPos[i].x(), dstPos[i].y(), 0, &srcLoc, nullptr);
+
+ t.stagingBuffers.append(sbuf);
+ placedOffset += alignedSize(bufDesc.Width, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
+ }
+
+ copyCommandList->Close();
+ ID3D12CommandList *commandLists[] = { copyCommandList.Get() };
+ copyCommandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
+ copyCommandQueue->Signal(textureUploadFence.Get(), t.fenceValue);
+}
+
+void QSGD3D12EnginePrivate::releaseTexture(uint id)
+{
+ if (!id || !initialized)
+ return;
+
+ const int idx = id - 1;
+ Q_ASSERT(idx < textures.count());
+
+ if (Q_UNLIKELY(debug_texture()))
+ qDebug("releasing texture %d", id);
+
+ Texture &t(textures[idx]);
+ if (!t.entryInUse())
+ return;
+
+ if (t.texture) {
+ deferredDelete(t.texture);
+ deferredDelete(t.srv, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ for (D3D12_CPU_DESCRIPTOR_HANDLE h : t.mipUAVs)
+ deferredDelete(h, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ }
+
+ QSet<PersistentFrameData::PendingRelease> *pendingReleasesSet = inFrame
+ ? &pframeData[currentPFrameIndex].pendingReleases
+ : &pframeData[(currentPFrameIndex + 1) % frameInFlightCount].outOfFramePendingReleases;
+
+ pendingReleasesSet->insert(PersistentFrameData::PendingRelease(PersistentFrameData::PendingRelease::TypeTexture, id));
+}
+
+void QSGD3D12EnginePrivate::useTexture(uint id)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ Q_ASSERT(id);
+ const int idx = id - 1;
+ Q_ASSERT(idx < textures.count() && textures[idx].entryInUse());
+
+ // Within one frame the order of calling this function determines the
+ // texture register (0, 1, ...) so fill up activeTextures accordingly.
+ tframeData.activeTextures[tframeData.activeTextureCount++]
+ = TransientFrameData::ActiveTexture(TransientFrameData::ActiveTexture::TypeTexture, id);
+
+ if (textures[idx].fenceValue)
+ pframeData[currentPFrameIndex].pendingTextureUploads.insert(id);
+}
+
+bool QSGD3D12EnginePrivate::MipMapGen::initialize(QSGD3D12EnginePrivate *enginePriv)
+{
+ engine = enginePriv;
+
+ D3D12_STATIC_SAMPLER_DESC sampler = {};
+ sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
+ sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+ sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+ sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+ sampler.MinLOD = 0.0f;
+ sampler.MaxLOD = D3D12_FLOAT32_MAX;
+
+ D3D12_DESCRIPTOR_RANGE descRange[2];
+ descRange[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
+ descRange[0].NumDescriptors = 1;
+ descRange[0].BaseShaderRegister = 0; // t0
+ descRange[0].RegisterSpace = 0;
+ descRange[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
+ descRange[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
+ descRange[1].NumDescriptors = 4;
+ descRange[1].BaseShaderRegister = 0; // u0..u3
+ descRange[1].RegisterSpace = 0;
+ descRange[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
+
+ // Split into two to allow switching between the first and second set of UAVs later.
+ D3D12_ROOT_PARAMETER rootParameters[3];
+ rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+ rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+ rootParameters[0].DescriptorTable.NumDescriptorRanges = 1;
+ rootParameters[0].DescriptorTable.pDescriptorRanges = &descRange[0];
+
+ rootParameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+ rootParameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+ rootParameters[1].DescriptorTable.NumDescriptorRanges = 1;
+ rootParameters[1].DescriptorTable.pDescriptorRanges = &descRange[1];
+
+ rootParameters[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
+ rootParameters[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+ rootParameters[2].Constants.Num32BitValues = 4; // uint2 mip1Size, uint sampleLevel, uint totalMips
+ rootParameters[2].Constants.ShaderRegister = 0; // b0
+ rootParameters[2].Constants.RegisterSpace = 0;
+
+ D3D12_ROOT_SIGNATURE_DESC desc = {};
+ desc.NumParameters = 3;
+ desc.pParameters = rootParameters;
+ desc.NumStaticSamplers = 1;
+ desc.pStaticSamplers = &sampler;
+
+ ComPtr<ID3DBlob> signature;
+ ComPtr<ID3DBlob> error;
+ if (FAILED(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error))) {
+ QByteArray msg(static_cast<const char *>(error->GetBufferPointer()), error->GetBufferSize());
+ qWarning("Failed to serialize compute root signature: %s", qPrintable(msg));
+ return false;
+ }
+ if (FAILED(engine->device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(),
+ IID_PPV_ARGS(&rootSig)))) {
+ qWarning("Failed to create compute root signature");
+ return false;
+ }
+
+ D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
+ psoDesc.pRootSignature = rootSig.Get();
+ psoDesc.CS.pShaderBytecode = g_CS_Generate4MipMaps;
+ psoDesc.CS.BytecodeLength = sizeof(g_CS_Generate4MipMaps);
+
+ if (FAILED(engine->device->CreateComputePipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState)))) {
+ qWarning("Failed to create compute pipeline state");
+ return false;
+ }
+
+ return true;
+}
+
+void QSGD3D12EnginePrivate::MipMapGen::releaseResources()
+{
+ pipelineState = nullptr;
+ rootSig = nullptr;
+}
+
+// The mipmap generator is used to insert commands on the main 3D queue. It is
+// guaranteed that the queue has a wait for the base texture level upload
+// before invoking queueGenerate(). There can be any number of invocations
+// without waiting for earlier ones to finish. finished() is invoked when it is
+// known for sure that frame containing the upload and mipmap generation has
+// finished on the GPU.
+
+void QSGD3D12EnginePrivate::MipMapGen::queueGenerate(const Texture &t)
+{
+ D3D12_RESOURCE_DESC textureDesc = t.texture->GetDesc();
+
+ engine->commandList->SetPipelineState(pipelineState.Get());
+ engine->commandList->SetComputeRootSignature(rootSig.Get());
+
+ // 1 SRV + (miplevels - 1) UAVs
+ const int descriptorCount = 1 + (textureDesc.MipLevels - 1);
+
+ engine->ensureGPUDescriptorHeap(descriptorCount);
+
+ // The descriptor heap is set on the command list either because the
+ // ensure() call above resized, or, typically, due to a texture-dependent
+ // draw call earlier.
+
+ engine->transitionResource(t.texture.Get(), engine->commandList,
+ D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+
+ QSGD3D12EnginePrivate::PersistentFrameData &pfd(engine->pframeData[engine->currentPFrameIndex]);
+
+ const uint stride = engine->cpuDescHeapManager.handleSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ D3D12_CPU_DESCRIPTOR_HANDLE h = pfd.gpuCbvSrvUavHeap->GetCPUDescriptorHandleForHeapStart();
+ h.ptr += pfd.cbvSrvUavNextFreeDescriptorIndex * stride;
+
+ engine->device->CopyDescriptorsSimple(1, h, t.srv, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ h.ptr += stride;
+
+ for (int level = 1; level < textureDesc.MipLevels; ++level, h.ptr += stride)
+ engine->device->CopyDescriptorsSimple(1, h, t.mipUAVs[level - 1], D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+
+ D3D12_GPU_DESCRIPTOR_HANDLE gpuAddr = pfd.gpuCbvSrvUavHeap->GetGPUDescriptorHandleForHeapStart();
+ gpuAddr.ptr += pfd.cbvSrvUavNextFreeDescriptorIndex * stride;
+
+ engine->commandList->SetComputeRootDescriptorTable(0, gpuAddr);
+ gpuAddr.ptr += stride; // now points to the first UAV
+
+ for (int level = 1; level < textureDesc.MipLevels; level += 4, gpuAddr.ptr += stride * 4) {
+ engine->commandList->SetComputeRootDescriptorTable(1, gpuAddr);
+
+ QSize sz(textureDesc.Width, textureDesc.Height);
+ sz.setWidth(qMax(1, sz.width() >> level));
+ sz.setHeight(qMax(1, sz.height() >> level));
+
+ const quint32 constants[4] = { quint32(sz.width()), quint32(sz.height()),
+ quint32(level - 1),
+ quint32(textureDesc.MipLevels - 1) };
+
+ engine->commandList->SetComputeRoot32BitConstants(2, 4, constants, 0);
+ engine->commandList->Dispatch(sz.width(), sz.height(), 1);
+ engine->uavBarrier(t.texture.Get(), engine->commandList);
+ }
+
+ engine->transitionResource(t.texture.Get(), engine->commandList,
+ D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
+
+ pfd.cbvSrvUavNextFreeDescriptorIndex += descriptorCount;
+}
+
+void QSGD3D12EnginePrivate::deferredDelete(ComPtr<ID3D12Resource> res)
+{
+ PersistentFrameData::DeleteQueueEntry e;
+ e.res = res;
+ QVector<PersistentFrameData::DeleteQueueEntry> *dq = inFrame
+ ? &pframeData[currentPFrameIndex].deleteQueue
+ : &pframeData[(currentPFrameIndex + 1) % frameInFlightCount].outOfFrameDeleteQueue;
+ (*dq) << e;
+}
+
+void QSGD3D12EnginePrivate::deferredDelete(ComPtr<ID3D12DescriptorHeap> dh)
+{
+ PersistentFrameData::DeleteQueueEntry e;
+ e.descHeap = dh;
+ QVector<PersistentFrameData::DeleteQueueEntry> *dq = inFrame
+ ? &pframeData[currentPFrameIndex].deleteQueue
+ : &pframeData[(currentPFrameIndex + 1) % frameInFlightCount].outOfFrameDeleteQueue;
+ (*dq) << e;
+}
+
+void QSGD3D12EnginePrivate::deferredDelete(D3D12_CPU_DESCRIPTOR_HANDLE h, D3D12_DESCRIPTOR_HEAP_TYPE type)
+{
+ PersistentFrameData::DeleteQueueEntry e;
+ e.cpuDescriptorPtr = h.ptr;
+ e.descHeapType = type;
+ QVector<PersistentFrameData::DeleteQueueEntry> *dq = inFrame
+ ? &pframeData[currentPFrameIndex].deleteQueue
+ : &pframeData[(currentPFrameIndex + 1) % frameInFlightCount].outOfFrameDeleteQueue;
+ (*dq) << e;
+}
+
+uint QSGD3D12EnginePrivate::genRenderTarget()
+{
+ return newId(&renderTargets);
+}
+
+void QSGD3D12EnginePrivate::createRenderTarget(uint id, const QSize &size, const QVector4D &clearColor, uint samples)
+{
+ ensureDevice();
+
+ Q_ASSERT(id);
+ const int idx = id - 1;
+ Q_ASSERT(idx < renderTargets.count() && renderTargets[idx].entryInUse());
+ RenderTarget &rt(renderTargets[idx]);
+
+ rt.rtv = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
+ rt.dsv = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
+ rt.srv = cpuDescHeapManager.allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+
+ ID3D12Resource *res = createColorBuffer(rt.rtv, size, clearColor, samples);
+ if (res)
+ rt.color.Attach(res);
+
+ ID3D12Resource *dsres = createDepthStencil(rt.dsv, size, samples);
+ if (dsres)
+ rt.ds.Attach(dsres);
+
+ const bool multisample = rt.color->GetDesc().SampleDesc.Count > 1;
+ syncEntryFlags(&rt, RenderTarget::Multisample, multisample);
+
+ if (!multisample) {
+ device->CreateShaderResourceView(rt.color.Get(), nullptr, rt.srv);
+ } else {
+ D3D12_HEAP_PROPERTIES defaultHeapProp = {};
+ defaultHeapProp.Type = D3D12_HEAP_TYPE_DEFAULT;
+
+ D3D12_RESOURCE_DESC textureDesc = {};
+ textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+ textureDesc.Width = size.width();
+ textureDesc.Height = size.height();
+ textureDesc.DepthOrArraySize = 1;
+ textureDesc.MipLevels = 1;
+ textureDesc.Format = RT_COLOR_FORMAT;
+ textureDesc.SampleDesc.Count = 1;
+ textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+
+ HRESULT hr = device->CreateCommittedResource(&defaultHeapProp, D3D12_HEAP_FLAG_NONE, &textureDesc,
+ D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&rt.colorResolve));
+ if (FAILED(hr)) {
+ qWarning("Failed to create resolve buffer: 0x%x", hr);
+ return;
+ }
+
+ device->CreateShaderResourceView(rt.colorResolve.Get(), nullptr, rt.srv);
+ }
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("created new render target %u, size %dx%d, samples %d", id, size.width(), size.height(), samples);
+}
+
+void QSGD3D12EnginePrivate::releaseRenderTarget(uint id)
+{
+ if (!id || !initialized)
+ return;
+
+ const int idx = id - 1;
+ Q_ASSERT(idx < renderTargets.count());
+ RenderTarget &rt(renderTargets[idx]);
+ if (!rt.entryInUse())
+ return;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("releasing render target %u", id);
+
+ if (rt.colorResolve) {
+ deferredDelete(rt.colorResolve);
+ rt.colorResolve = nullptr;
+ }
+ if (rt.color) {
+ deferredDelete(rt.color);
+ rt.color = nullptr;
+ deferredDelete(rt.rtv, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
+ deferredDelete(rt.srv, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+ }
+ if (rt.ds) {
+ deferredDelete(rt.ds);
+ rt.ds = nullptr;
+ deferredDelete(rt.dsv, D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
+ }
+
+ rt.flags &= ~RenderTarget::EntryInUse;
+}
+
+void QSGD3D12EnginePrivate::useRenderTargetAsTexture(uint id)
+{
+ if (!inFrame) {
+ qWarning("%s: Cannot be called outside begin/endFrame", __FUNCTION__);
+ return;
+ }
+
+ Q_ASSERT(id);
+ const int idx = id - 1;
+ Q_ASSERT(idx < renderTargets.count());
+ RenderTarget &rt(renderTargets[idx]);
+ Q_ASSERT(rt.entryInUse() && rt.color);
+
+ if (rt.flags & RenderTarget::NeedsReadBarrier) {
+ rt.flags &= ~RenderTarget::NeedsReadBarrier;
+ if (rt.flags & RenderTarget::Multisample)
+ resolveMultisampledTarget(rt.color.Get(), rt.colorResolve.Get(), D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, commandList);
+ else
+ transitionResource(rt.color.Get(), commandList, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
+ }
+
+ tframeData.activeTextures[tframeData.activeTextureCount++] =
+ TransientFrameData::ActiveTexture::ActiveTexture(TransientFrameData::ActiveTexture::TypeRenderTarget, id);
+}
+
+QImage QSGD3D12EnginePrivate::executeAndWaitReadbackRenderTarget(uint id)
+{
+ // Readback due to QQuickWindow::grabWindow() happens outside
+ // begin-endFrame, but QQuickItemGrabResult leads to rendering a layer
+ // without a real frame afterwards and triggering readback. This has to be
+ // supported as well.
+ if (inFrame && (!activeLayers || currentLayerDepth)) {
+ qWarning("%s: Cannot be called while frame preparation is active", __FUNCTION__);
+ return QImage();
+ }
+
+ // Due to the above we insert a fake "real" frame when a layer was just rendered into.
+ if (inFrame) {
+ beginFrame();
+ endFrame();
+ }
+
+ frameCommandList->Reset(frameCommandAllocator[frameIndex % frameInFlightCount].Get(), nullptr);
+
+ D3D12_RESOURCE_STATES bstate;
+ bool needsBarrier = false;
+ ID3D12Resource *rtRes;
+ if (id == 0) {
+ const int idx = presentFrameIndex % swapChainBufferCount;
+ if (windowSamples > 1) {
+ resolveMultisampledTarget(defaultRT[idx].Get(), backBufferRT[idx].Get(),
+ D3D12_RESOURCE_STATE_COPY_SOURCE, frameCommandList.Get());
+ } else {
+ bstate = D3D12_RESOURCE_STATE_PRESENT;
+ needsBarrier = true;
+ }
+ rtRes = backBufferRT[idx].Get();
+ } else {
+ const int idx = id - 1;
+ Q_ASSERT(idx < renderTargets.count());
+ RenderTarget &rt(renderTargets[idx]);
+ Q_ASSERT(rt.entryInUse() && rt.color);
+
+ if (rt.flags & RenderTarget::Multisample) {
+ resolveMultisampledTarget(rt.color.Get(), rt.colorResolve.Get(),
+ D3D12_RESOURCE_STATE_COPY_SOURCE, frameCommandList.Get());
+ rtRes = rt.colorResolve.Get();
+ } else {
+ rtRes = rt.color.Get();
+ bstate = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
+ needsBarrier = true;
+ }
+ }
+
+ ComPtr<ID3D12Resource> readbackBuf;
+
+ D3D12_RESOURCE_DESC rtDesc = rtRes->GetDesc();
+ UINT64 textureByteSize = 0;
+ D3D12_PLACED_SUBRESOURCE_FOOTPRINT textureLayout = {};
+ device->GetCopyableFootprints(&rtDesc, 0, 1, 0, &textureLayout, nullptr, nullptr, &textureByteSize);
+
+ D3D12_HEAP_PROPERTIES heapProp = {};
+ heapProp.Type = D3D12_HEAP_TYPE_READBACK;
+
+ D3D12_RESOURCE_DESC bufDesc = {};
+ bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+ bufDesc.Width = textureByteSize;
+ bufDesc.Height = 1;
+ bufDesc.DepthOrArraySize = 1;
+ bufDesc.MipLevels = 1;
+ bufDesc.Format = DXGI_FORMAT_UNKNOWN;
+ bufDesc.SampleDesc.Count = 1;
+ bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+
+ if (FAILED(device->CreateCommittedResource(&heapProp, D3D12_HEAP_FLAG_NONE, &bufDesc,
+ D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&readbackBuf)))) {
+ qWarning("Failed to create committed resource (readback buffer)");
+ return QImage();
+ }
+
+ D3D12_TEXTURE_COPY_LOCATION dstLoc;
+ dstLoc.pResource = readbackBuf.Get();
+ dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
+ dstLoc.PlacedFootprint = textureLayout;
+ D3D12_TEXTURE_COPY_LOCATION srcLoc;
+ srcLoc.pResource = rtRes;
+ srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+ srcLoc.SubresourceIndex = 0;
+
+ ID3D12GraphicsCommandList *cl = frameCommandList.Get();
+ if (needsBarrier)
+ transitionResource(rtRes, cl, bstate, D3D12_RESOURCE_STATE_COPY_SOURCE);
+ cl->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
+ if (needsBarrier)
+ transitionResource(rtRes, cl, D3D12_RESOURCE_STATE_COPY_SOURCE, bstate);
+
+ cl->Close();
+ ID3D12CommandList *commandLists[] = { cl };
+ commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
+
+ QScopedPointer<QSGD3D12CPUWaitableFence> f(createCPUWaitableFence());
+ waitForGPU(f.data()); // uh oh
+
+ QImage::Format fmt = imageFormatForTexture(rtDesc.Format);
+ if (fmt == QImage::Format_Invalid) {
+ qWarning("Could not map render target format %d to a QImage format", rtDesc.Format);
+ return QImage();
+ }
+ QImage img(rtDesc.Width, rtDesc.Height, fmt);
+ quint8 *p = nullptr;
+ const D3D12_RANGE readRange = { 0, 0 };
+ if (FAILED(readbackBuf->Map(0, &readRange, reinterpret_cast<void **>(&p)))) {
+ qWarning("Mapping the readback buffer failed");
+ return QImage();
+ }
+ const int bpp = 4; // ###
+ if (id == 0) {
+ for (UINT y = 0; y < rtDesc.Height; ++y) {
+ quint8 *dst = img.scanLine(y);
+ memcpy(dst, p, rtDesc.Width * bpp);
+ p += textureLayout.Footprint.RowPitch;
+ }
+ } else {
+ for (int y = rtDesc.Height - 1; y >= 0; --y) {
+ quint8 *dst = img.scanLine(y);
+ memcpy(dst, p, rtDesc.Width * bpp);
+ p += textureLayout.Footprint.RowPitch;
+ }
+ }
+ readbackBuf->Unmap(0, nullptr);
+
+ return img;
+}
+
+void QSGD3D12EnginePrivate::simulateDeviceLoss()
+{
+ qWarning("QSGD3D12Engine: Triggering device loss via TDR");
+ devLossTest.killDevice();
+}
+
+bool QSGD3D12EnginePrivate::DeviceLossTester::initialize(QSGD3D12EnginePrivate *enginePriv)
+{
+ engine = enginePriv;
+
+#ifdef DEVLOSS_TEST
+ D3D12_DESCRIPTOR_RANGE descRange[2];
+ descRange[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
+ descRange[0].NumDescriptors = 1;
+ descRange[0].BaseShaderRegister = 0;
+ descRange[0].RegisterSpace = 0;
+ descRange[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
+ descRange[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
+ descRange[1].NumDescriptors = 1;
+ descRange[1].BaseShaderRegister = 0;
+ descRange[1].RegisterSpace = 0;
+ descRange[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
+
+ D3D12_ROOT_PARAMETER param;
+ param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+ param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+ param.DescriptorTable.NumDescriptorRanges = 2;
+ param.DescriptorTable.pDescriptorRanges = descRange;
+
+ D3D12_ROOT_SIGNATURE_DESC desc = {};
+ desc.NumParameters = 1;
+ desc.pParameters = &param;
+
+ ComPtr<ID3DBlob> signature;
+ ComPtr<ID3DBlob> error;
+ if (FAILED(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error))) {
+ QByteArray msg(static_cast<const char *>(error->GetBufferPointer()), error->GetBufferSize());
+ qWarning("Failed to serialize compute root signature: %s", qPrintable(msg));
+ return false;
+ }
+ if (FAILED(engine->device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(),
+ IID_PPV_ARGS(&computeRootSignature)))) {
+ qWarning("Failed to create compute root signature");
+ return false;
+ }
+
+ D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
+ psoDesc.pRootSignature = computeRootSignature.Get();
+ psoDesc.CS.pShaderBytecode = g_timeout;
+ psoDesc.CS.BytecodeLength = sizeof(g_timeout);
+
+ if (FAILED(engine->device->CreateComputePipelineState(&psoDesc, IID_PPV_ARGS(&computeState)))) {
+ qWarning("Failed to create compute pipeline state");
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+void QSGD3D12EnginePrivate::DeviceLossTester::releaseResources()
+{
+ computeState = nullptr;
+ computeRootSignature = nullptr;
+}
+
+void QSGD3D12EnginePrivate::DeviceLossTester::killDevice()
+{
+#ifdef DEVLOSS_TEST
+ ID3D12CommandAllocator *ca = engine->frameCommandAllocator[engine->frameIndex % engine->frameInFlightCount].Get();
+ ID3D12GraphicsCommandList *cl = engine->frameCommandList.Get();
+ cl->Reset(ca, computeState.Get());
+
+ cl->SetComputeRootSignature(computeRootSignature.Get());
+ cl->Dispatch(256, 1, 1);
+
+ cl->Close();
+ ID3D12CommandList *commandLists[] = { cl };
+ engine->commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
+
+ engine->waitGPU();
+#endif
+}
+
+void *QSGD3D12EnginePrivate::getResource(QSGRendererInterface::Resource resource) const
+{
+ switch (resource) {
+ case QSGRendererInterface::Device:
+ return device;
+ case QSGRendererInterface::CommandQueue:
+ return commandQueue.Get();
+ case QSGRendererInterface::CommandList:
+ return commandList;
+ default:
+ break;
+ }
+ return nullptr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12engine_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12engine_p.h
new file mode 100644
index 0000000000..b30994fe0d
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12engine_p.h
@@ -0,0 +1,394 @@
+/****************************************************************************
+**
+** 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 QSGD3D12ENGINE_P_H
+#define QSGD3D12ENGINE_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 <QWindow>
+#include <QImage>
+#include <QVector4D>
+#include <qsggeometry.h>
+#include <qsgrendererinterface.h>
+#include <qt_windows.h>
+
+QT_BEGIN_NAMESPACE
+
+// No D3D or COM headers must be pulled in here. All that has to be isolated
+// to engine_p_p.h and engine.cpp.
+
+class QSGD3D12EnginePrivate;
+
+// Shader bytecode and other strings are expected to be static so that a
+// different pointer means a different shader.
+
+enum QSGD3D12Format {
+ FmtUnknown = 0,
+
+ FmtFloat4 = 2, // DXGI_FORMAT_R32G32B32A32_FLOAT
+ FmtFloat3 = 6, // DXGI_FORMAT_R32G32B32_FLOAT
+ FmtFloat2 = 16, // DXGI_FORMAT_R32G32_FLOAT
+ FmtFloat = 41, // DXGI_FORMAT_R32_FLOAT
+
+ // glVertexAttribPointer with GL_UNSIGNED_BYTE and normalized == true maps to the UNORM formats below
+ FmtUNormByte4 = 28, // DXGI_FORMAT_R8G8B8A8_UNORM
+ FmtUNormByte2 = 49, // DXGI_FORMAT_R8G8_UNORM
+ FmtUNormByte = 61, // DXGI_FORMAT_R8_UNORM
+
+ // Index data types
+ FmtUnsignedShort = 57, // DXGI_FORMAT_R16_UINT
+ FmtUnsignedInt = 42 // DXGI_FORMAT_R32_UINT
+};
+
+struct QSGD3D12InputElement
+{
+ const char *semanticName = nullptr;
+ int semanticIndex = 0;
+ QSGD3D12Format format = FmtFloat4;
+ quint32 slot = 0;
+ quint32 offset = 0;
+
+ bool operator==(const QSGD3D12InputElement &other) const {
+ return semanticName == other.semanticName && semanticIndex == other.semanticIndex
+ && format == other.format && slot == other.slot && offset == other.offset;
+ }
+};
+
+inline uint qHash(const QSGD3D12InputElement &key, uint seed = 0)
+{
+ return qHash(key.semanticName, seed) + key.semanticIndex + key.format + key.offset;
+}
+
+struct QSGD3D12TextureView
+{
+ enum Filter {
+ FilterNearest = 0,
+ FilterLinear = 0x15,
+ FilterMinMagNearestMipLinear = 0x1,
+ FilterMinMagLinearMipNearest = 0x14
+ };
+
+ enum AddressMode {
+ AddressWrap = 1,
+ AddressClamp = 3
+ };
+
+ Filter filter = FilterLinear;
+ AddressMode addressModeHoriz = AddressClamp;
+ AddressMode addressModeVert = AddressClamp;
+
+ bool operator==(const QSGD3D12TextureView &other) const {
+ return filter == other.filter
+ && addressModeHoriz == other.addressModeHoriz
+ && addressModeVert == other.addressModeVert;
+ }
+};
+
+inline uint qHash(const QSGD3D12TextureView &key, uint seed = 0)
+{
+ Q_UNUSED(seed);
+ return key.filter + key.addressModeHoriz + key.addressModeVert;
+}
+
+const int QSGD3D12_MAX_TEXTURE_VIEWS = 8;
+
+struct QSGD3D12RootSignature
+{
+ int textureViewCount = 0;
+ QSGD3D12TextureView textureViews[QSGD3D12_MAX_TEXTURE_VIEWS];
+
+ bool operator==(const QSGD3D12RootSignature &other) const {
+ if (textureViewCount != other.textureViewCount)
+ return false;
+ for (int i = 0; i < textureViewCount; ++i)
+ if (!(textureViews[i] == other.textureViews[i]))
+ return false;
+ return true;
+ }
+};
+
+inline uint qHash(const QSGD3D12RootSignature &key, uint seed = 0)
+{
+ return key.textureViewCount + (key.textureViewCount > 0 ? qHash(key.textureViews[0], seed) : 0);
+}
+
+// Shader bytecode blobs and root signature-related data.
+struct QSGD3D12ShaderState
+{
+ const quint8 *vs = nullptr;
+ quint32 vsSize = 0;
+ const quint8 *ps = nullptr;
+ quint32 psSize = 0;
+
+ QSGD3D12RootSignature rootSig;
+
+ bool operator==(const QSGD3D12ShaderState &other) const {
+ return vs == other.vs && vsSize == other.vsSize
+ && ps == other.ps && psSize == other.psSize
+ && rootSig == other.rootSig;
+ }
+};
+
+inline uint qHash(const QSGD3D12ShaderState &key, uint seed = 0)
+{
+ return qHash(key.vs, seed) + key.vsSize + qHash(key.ps, seed) + key.psSize + qHash(key.rootSig, seed);
+}
+
+const int QSGD3D12_MAX_INPUT_ELEMENTS = 8;
+
+struct QSGD3D12PipelineState
+{
+ enum CullMode {
+ CullNone = 1,
+ CullFront,
+ CullBack
+ };
+
+ enum CompareFunc {
+ CompareNever = 1,
+ CompareLess,
+ CompareEqual,
+ CompareLessEqual,
+ CompareGreater,
+ CompareNotEqual,
+ CompareGreaterEqual,
+ CompareAlways
+ };
+
+ enum StencilOp {
+ StencilKeep = 1,
+ StencilZero,
+ StencilReplace,
+ StencilIncrSat,
+ StencilDecrSat,
+ StencilInvert,
+ StencilIncr,
+ StencilDescr
+ };
+
+ enum TopologyType {
+ TopologyTypePoint = 1,
+ TopologyTypeLine,
+ TopologyTypeTriangle
+ };
+
+ enum BlendType {
+ BlendNone,
+ BlendPremul, // == GL_ONE, GL_ONE_MINUS_SRC_ALPHA
+ BlendColor // == GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR
+ };
+
+ QSGD3D12ShaderState shaders;
+
+ int inputElementCount = 0;
+ QSGD3D12InputElement inputElements[QSGD3D12_MAX_INPUT_ELEMENTS];
+
+ CullMode cullMode = CullNone;
+ bool frontCCW = true;
+ bool colorWrite = true;
+ BlendType blend = BlendNone;
+ bool depthEnable = true;
+ CompareFunc depthFunc = CompareLess;
+ bool depthWrite = true;
+ bool stencilEnable = false;
+ CompareFunc stencilFunc = CompareEqual;
+ StencilOp stencilFailOp = StencilKeep;
+ StencilOp stencilDepthFailOp = StencilKeep;
+ StencilOp stencilPassOp = StencilKeep;
+ TopologyType topologyType = TopologyTypeTriangle;
+
+ bool operator==(const QSGD3D12PipelineState &other) const {
+ bool eq = shaders == other.shaders
+ && inputElementCount == other.inputElementCount
+ && cullMode == other.cullMode
+ && frontCCW == other.frontCCW
+ && colorWrite == other.colorWrite
+ && blend == other.blend
+ && depthEnable == other.depthEnable
+ && (!depthEnable || depthFunc == other.depthFunc)
+ && depthWrite == other.depthWrite
+ && stencilEnable == other.stencilEnable
+ && (!stencilEnable || stencilFunc == other.stencilFunc)
+ && (!stencilEnable || stencilFailOp == other.stencilFailOp)
+ && (!stencilEnable || stencilDepthFailOp == other.stencilDepthFailOp)
+ && (!stencilEnable || stencilPassOp == other.stencilPassOp)
+ && topologyType == other.topologyType;
+ if (eq) {
+ for (int i = 0; i < inputElementCount; ++i) {
+ if (!(inputElements[i] == other.inputElements[i])) {
+ eq = false;
+ break;
+ }
+ }
+ }
+ return eq;
+ }
+};
+
+inline uint qHash(const QSGD3D12PipelineState &key, uint seed = 0)
+{
+ return qHash(key.shaders, seed) + key.inputElementCount
+ + key.cullMode + key.frontCCW
+ + key.colorWrite + key.blend
+ + key.depthEnable + key.depthWrite
+ + key.stencilEnable
+ + key.topologyType;
+}
+
+class QSGD3D12Engine
+{
+public:
+ QSGD3D12Engine();
+ ~QSGD3D12Engine();
+
+ bool attachToWindow(WId window, const QSize &size, float dpr, int surfaceFormatSamples, bool alpha);
+ void releaseResources();
+ bool hasResources() const;
+ void setWindowSize(const QSize &size, float dpr);
+ WId window() const;
+ QSize windowSize() const;
+ float windowDevicePixelRatio() const;
+ uint windowSamples() const;
+
+ void beginFrame();
+ void endFrame();
+ void beginLayer();
+ void endLayer();
+ void invalidateCachedFrameState();
+ void restoreFrameState(bool minimal = false);
+
+ uint genBuffer();
+ void releaseBuffer(uint id);
+ void resetBuffer(uint id, const quint8 *data, int size);
+ void markBufferDirty(uint id, int offset, int size);
+
+ enum ClearFlag {
+ ClearDepth = 0x1,
+ ClearStencil = 0x2
+ };
+ Q_DECLARE_FLAGS(ClearFlags, ClearFlag)
+
+ void queueViewport(const QRect &rect);
+ void queueScissor(const QRect &rect);
+ void queueSetRenderTarget(uint id = 0);
+ void queueClearRenderTarget(const QColor &color);
+ void queueClearDepthStencil(float depthValue, quint8 stencilValue, ClearFlags which);
+ void queueSetBlendFactor(const QVector4D &factor);
+ void queueSetStencilRef(quint32 ref);
+
+ void finalizePipeline(const QSGD3D12PipelineState &pipelineState);
+
+ struct DrawParams {
+ QSGGeometry::DrawingMode mode = QSGGeometry::DrawTriangles;
+ int count = 0;
+ uint vertexBuf = 0;
+ uint indexBuf = 0;
+ uint constantBuf = 0;
+ int vboOffset = 0;
+ int vboSize = 0;
+ int vboStride = 0;
+ int cboOffset = 0;
+ int startIndexIndex = -1;
+ QSGD3D12Format indexFormat = FmtUnsignedShort;
+ };
+
+ void queueDraw(const DrawParams &params);
+
+ void present();
+ void waitGPU();
+
+ static quint32 alignedConstantBufferSize(quint32 size);
+ static QSGD3D12Format toDXGIFormat(QSGGeometry::Type sgtype, int tupleSize = 1, int *size = nullptr);
+ static int mipMapLevels(const QSize &size);
+ static QSize mipMapAdjustedSourceSize(const QSize &size);
+
+ enum TextureCreateFlag {
+ TextureWithAlpha = 0x01,
+ TextureWithMipMaps = 0x02,
+ TextureAlways32Bit = 0x04
+ };
+ Q_DECLARE_FLAGS(TextureCreateFlags, TextureCreateFlag)
+
+ enum TextureUploadFlag {
+ TextureUploadAlways32Bit = 0x01
+ };
+ Q_DECLARE_FLAGS(TextureUploadFlags, TextureUploadFlag)
+
+ uint genTexture();
+ void createTexture(uint id, const QSize &size, QImage::Format format, TextureCreateFlags flags);
+ void queueTextureResize(uint id, const QSize &size);
+ void queueTextureUpload(uint id, const QImage &image, const QPoint &dstPos = QPoint(), TextureUploadFlags flags = 0);
+ void queueTextureUpload(uint id, const QVector<QImage> &images, const QVector<QPoint> &dstPos, TextureUploadFlags flags = 0);
+ void releaseTexture(uint id);
+ void useTexture(uint id);
+
+ uint genRenderTarget();
+ void createRenderTarget(uint id, const QSize &size, const QVector4D &clearColor, uint samples);
+ void releaseRenderTarget(uint id);
+ void useRenderTargetAsTexture(uint id);
+ uint activeRenderTarget() const;
+
+ QImage executeAndWaitReadbackRenderTarget(uint id = 0);
+
+ void simulateDeviceLoss();
+
+ void *getResource(QQuickWindow *window, QSGRendererInterface::Resource resource) const;
+
+private:
+ QSGD3D12EnginePrivate *d;
+ Q_DISABLE_COPY(QSGD3D12Engine)
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSGD3D12Engine::ClearFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSGD3D12Engine::TextureCreateFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSGD3D12Engine::TextureUploadFlags)
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12ENGINE_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12engine_p_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12engine_p_p.h
new file mode 100644
index 0000000000..1048ed63e7
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12engine_p_p.h
@@ -0,0 +1,454 @@
+/****************************************************************************
+**
+** 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 QSGD3D12ENGINE_P_P_H
+#define QSGD3D12ENGINE_P_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 "qsgd3d12engine_p.h"
+#include <QCache>
+
+#include <d3d12.h>
+#include <dxgi1_4.h>
+#include <dcomp.h>
+#include <wrl/client.h>
+
+using namespace Microsoft::WRL;
+
+// No moc-related features (Q_OBJECT, signals, etc.) can be used here to due
+// moc-generated code failing to compile when combined with COM stuff.
+
+// Recommended reading before moving further: https://github.com/Microsoft/DirectXTK/wiki/ComPtr
+// Note esp. operator= vs. Attach and operator& vs. GetAddressOf
+
+// ID3D12* is never passed to Qt containers directly. Always use ComPtr and put it into a struct.
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12CPUDescriptorHeapManager
+{
+public:
+ void initialize(ID3D12Device *device);
+
+ void releaseResources();
+
+ D3D12_CPU_DESCRIPTOR_HANDLE allocate(D3D12_DESCRIPTOR_HEAP_TYPE type);
+ void release(D3D12_CPU_DESCRIPTOR_HANDLE handle, D3D12_DESCRIPTOR_HEAP_TYPE type);
+ quint32 handleSize(D3D12_DESCRIPTOR_HEAP_TYPE type) const { return m_handleSizes[type]; }
+
+private:
+ ID3D12Device *m_device = nullptr;
+ struct Heap {
+ D3D12_DESCRIPTOR_HEAP_TYPE type;
+ ComPtr<ID3D12DescriptorHeap> heap;
+ D3D12_CPU_DESCRIPTOR_HANDLE start;
+ quint32 handleSize;
+ quint32 freeMap[8];
+ };
+ QVector<Heap> m_heaps;
+ quint32 m_handleSizes[D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES];
+};
+
+class QSGD3D12DeviceManager
+{
+public:
+ ID3D12Device *ref();
+ void unref();
+ void deviceLossDetected();
+ IDXGIFactory4 *dxgi();
+
+ struct DeviceLossObserver {
+ virtual void deviceLost() = 0;
+ };
+ void registerDeviceLossObserver(DeviceLossObserver *observer);
+
+private:
+ void ensureCreated();
+
+ ComPtr<ID3D12Device> m_device;
+ ComPtr<IDXGIFactory4> m_factory;
+ QAtomicInt m_ref;
+ QVector<DeviceLossObserver *> m_observers;
+};
+
+struct QSGD3D12CPUWaitableFence
+{
+ ~QSGD3D12CPUWaitableFence() {
+ if (event)
+ CloseHandle(event);
+ }
+ ComPtr<ID3D12Fence> fence;
+ HANDLE event = nullptr;
+ QAtomicInt value;
+};
+
+class QSGD3D12EnginePrivate : public QSGD3D12DeviceManager::DeviceLossObserver
+{
+public:
+ void initialize(WId w, const QSize &size, float dpr, int surfaceFormatSamples, bool alpha);
+ bool isInitialized() const { return initialized; }
+ void releaseResources();
+ void setWindowSize(const QSize &size, float dpr);
+ WId currentWindow() const { return window; }
+ QSize currentWindowSize() const { return windowSize; }
+ float currentWindowDpr() const { return windowDpr; }
+ uint currentWindowSamples() const { return windowSamples; }
+
+ void beginFrame();
+ void endFrame();
+ void beginLayer();
+ void endLayer();
+ void invalidateCachedFrameState();
+ void restoreFrameState(bool minimal = false);
+
+ uint genBuffer();
+ void releaseBuffer(uint id);
+ void resetBuffer(uint id, const quint8 *data, int size);
+ void markBufferDirty(uint id, int offset, int size);
+
+ void queueViewport(const QRect &rect);
+ void queueScissor(const QRect &rect);
+ void queueSetRenderTarget(uint id);
+ void queueClearRenderTarget(const QColor &color);
+ void queueClearDepthStencil(float depthValue, quint8 stencilValue, QSGD3D12Engine::ClearFlags which);
+ void queueSetBlendFactor(const QVector4D &factor);
+ void queueSetStencilRef(quint32 ref);
+
+ void finalizePipeline(const QSGD3D12PipelineState &pipelineState);
+
+ void queueDraw(const QSGD3D12Engine::DrawParams &params);
+
+ void present();
+ void waitGPU();
+
+ uint genTexture();
+ void createTexture(uint id, const QSize &size, QImage::Format format, QSGD3D12Engine::TextureCreateFlags flags);
+ void queueTextureResize(uint id, const QSize &size);
+ void queueTextureUpload(uint id, const QVector<QImage> &images, const QVector<QPoint> &dstPos,
+ QSGD3D12Engine::TextureUploadFlags flags);
+ void releaseTexture(uint id);
+ void useTexture(uint id);
+
+ uint genRenderTarget();
+ void createRenderTarget(uint id, const QSize &size, const QVector4D &clearColor, uint samples);
+ void releaseRenderTarget(uint id);
+ void useRenderTargetAsTexture(uint id);
+ uint activeRenderTarget() const { return currentRenderTarget; }
+
+ QImage executeAndWaitReadbackRenderTarget(uint id);
+
+ void simulateDeviceLoss();
+
+ void *getResource(QSGRendererInterface::Resource resource) const;
+
+ // the device is intentionally hidden here. all resources have to go
+ // through the engine and, unlike with GL, cannot just be created in random
+ // places due to the need for proper tracking, managing and releasing.
+private:
+ void ensureDevice();
+ void setupDefaultRenderTargets();
+ void deviceLost() override;
+
+ bool createCbvSrvUavHeap(int pframeIndex, int descriptorCount);
+ void setDescriptorHeaps(bool force = false);
+ void ensureGPUDescriptorHeap(int cbvSrvUavDescriptorCount);
+
+ DXGI_SAMPLE_DESC makeSampleDesc(DXGI_FORMAT format, uint samples);
+ ID3D12Resource *createColorBuffer(D3D12_CPU_DESCRIPTOR_HANDLE viewHandle, const QSize &size,
+ const QVector4D &clearColor, uint samples);
+ ID3D12Resource *createDepthStencil(D3D12_CPU_DESCRIPTOR_HANDLE viewHandle, const QSize &size, uint samples);
+
+ QSGD3D12CPUWaitableFence *createCPUWaitableFence() const;
+ void waitForGPU(QSGD3D12CPUWaitableFence *f) const;
+
+ void transitionResource(ID3D12Resource *resource, ID3D12GraphicsCommandList *commandList,
+ D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) const;
+ void resolveMultisampledTarget(ID3D12Resource *msaa, ID3D12Resource *resolve, D3D12_RESOURCE_STATES resolveUsage,
+ ID3D12GraphicsCommandList *commandList) const;
+ void uavBarrier(ID3D12Resource *resource, ID3D12GraphicsCommandList *commandList) const;
+
+ ID3D12Resource *createBuffer(int size);
+
+ typedef QVector<QPair<int, int> > DirtyList;
+ void addDirtyRange(DirtyList *dirty, int offset, int size, int bufferSize);
+
+ struct PersistentFrameData {
+ ComPtr<ID3D12DescriptorHeap> gpuCbvSrvUavHeap;
+ int gpuCbvSrvUavHeapSize;
+ int cbvSrvUavNextFreeDescriptorIndex;
+ QSet<uint> pendingTextureUploads;
+ QSet<uint> pendingTextureMipMap;
+ struct DeleteQueueEntry {
+ ComPtr<ID3D12Resource> res;
+ ComPtr<ID3D12DescriptorHeap> descHeap;
+ SIZE_T cpuDescriptorPtr = 0;
+ D3D12_DESCRIPTOR_HEAP_TYPE descHeapType = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
+ };
+ QVector<DeleteQueueEntry> deleteQueue;
+ QVector<DeleteQueueEntry> outOfFrameDeleteQueue;
+ QSet<uint> buffersUsedInDrawCallSet;
+ QSet<uint> buffersUsedInFrame;
+ struct PendingRelease {
+ enum Type {
+ TypeTexture,
+ TypeBuffer
+ };
+ Type type = TypeTexture;
+ uint id = 0;
+ PendingRelease(Type type, uint id) : type(type), id(id) { }
+ PendingRelease() { }
+ bool operator==(const PendingRelease &other) const { return type == other.type && id == other.id; }
+ };
+ QSet<PendingRelease> pendingReleases;
+ QSet<PendingRelease> outOfFramePendingReleases;
+ };
+ friend uint qHash(const PersistentFrameData::PendingRelease &pr, uint seed);
+
+ void deferredDelete(ComPtr<ID3D12Resource> res);
+ void deferredDelete(ComPtr<ID3D12DescriptorHeap> dh);
+ void deferredDelete(D3D12_CPU_DESCRIPTOR_HANDLE h, D3D12_DESCRIPTOR_HEAP_TYPE type);
+
+ struct Buffer;
+ void ensureBuffer(Buffer *buf);
+ void updateBuffer(Buffer *buf);
+
+ void beginDrawCalls();
+ void beginFrameDraw();
+ void endDrawCalls(bool lastInFrame = false);
+
+ static const int MAX_SWAP_CHAIN_BUFFER_COUNT = 4;
+ static const int MAX_FRAME_IN_FLIGHT_COUNT = 4;
+
+ bool initialized = false;
+ bool inFrame = false;
+ WId window = 0;
+ QSize windowSize;
+ float windowDpr;
+ uint windowSamples;
+ bool windowAlpha;
+ int swapChainBufferCount;
+ int frameInFlightCount;
+ int waitableSwapChainMaxLatency;
+ ID3D12Device *device;
+ ComPtr<ID3D12CommandQueue> commandQueue;
+ ComPtr<ID3D12CommandQueue> copyCommandQueue;
+ ComPtr<IDXGISwapChain3> swapChain;
+ HANDLE swapEvent;
+ ComPtr<ID3D12Resource> backBufferRT[MAX_SWAP_CHAIN_BUFFER_COUNT];
+ ComPtr<ID3D12Resource> defaultRT[MAX_SWAP_CHAIN_BUFFER_COUNT];
+ D3D12_CPU_DESCRIPTOR_HANDLE defaultRTV[MAX_SWAP_CHAIN_BUFFER_COUNT];
+ ComPtr<ID3D12Resource> defaultDS;
+ D3D12_CPU_DESCRIPTOR_HANDLE defaultDSV;
+ ComPtr<ID3D12CommandAllocator> frameCommandAllocator[MAX_FRAME_IN_FLIGHT_COUNT];
+ ComPtr<ID3D12CommandAllocator> copyCommandAllocator;
+ ComPtr<ID3D12GraphicsCommandList> frameCommandList;
+ ComPtr<ID3D12GraphicsCommandList> copyCommandList;
+ QSGD3D12CPUDescriptorHeapManager cpuDescHeapManager;
+ quint64 presentFrameIndex;
+ quint64 frameIndex;
+ QSGD3D12CPUWaitableFence *presentFence = nullptr;
+ QSGD3D12CPUWaitableFence *frameFence[MAX_FRAME_IN_FLIGHT_COUNT];
+
+ PersistentFrameData pframeData[MAX_FRAME_IN_FLIGHT_COUNT];
+ int currentPFrameIndex;
+ ID3D12GraphicsCommandList *commandList = nullptr;
+ int activeLayers = 0;
+ int currentLayerDepth = 0;
+
+ struct PSOCacheEntry {
+ ComPtr<ID3D12PipelineState> pso;
+ };
+ QCache<QSGD3D12PipelineState, PSOCacheEntry> psoCache;
+ struct RootSigCacheEntry {
+ ComPtr<ID3D12RootSignature> rootSig;
+ };
+ QCache<QSGD3D12RootSignature, RootSigCacheEntry> rootSigCache;
+
+ struct Texture {
+ enum Flag {
+ EntryInUse = 0x01,
+ Alpha = 0x02,
+ MipMap = 0x04
+ };
+ int flags = 0;
+ bool entryInUse() const { return flags & EntryInUse; }
+ bool alpha() const { return flags & Alpha; }
+ bool mipmap() const { return flags & MipMap; }
+ ComPtr<ID3D12Resource> texture;
+ D3D12_CPU_DESCRIPTOR_HANDLE srv;
+ quint64 fenceValue = 0;
+ quint64 lastWaitFenceValue = 0;
+ struct StagingHeap {
+ ComPtr<ID3D12Heap> heap;
+ };
+ QVector<StagingHeap> stagingHeaps;
+ struct StagingBuffer {
+ ComPtr<ID3D12Resource> buffer;
+ };
+ QVector<StagingBuffer> stagingBuffers;
+ QVector<D3D12_CPU_DESCRIPTOR_HANDLE> mipUAVs;
+ };
+
+ QVector<Texture> textures;
+ ComPtr<ID3D12Fence> textureUploadFence;
+ QAtomicInt nextTextureUploadFenceValue;
+
+ struct TransientFrameData {
+ QSGGeometry::DrawingMode drawingMode;
+ uint currentIndexBuffer;
+ struct ActiveTexture {
+ enum Type {
+ TypeTexture,
+ TypeRenderTarget
+ };
+ Type type = TypeTexture;
+ uint id = 0;
+ ActiveTexture(Type type, uint id) : type(type), id(id) { }
+ ActiveTexture() { }
+ };
+ int activeTextureCount;
+ ActiveTexture activeTextures[QSGD3D12_MAX_TEXTURE_VIEWS];
+ int drawCount;
+ ID3D12PipelineState *lastPso;
+ ID3D12RootSignature *lastRootSig;
+ bool descHeapSet;
+
+ QRect viewport;
+ QRect scissor;
+ QVector4D blendFactor = QVector4D(1, 1, 1, 1);
+ quint32 stencilRef = 1;
+ QSGD3D12PipelineState pipelineState;
+ };
+ TransientFrameData tframeData;
+
+ struct MipMapGen {
+ bool initialize(QSGD3D12EnginePrivate *enginePriv);
+ void releaseResources();
+ void queueGenerate(const Texture &t);
+
+ QSGD3D12EnginePrivate *engine;
+ ComPtr<ID3D12RootSignature> rootSig;
+ ComPtr<ID3D12PipelineState> pipelineState;
+ };
+
+ MipMapGen mipmapper;
+
+ struct RenderTarget {
+ enum Flag {
+ EntryInUse = 0x01,
+ NeedsReadBarrier = 0x02,
+ Multisample = 0x04
+ };
+ int flags = 0;
+ bool entryInUse() const { return flags & EntryInUse; }
+ ComPtr<ID3D12Resource> color;
+ ComPtr<ID3D12Resource> colorResolve;
+ D3D12_CPU_DESCRIPTOR_HANDLE rtv;
+ ComPtr<ID3D12Resource> ds;
+ D3D12_CPU_DESCRIPTOR_HANDLE dsv;
+ D3D12_CPU_DESCRIPTOR_HANDLE srv;
+ };
+
+ QVector<RenderTarget> renderTargets;
+ uint currentRenderTarget;
+
+ struct CPUBufferRef {
+ const quint8 *p = nullptr;
+ quint32 size = 0;
+ DirtyList dirty;
+ CPUBufferRef() { dirty.reserve(16); }
+ };
+
+ struct Buffer {
+ enum Flag {
+ EntryInUse = 0x01
+ };
+ int flags = 0;
+ bool entryInUse() const { return flags & EntryInUse; }
+ struct InFlightData {
+ ComPtr<ID3D12Resource> buffer;
+ DirtyList dirty;
+ quint32 dataSize = 0;
+ quint32 resourceSize = 0;
+ InFlightData() { dirty.reserve(16); }
+ };
+ InFlightData d[MAX_FRAME_IN_FLIGHT_COUNT];
+ CPUBufferRef cpuDataRef;
+ };
+
+ QVector<Buffer> buffers;
+
+ struct DeviceLossTester {
+ bool initialize(QSGD3D12EnginePrivate *enginePriv);
+ void releaseResources();
+ void killDevice();
+
+ QSGD3D12EnginePrivate *engine;
+ ComPtr<ID3D12PipelineState> computeState;
+ ComPtr<ID3D12RootSignature> computeRootSignature;
+ };
+
+ DeviceLossTester devLossTest;
+
+#ifndef Q_OS_WINRT
+ ComPtr<IDCompositionDevice> dcompDevice;
+ ComPtr<IDCompositionTarget> dcompTarget;
+ ComPtr<IDCompositionVisual> dcompVisual;
+#endif
+};
+
+inline uint qHash(const QSGD3D12EnginePrivate::PersistentFrameData::PendingRelease &pr, uint seed = 0)
+{
+ Q_UNUSED(seed);
+ return pr.id + pr.type;
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12glyphcache.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12glyphcache.cpp
new file mode 100644
index 0000000000..915917c3d5
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12glyphcache.cpp
@@ -0,0 +1,197 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12glyphcache_p.h"
+#include "qsgd3d12engine_p.h"
+
+QT_BEGIN_NAMESPACE
+
+// Convert A8 glyphs to 32-bit in the engine. This is here to work around
+// QTBUG-55330 for AMD cards.
+// If removing, textmask.hlsl must be adjusted! (.a -> .r)
+#define ALWAYS_32BIT
+
+// 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)
+
+QSGD3D12GlyphCache::QSGD3D12GlyphCache(QSGD3D12Engine *engine, QFontEngine::GlyphFormat format, const QTransform &matrix)
+ : QTextureGlyphCache(format, matrix),
+ m_engine(engine)
+{
+}
+
+QSGD3D12GlyphCache::~QSGD3D12GlyphCache()
+{
+ if (m_id)
+ m_engine->releaseTexture(m_id);
+}
+
+void QSGD3D12GlyphCache::createTextureData(int width, int height)
+{
+ width = qMax(128, width);
+ height = qMax(32, height);
+
+ m_id = m_engine->genTexture();
+ Q_ASSERT(m_id);
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("new glyph cache texture %u of size %dx%d, fontengine format %d", m_id, width, height, m_format);
+
+ m_size = QSize(width, height);
+
+ const QImage::Format imageFormat =
+ m_format == QFontEngine::Format_A8 ? QImage::Format_Alpha8 : QImage::Format_ARGB32_Premultiplied;
+ m_engine->createTexture(m_id, m_size, imageFormat, QSGD3D12Engine::TextureWithAlpha
+#ifdef ALWAYS_32BIT
+ | QSGD3D12Engine::TextureAlways32Bit
+#endif
+ );
+}
+
+void QSGD3D12GlyphCache::resizeTextureData(int width, int height)
+{
+ width = qMax(128, width);
+ height = qMax(32, height);
+
+ if (m_size.width() >= width && m_size.height() >= height)
+ return;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("glyph cache texture %u resize to %dx%d", m_id, width, height);
+
+ m_size = QSize(width, height);
+
+ m_engine->queueTextureResize(m_id, m_size);
+}
+
+void QSGD3D12GlyphCache::beginFillTexture()
+{
+ Q_ASSERT(m_glyphImages.isEmpty() && m_glyphPos.isEmpty());
+}
+
+void QSGD3D12GlyphCache::fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition)
+{
+ QImage mask = textureMapForGlyph(glyph, subPixelPosition);
+ const int maskWidth = mask.width();
+ const int maskHeight = mask.height();
+
+ if (mask.format() == QImage::Format_Mono) {
+ mask = mask.convertToFormat(QImage::Format_Indexed8);
+ for (int y = 0; y < maskHeight; ++y) {
+ uchar *src = mask.scanLine(y);
+ for (int x = 0; x < maskWidth; ++x)
+ src[x] = -src[x]; // convert 0 and 1 into 0 and 255
+ }
+ } else if (mask.depth() == 32) {
+ if (mask.format() == QImage::Format_RGB32) {
+ // We need to make the alpha component equal to the average of the RGB values.
+ // This is needed when drawing sub-pixel antialiased text on translucent targets.
+ for (int y = 0; y < maskHeight; ++y) {
+ QRgb *src = reinterpret_cast<QRgb *>(mask.scanLine(y));
+ for (int x = 0; x < maskWidth; ++x) {
+ const int r = qRed(src[x]);
+ const int g = qGreen(src[x]);
+ const int b = qBlue(src[x]);
+ int avg;
+ if (mask.format() == QImage::Format_RGB32)
+ avg = (r + g + b + 1) / 3; // "+1" for rounding.
+ else // Format_ARGB32_Premultiplied
+ avg = qAlpha(src[x]);
+ src[x] = qRgba(r, g, b, avg);
+ }
+ }
+ }
+ }
+
+ m_glyphImages.append(mask);
+ m_glyphPos.append(QPoint(c.x, c.y));
+}
+
+void QSGD3D12GlyphCache::endFillTexture()
+{
+ if (m_glyphImages.isEmpty())
+ return;
+
+ Q_ASSERT(m_id);
+
+ m_engine->queueTextureUpload(m_id, m_glyphImages, m_glyphPos
+#ifdef ALWAYS_32BIT
+ , QSGD3D12Engine::TextureUploadAlways32Bit
+#endif
+ );
+
+ // Nothing else left to do, it is up to the text material to call
+ // useTexture() which will then add the texture dependency to the frame.
+
+ m_glyphImages.clear();
+ m_glyphPos.clear();
+}
+
+int QSGD3D12GlyphCache::glyphPadding() const
+{
+ return 1;
+}
+
+int QSGD3D12GlyphCache::maxTextureWidth() const
+{
+ return 16384;
+}
+
+int QSGD3D12GlyphCache::maxTextureHeight() const
+{
+ return 16384;
+}
+
+void QSGD3D12GlyphCache::useTexture()
+{
+ if (m_id)
+ m_engine->useTexture(m_id);
+}
+
+QSize QSGD3D12GlyphCache::currentSize() const
+{
+ return m_size;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12glyphcache_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12glyphcache_p.h
new file mode 100644
index 0000000000..88d3d36f33
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12glyphcache_p.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** 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 QSGD3D12GLYPHCACHE_P_H
+#define QSGD3D12GLYPHCACHE_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 <QtGui/private/qtextureglyphcache_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Engine;
+
+class QSGD3D12GlyphCache : public QTextureGlyphCache
+{
+public:
+ QSGD3D12GlyphCache(QSGD3D12Engine *engine, QFontEngine::GlyphFormat format, const QTransform &matrix);
+ ~QSGD3D12GlyphCache();
+
+ void createTextureData(int width, int height) override;
+ void resizeTextureData(int width, int height) override;
+ void beginFillTexture() override;
+ void fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition) override;
+ void endFillTexture() override;
+ int glyphPadding() const override;
+ int maxTextureWidth() const override;
+ int maxTextureHeight() const override;
+
+ void useTexture();
+ QSize currentSize() const;
+
+private:
+ QSGD3D12Engine *m_engine;
+ uint m_id = 0;
+ QVector<QImage> m_glyphImages;
+ QVector<QPoint> m_glyphPos;
+ QSize m_size;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12GLYPHCACHE_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12glyphnode.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12glyphnode.cpp
new file mode 100644
index 0000000000..e559739018
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12glyphnode.cpp
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12glyphnode_p.h"
+#include "qsgd3d12builtinmaterials_p.h"
+
+QT_BEGIN_NAMESPACE
+
+void QSGD3D12GlyphNode::setMaterialColor(const QColor &color)
+{
+ static_cast<QSGD3D12TextMaterial *>(m_material)->setColor(color);
+}
+
+void QSGD3D12GlyphNode::update()
+{
+ QRawFont font = m_glyphs.rawFont();
+ QMargins margins(0, 0, 0, 0);
+
+ if (m_style == QQuickText::Normal) {
+ // QSGBasicGlyphNode dtor will delete
+ m_material = new QSGD3D12TextMaterial(QSGD3D12TextMaterial::Normal, m_rc, font);
+ } else if (m_style == QQuickText::Outline) {
+ QSGD3D12TextMaterial *material = new QSGD3D12TextMaterial(QSGD3D12TextMaterial::Outlined,
+ m_rc, font, QFontEngine::Format_A8);
+ material->setStyleColor(m_styleColor);
+ m_material = material;
+ margins = QMargins(1, 1, 1, 1);
+ } else {
+ QSGD3D12TextMaterial *material = new QSGD3D12TextMaterial(QSGD3D12TextMaterial::Styled,
+ m_rc, font, QFontEngine::Format_A8);
+ if (m_style == QQuickText::Sunken) {
+ material->setStyleShift(QVector2D(0, -1));
+ margins.setTop(1);
+ } else if (m_style == QQuickText::Raised) {
+ material->setStyleShift(QVector2D(0, 1));
+ margins.setBottom(1);
+ }
+ material->setStyleColor(m_styleColor);
+ m_material = material;
+ }
+
+ QSGD3D12TextMaterial *textMaterial = static_cast<QSGD3D12TextMaterial *>(m_material);
+ textMaterial->setColor(m_color);
+
+ QRectF boundingRect;
+ textMaterial->populate(m_position, m_glyphs.glyphIndexes(), m_glyphs.positions(), geometry(),
+ &boundingRect, &m_baseLine, margins);
+ setBoundingRect(boundingRect);
+
+ setMaterial(m_material);
+ markDirty(DirtyGeometry);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12glyphnode_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12glyphnode_p.h
new file mode 100644
index 0000000000..d04a8e8777
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12glyphnode_p.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** 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 QSGD3D12GLYPHNODE_P_H
+#define QSGD3D12GLYPHNODE_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/qsgbasicglyphnode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12RenderContext;
+
+class QSGD3D12GlyphNode : public QSGBasicGlyphNode
+{
+public:
+ QSGD3D12GlyphNode(QSGD3D12RenderContext *rc) : m_rc(rc) { }
+
+ void setMaterialColor(const QColor &color) override;
+ void update() override;
+
+private:
+ QSGD3D12RenderContext *m_rc;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12GLYPHNODE_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode.cpp
new file mode 100644
index 0000000000..aa163cacbf
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode.cpp
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12internalimagenode_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGD3D12InternalImageNode::QSGD3D12InternalImageNode()
+{
+ setMaterial(&m_material);
+}
+
+void QSGD3D12InternalImageNode::setFiltering(QSGTexture::Filtering filtering)
+{
+ if (m_material.filtering() == filtering)
+ return;
+
+ m_material.setFiltering(filtering);
+ m_smoothMaterial.setFiltering(filtering);
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12InternalImageNode::setMipmapFiltering(QSGTexture::Filtering filtering)
+{
+ if (m_material.mipmapFiltering() == filtering)
+ return;
+
+ m_material.setMipmapFiltering(filtering);
+ m_smoothMaterial.setMipmapFiltering(filtering);
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12InternalImageNode::setVerticalWrapMode(QSGTexture::WrapMode wrapMode)
+{
+ if (m_material.verticalWrapMode() == wrapMode)
+ return;
+
+ m_material.setVerticalWrapMode(wrapMode);
+ m_smoothMaterial.setVerticalWrapMode(wrapMode);
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12InternalImageNode::setHorizontalWrapMode(QSGTexture::WrapMode wrapMode)
+{
+ if (m_material.horizontalWrapMode() == wrapMode)
+ return;
+
+ m_material.setHorizontalWrapMode(wrapMode);
+ m_smoothMaterial.setHorizontalWrapMode(wrapMode);
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12InternalImageNode::updateMaterialAntialiasing()
+{
+ if (m_antialiasing)
+ setMaterial(&m_smoothMaterial);
+ else
+ setMaterial(&m_material);
+}
+
+void QSGD3D12InternalImageNode::setMaterialTexture(QSGTexture *texture)
+{
+ m_material.setTexture(texture);
+ m_smoothMaterial.setTexture(texture);
+}
+
+QSGTexture *QSGD3D12InternalImageNode::materialTexture() const
+{
+ return m_material.texture();
+}
+
+bool QSGD3D12InternalImageNode::updateMaterialBlending()
+{
+ const bool alpha = m_material.flags() & QSGMaterial::Blending;
+ if (materialTexture() && alpha != materialTexture()->hasAlphaChannel()) {
+ m_material.setFlag(QSGMaterial::Blending, !alpha);
+ return true;
+ }
+ return false;
+}
+
+bool QSGD3D12InternalImageNode::supportsWrap(const QSize &) const
+{
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode_p.h
new file mode 100644
index 0000000000..26284740ee
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12internalimagenode_p.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** 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 QSGD3D12INTERNALIMAGENODE_P_H
+#define QSGD3D12INTERNALIMAGENODE_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/qsgbasicinternalimagenode_p.h>
+#include "qsgd3d12builtinmaterials_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12InternalImageNode : public QSGBasicInternalImageNode
+{
+public:
+ QSGD3D12InternalImageNode();
+
+ void setMipmapFiltering(QSGTexture::Filtering filtering) override;
+ void setFiltering(QSGTexture::Filtering filtering) override;
+ void setHorizontalWrapMode(QSGTexture::WrapMode wrapMode) override;
+ void setVerticalWrapMode(QSGTexture::WrapMode wrapMode) override;
+
+ void updateMaterialAntialiasing() override;
+ void setMaterialTexture(QSGTexture *texture) override;
+ QSGTexture *materialTexture() const override;
+ bool updateMaterialBlending() override;
+ bool supportsWrap(const QSize &size) const override;
+
+private:
+ QSGD3D12TextureMaterial m_material;
+ QSGD3D12SmoothTextureMaterial m_smoothMaterial;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12INTERNALIMAGENODE_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode.cpp
new file mode 100644
index 0000000000..2d9c5b55d1
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode.cpp
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12internalrectanglenode_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGD3D12InternalRectangleNode::QSGD3D12InternalRectangleNode()
+{
+ setMaterial(&m_material);
+}
+
+void QSGD3D12InternalRectangleNode::updateMaterialAntialiasing()
+{
+ if (m_antialiasing)
+ setMaterial(&m_smoothMaterial);
+ else
+ setMaterial(&m_material);
+}
+
+void QSGD3D12InternalRectangleNode::updateMaterialBlending(QSGNode::DirtyState *state)
+{
+ // smoothed material is always blended, so no change in material state
+ if (material() == &m_material) {
+ bool wasBlending = (m_material.flags() & QSGMaterial::Blending);
+ bool isBlending = (m_gradient_stops.size() > 0 && !m_gradient_is_opaque)
+ || (m_color.alpha() < 255 && m_color.alpha() != 0)
+ || (m_pen_width > 0 && m_border_color.alpha() < 255);
+ if (wasBlending != isBlending) {
+ m_material.setFlag(QSGMaterial::Blending, isBlending);
+ *state |= QSGNode::DirtyMaterial;
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode_p.h
new file mode 100644
index 0000000000..2fc3c69285
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12internalrectanglenode_p.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** 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 QSGD3D12INTERNALRECTANGLENODE_P_H
+#define QSGD3D12INTERNALRECTANGLENODE_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/qsgbasicinternalrectanglenode_p.h>
+#include "qsgd3d12builtinmaterials_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12InternalRectangleNode : public QSGBasicInternalRectangleNode
+{
+public:
+ QSGD3D12InternalRectangleNode();
+
+private:
+ void updateMaterialAntialiasing() override;
+ void updateMaterialBlending(QSGNode::DirtyState *state) override;
+
+ QSGD3D12VertexColorMaterial m_material;
+ QSGD3D12SmoothColorMaterial m_smoothMaterial;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12INTERNALRECTANGLENODE_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12layer.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12layer.cpp
new file mode 100644
index 0000000000..faa6f7566a
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12layer.cpp
@@ -0,0 +1,363 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12layer_p.h"
+#include "qsgd3d12rendercontext_p.h"
+#include "qsgd3d12engine_p.h"
+#include "qsgd3d12renderer_p.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)
+
+QSGD3D12Layer::QSGD3D12Layer(QSGD3D12RenderContext *rc)
+ : m_rc(rc)
+{
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("new layer %p", this);
+}
+
+QSGD3D12Layer::~QSGD3D12Layer()
+{
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("destroying layer %p", this);
+
+ cleanup();
+}
+
+// QSGTexture
+
+int QSGD3D12Layer::textureId() const
+{
+ return m_rt; // not a texture id per se but will do
+}
+
+QSize QSGD3D12Layer::textureSize() const
+{
+ return m_size;
+}
+
+bool QSGD3D12Layer::hasAlphaChannel() const
+{
+ return true;
+}
+
+bool QSGD3D12Layer::hasMipmaps() const
+{
+ // mipmapped layers are not supported for now
+ return false;
+}
+
+QRectF QSGD3D12Layer::normalizedTextureSubRect() const
+{
+ return QRectF(m_mirrorHorizontal ? 1 : 0,
+ m_mirrorVertical ? 0 : 1,
+ m_mirrorHorizontal ? -1 : 1,
+ m_mirrorVertical ? 1 : -1);
+}
+
+void QSGD3D12Layer::bind()
+{
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("layer %p bind rt=%u", this, m_rt);
+
+ QSGD3D12Engine *engine = m_rc->engine();
+ Q_ASSERT(m_rt);
+
+#ifndef QT_NO_DEBUG
+ // Should not use the color buffer as a texture while it is the current render target.
+ if (!m_recursive && engine->activeRenderTarget() == m_rt && engine->windowSamples() == 1)
+ qWarning("ShaderEffectSource: \'recursive\' must be set to true when rendering recursively.");
+#endif
+
+ engine->useRenderTargetAsTexture(m_rt);
+}
+
+// QSGDynamicTexture
+
+bool QSGD3D12Layer::updateTexture()
+{
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("layer %p updateTexture", this);
+
+ const bool doUpdate = (m_live || m_updateContentPending) && m_dirtyTexture;
+
+ if (doUpdate)
+ updateContent();
+
+ if (m_updateContentPending) {
+ m_updateContentPending = false;
+ emit scheduledUpdateCompleted();
+ }
+
+ return doUpdate;
+}
+
+// QSGLayer
+
+void QSGD3D12Layer::setItem(QSGNode *item)
+{
+ if (m_item == item)
+ return;
+
+ if (m_live && !item)
+ resetRenderTarget();
+
+ m_item = item;
+ markDirtyTexture();
+}
+
+void QSGD3D12Layer::setRect(const QRectF &rect)
+{
+ if (m_rect == rect)
+ return;
+
+ m_rect = rect;
+ markDirtyTexture();
+}
+
+void QSGD3D12Layer::setSize(const QSize &size)
+{
+ if (m_size == size)
+ return;
+
+ if (m_live && size.isNull())
+ resetRenderTarget();
+
+ m_size = size;
+ markDirtyTexture();
+}
+
+void QSGD3D12Layer::scheduleUpdate()
+{
+ if (m_updateContentPending)
+ return;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("layer %p scheduleUpdate", this);
+
+ m_updateContentPending = true;
+
+ if (m_dirtyTexture)
+ emit updateRequested();
+}
+
+QImage QSGD3D12Layer::toImage() const
+{
+ return m_rc->engine()->executeAndWaitReadbackRenderTarget(m_rt);
+}
+
+void QSGD3D12Layer::setLive(bool live)
+{
+ if (m_live == live)
+ return;
+
+ if (live && (!m_item || m_size.isNull()))
+ resetRenderTarget();
+
+ m_live = live;
+ markDirtyTexture();
+}
+
+void QSGD3D12Layer::setRecursive(bool recursive)
+{
+ m_recursive = recursive;
+}
+
+void QSGD3D12Layer::setFormat(uint format)
+{
+ Q_UNUSED(format);
+}
+
+void QSGD3D12Layer::setHasMipmaps(bool mipmap)
+{
+ // mipmapped layers are not supported for now
+ Q_UNUSED(mipmap);
+}
+
+void QSGD3D12Layer::setDevicePixelRatio(qreal ratio)
+{
+ m_dpr = ratio;
+}
+
+void QSGD3D12Layer::setMirrorHorizontal(bool mirror)
+{
+ m_mirrorHorizontal = mirror;
+}
+
+void QSGD3D12Layer::setMirrorVertical(bool mirror)
+{
+ m_mirrorVertical = mirror;
+}
+
+void QSGD3D12Layer::markDirtyTexture()
+{
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("layer %p markDirtyTexture", this);
+
+ m_dirtyTexture = true;
+
+ if (m_live || m_updateContentPending)
+ emit updateRequested();
+}
+
+void QSGD3D12Layer::invalidated()
+{
+ cleanup();
+}
+
+void QSGD3D12Layer::cleanup()
+{
+ if (!m_renderer && !m_rt)
+ return;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("layer %p cleanup renderer=%p rt=%u", this, m_renderer, m_rt);
+
+ delete m_renderer;
+ m_renderer = nullptr;
+
+ resetRenderTarget();
+}
+
+void QSGD3D12Layer::resetRenderTarget()
+{
+ if (!m_rt)
+ return;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("layer %p resetRenderTarget rt=%u", this, m_rt);
+
+ m_rc->engine()->releaseRenderTarget(m_rt);
+ m_rt = 0;
+
+ if (m_secondaryRT) {
+ m_rc->engine()->releaseRenderTarget(m_secondaryRT);
+ m_secondaryRT = 0;
+ }
+}
+
+void QSGD3D12Layer::updateContent()
+{
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("layer %p updateContent", this);
+
+ if (!m_item || m_size.isNull()) {
+ resetRenderTarget();
+ m_dirtyTexture = false;
+ return;
+ }
+
+ QSGNode *root = m_item;
+ while (root->firstChild() && root->type() != QSGNode::RootNodeType)
+ root = root->firstChild();
+
+ if (root->type() != QSGNode::RootNodeType)
+ return;
+
+ if (!m_renderer) {
+ m_renderer = m_rc->createRenderer();
+ static_cast<QSGD3D12Renderer *>(m_renderer)->turnToLayerRenderer();
+ connect(m_renderer, &QSGRenderer::sceneGraphChanged, this, &QSGD3D12Layer::markDirtyTexture);
+ }
+
+ m_renderer->setDevicePixelRatio(m_dpr);
+ m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
+
+ QSGD3D12Engine *engine = m_rc->engine();
+ const uint sampleCount = engine->windowSamples();
+ const QVector4D clearColor;
+
+ if (!m_rt || m_rtSize != m_size) {
+ if (m_rt)
+ resetRenderTarget();
+
+ m_rt = engine->genRenderTarget();
+ m_rtSize = m_size;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("new render target for layer %p, size=%dx%d, samples=%d",
+ this, m_size.width(), m_size.height(), sampleCount);
+
+ engine->createRenderTarget(m_rt, m_rtSize, clearColor, sampleCount);
+
+ // For multisampling the resolving via an extra non-ms color buffer is
+ // handled internally in the engine, no need to worry about it here.
+ }
+
+ if (m_recursive && !m_secondaryRT && sampleCount == 1) {
+ m_secondaryRT = engine->genRenderTarget();
+ engine->createRenderTarget(m_secondaryRT, m_rtSize, clearColor, sampleCount);
+ }
+
+ m_dirtyTexture = false;
+
+ m_renderer->setDeviceRect(m_size);
+ m_renderer->setViewportRect(m_size);
+
+ // Note that the handling of vertical mirroring differs from OpenGL here
+ // due to y running top-bottom with D3D as opposed to bottom-top with GL.
+ // The common parts of Quick follow OpenGL so vertical mirroring is
+ // typically enabled.
+ QRectF mirrored(m_mirrorHorizontal ? m_rect.right() : m_rect.left(),
+ m_mirrorVertical ? m_rect.top() : m_rect.bottom(),
+ m_mirrorHorizontal ? -m_rect.width() : m_rect.width(),
+ m_mirrorVertical ? m_rect.height() : -m_rect.height());
+
+ m_renderer->setProjectionMatrixToRect(mirrored);
+ m_renderer->setClearColor(Qt::transparent);
+
+ if (!m_recursive || sampleCount > 1) {
+ m_renderer->renderScene(m_rt);
+ } else {
+ m_renderer->renderScene(m_secondaryRT);
+ qSwap(m_rt, m_secondaryRT);
+ }
+
+ if (m_recursive)
+ markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12layer_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12layer_p.h
new file mode 100644
index 0000000000..f1ab580a84
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12layer_p.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** 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 QSGD3D12LAYER_P_H
+#define QSGD3D12LAYER_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>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12RenderContext;
+
+class QSGD3D12Layer : public QSGLayer
+{
+ Q_OBJECT
+
+public:
+ QSGD3D12Layer(QSGD3D12RenderContext *rc);
+ ~QSGD3D12Layer();
+
+ int textureId() const override;
+ QSize textureSize() const override;
+ bool hasAlphaChannel() const override;
+ bool hasMipmaps() const override;
+ QRectF normalizedTextureSubRect() const override;
+ void bind() override;
+
+ bool updateTexture() override;
+
+ void setItem(QSGNode *item) override;
+ void setRect(const QRectF &rect) override;
+ void setSize(const QSize &size) override;
+ void scheduleUpdate() override;
+ QImage toImage() const override;
+ void setLive(bool live) override;
+ void setRecursive(bool recursive) override;
+ void setFormat(uint format) override;
+ void setHasMipmaps(bool mipmap) override;
+ void setDevicePixelRatio(qreal ratio) override;
+ void setMirrorHorizontal(bool mirror) override;
+ void setMirrorVertical(bool mirror) override;
+
+public Q_SLOTS:
+ void markDirtyTexture() override;
+ void invalidated() override;
+
+private:
+ void cleanup();
+ void resetRenderTarget();
+ void updateContent();
+
+ QSGD3D12RenderContext *m_rc;
+ uint m_rt = 0;
+ uint m_secondaryRT = 0;
+ QSize m_rtSize;
+ QSize m_size;
+ QRectF m_rect;
+ QSGNode *m_item = nullptr;
+ QSGRenderer *m_renderer = nullptr;
+ float m_dpr = 1;
+ bool m_mirrorHorizontal = false;
+ bool m_mirrorVertical = true;
+ bool m_live = true;
+ bool m_recursive = false;
+ bool m_dirtyTexture = true;
+ bool m_updateContentPending = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12LAYER_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12material.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12material.cpp
new file mode 100644
index 0000000000..1b638106ee
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12material.cpp
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12material_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGMaterialShader *QSGD3D12Material::createShader() const
+{
+ return nullptr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12material_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12material_p.h
new file mode 100644
index 0000000000..65d53600c3
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12material_p.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** 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 QSGD3D12MATERIAL_P_H
+#define QSGD3D12MATERIAL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/qsgmaterial.h>
+#include "qsgd3d12engine_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGRenderer;
+
+// The D3D renderer works with QSGD3D12Material as the "base" class since
+// QSGMaterial and its GL program related bits are not suitable. Also, there is
+// no split like with QSGMaterialShader.
+
+typedef QSGMaterialShader::RenderState QSGD3D12MaterialRenderState;
+
+class QSGD3D12Material : public QSGMaterial
+{
+public:
+ struct ExtraState {
+ QVector4D blendFactor;
+ };
+
+ enum UpdateResult {
+ UpdatedShaders = 0x0001,
+ UpdatedConstantBuffer = 0x0002,
+ UpdatedBlendFactor = 0x0004
+ };
+ Q_DECLARE_FLAGS(UpdateResults, UpdateResult)
+
+ virtual int constantBufferSize() const = 0;
+ virtual void preparePipeline(QSGD3D12PipelineState *pipelineState) = 0;
+ virtual UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) = 0;
+
+private:
+ QSGMaterialShader *createShader() const override; // dummy, QSGMaterialShader is too GL dependent
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSGD3D12Material::UpdateResults)
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12MATERIAL_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12painternode.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12painternode.cpp
new file mode 100644
index 0000000000..b22c42f2e5
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12painternode.cpp
@@ -0,0 +1,255 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12painternode_p.h"
+#include "qsgd3d12rendercontext_p.h"
+#include "qsgd3d12engine_p.h"
+#include <private/qquickitem_p.h>
+#include <qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGD3D12PainterTexture::QSGD3D12PainterTexture(QSGD3D12Engine *engine)
+ : QSGD3D12Texture(engine)
+{
+}
+
+void QSGD3D12PainterTexture::bind()
+{
+ if (m_image.isNull()) {
+ if (!m_id) {
+ m_id = m_engine->genTexture();
+ m_engine->createTexture(m_id, QSize(16, 16), QImage::Format_RGB32, 0);
+ }
+ } else if (m_image.size() != lastSize) {
+ lastSize = m_image.size();
+ if (m_id)
+ m_engine->releaseTexture(m_id);
+ m_id = m_engine->genTexture();
+ m_engine->createTexture(m_id, m_image.size(), m_image.format(), QSGD3D12Engine::TextureWithAlpha);
+ m_engine->queueTextureUpload(m_id, m_image);
+ } else if (!dirty.isEmpty()) {
+ const int bpl = m_image.bytesPerLine();
+ const uchar *p = m_image.constBits() + dirty.y() * bpl + dirty.x() * 4;
+ QImage subImg(p, dirty.width(), dirty.height(), bpl, QImage::Format_ARGB32_Premultiplied);
+ m_engine->queueTextureUpload(m_id, subImg, dirty.topLeft());
+ }
+
+ dirty = QRect();
+
+ m_engine->useTexture(m_id);
+}
+
+QSGD3D12PainterNode::QSGD3D12PainterNode(QQuickPaintedItem *item)
+ : m_item(item),
+ m_engine(static_cast<QSGD3D12RenderContext *>(QQuickItemPrivate::get(item)->sceneGraphRenderContext())->engine()),
+ m_texture(new QSGD3D12PainterTexture(m_engine)),
+ m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4),
+ m_dirtyGeometry(false),
+ m_dirtyContents(false)
+{
+ setGeometry(&m_geometry);
+ m_material.setTexture(m_texture);
+ setMaterial(&m_material);
+}
+
+QSGD3D12PainterNode::~QSGD3D12PainterNode()
+{
+ delete m_texture;
+}
+
+void QSGD3D12PainterNode::setPreferredRenderTarget(QQuickPaintedItem::RenderTarget)
+{
+ // always QImage-based
+}
+
+void QSGD3D12PainterNode::setSize(const QSize &size)
+{
+ if (m_size == size)
+ return;
+
+ m_size = size;
+ m_dirtyGeometry = true;
+}
+
+void QSGD3D12PainterNode::setDirty(const QRect &dirtyRect)
+{
+ m_dirtyRect = dirtyRect;
+ m_dirtyContents = true;
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12PainterNode::setOpaquePainting(bool)
+{
+ // ignored
+}
+
+void QSGD3D12PainterNode::setLinearFiltering(bool linearFiltering)
+{
+ m_material.setFiltering(linearFiltering ? QSGTexture::Linear : QSGTexture::Nearest);
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12PainterNode::setMipmapping(bool)
+{
+ // ### not yet
+}
+
+void QSGD3D12PainterNode::setSmoothPainting(bool s)
+{
+ if (m_smoothPainting == s)
+ return;
+
+ m_smoothPainting = s;
+ m_dirtyContents = true;
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12PainterNode::setFillColor(const QColor &c)
+{
+ if (m_fillColor == c)
+ return;
+
+ m_fillColor = c;
+ m_dirtyContents = true;
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12PainterNode::setContentsScale(qreal s)
+{
+ if (m_contentsScale == s)
+ return;
+
+ m_contentsScale = s;
+ m_dirtyContents = true;
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12PainterNode::setFastFBOResizing(bool)
+{
+ // nope
+}
+
+void QSGD3D12PainterNode::setTextureSize(const QSize &size)
+{
+ if (m_textureSize == size)
+ return;
+
+ m_textureSize = size;
+ m_dirtyGeometry = true;
+}
+
+QImage QSGD3D12PainterNode::toImage() const
+{
+ return *m_texture->image();
+}
+
+void QSGD3D12PainterNode::update()
+{
+ if (m_dirtyGeometry) {
+ m_dirtyGeometry = false;
+ QRectF src(0, 0, 1, 1);
+ QRectF dst(QPointF(0, 0), m_size);
+ QSGGeometry::updateTexturedRectGeometry(&m_geometry, dst, src);
+ markDirty(DirtyGeometry);
+ }
+
+ QImage *img = m_texture->image();
+ if (img->size() != m_textureSize) {
+ *img = QImage(m_textureSize, QImage::Format_ARGB32_Premultiplied);
+ img->fill(Qt::transparent);
+ m_dirtyContents = true;
+ }
+
+ if (m_dirtyContents) {
+ m_dirtyContents = false;
+ if (!img->isNull()) {
+ QRect dirtyRect = m_dirtyRect.isNull() ? QRect(QPoint(0, 0), m_size) : m_dirtyRect;
+ QPainter painter;
+ painter.begin(img);
+ if (m_smoothPainting)
+ painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
+
+ QRect clipRect;
+ QRect dirtyTextureRect;
+
+ if (m_contentsScale == 1) {
+ float scaleX = m_textureSize.width() / (float) m_size.width();
+ float scaleY = m_textureSize.height() / (float) m_size.height();
+ painter.scale(scaleX, scaleY);
+ clipRect = dirtyRect;
+ dirtyTextureRect = QRectF(dirtyRect.x() * scaleX,
+ dirtyRect.y() * scaleY,
+ dirtyRect.width() * scaleX,
+ dirtyRect.height() * scaleY).toAlignedRect();
+ } else {
+ painter.scale(m_contentsScale, m_contentsScale);
+ QRect sclip(qFloor(dirtyRect.x() / m_contentsScale),
+ qFloor(dirtyRect.y() / m_contentsScale),
+ qCeil(dirtyRect.width() / m_contentsScale + dirtyRect.x() / m_contentsScale
+ - qFloor(dirtyRect.x() / m_contentsScale)),
+ qCeil(dirtyRect.height() / m_contentsScale + dirtyRect.y() / m_contentsScale
+ - qFloor(dirtyRect.y() / m_contentsScale)));
+ clipRect = sclip;
+ dirtyTextureRect = dirtyRect;
+ }
+
+ // only clip if we were originally updating only a subrect
+ if (!m_dirtyRect.isNull())
+ painter.setClipRect(clipRect);
+
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ painter.fillRect(clipRect, m_fillColor);
+ painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+
+ m_item->paint(&painter);
+ painter.end();
+
+ m_texture->dirty = dirtyTextureRect;
+ }
+ m_dirtyRect = QRect();
+ }
+}
+
+QSGTexture *QSGD3D12PainterNode::texture() const
+{
+ return m_texture;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12painternode_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12painternode_p.h
new file mode 100644
index 0000000000..7f4842b3a6
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12painternode_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** 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 QSGD3D12PAINTERNODE_P_H
+#define QSGD3D12PAINTERNODE_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 "qsgd3d12texture_p.h"
+#include "qsgd3d12builtinmaterials_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Engine;
+
+class QSGD3D12PainterTexture : public QSGD3D12Texture
+{
+public:
+ QSGD3D12PainterTexture(QSGD3D12Engine *engine);
+
+ void bind() override;
+ bool hasAlphaChannel() const override { return true; }
+
+ QImage *image() { return &m_image; }
+
+ QRect dirty;
+
+private:
+ QSize lastSize;
+};
+
+class QSGD3D12PainterNode : public QSGPainterNode
+{
+public:
+ QSGD3D12PainterNode(QQuickPaintedItem *item);
+ ~QSGD3D12PainterNode();
+
+ void setPreferredRenderTarget(QQuickPaintedItem::RenderTarget target) override;
+ void setSize(const QSize &size) override;
+ void setDirty(const QRect &dirtyRect = QRect()) override;
+ void setOpaquePainting(bool opaque) override;
+ void setLinearFiltering(bool linearFiltering) override;
+ void setMipmapping(bool mipmapping) override;
+ void setSmoothPainting(bool s) override;
+ void setFillColor(const QColor &c) override;
+ void setContentsScale(qreal s) override;
+ void setFastFBOResizing(bool dynamic) override;
+ void setTextureSize(const QSize &size) override;
+
+ QImage toImage() const override;
+ void update() override;
+ QSGTexture *texture() const override;
+
+private:
+ QQuickPaintedItem *m_item;
+ QSGD3D12Engine *m_engine;
+ QSGD3D12PainterTexture *m_texture;
+ QSize m_size;
+ QSize m_textureSize;
+ float m_contentsScale = 1;
+ bool m_smoothPainting = false;
+ QColor m_fillColor = Qt::transparent;
+ QRect m_dirtyRect;
+
+ QSGGeometry m_geometry;
+ QSGD3D12TextureMaterial m_material;
+
+ uint m_dirtyGeometry : 1;
+ uint m_dirtyContents : 1;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12PAINTERNODE_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12publicnodes.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12publicnodes.cpp
new file mode 100644
index 0000000000..783caa280f
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12publicnodes.cpp
@@ -0,0 +1,254 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12publicnodes_p.h"
+
+// for rebuildGeometry
+#include <private/qsgdefaultninepatchnode_p.h>
+#include <private/qsgdefaultimagenode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGD3D12RectangleNode::QSGD3D12RectangleNode()
+ : m_geometry(QSGGeometry::defaultAttributes_Point2D(), 4)
+{
+ QSGGeometry::updateRectGeometry(&m_geometry, QRectF());
+ setMaterial(&m_material);
+ setGeometry(&m_geometry);
+#ifdef QSG_RUNTIME_DESCRIPTION
+ qsgnode_set_description(this, QLatin1String("rectangle"));
+#endif
+}
+
+void QSGD3D12RectangleNode::setRect(const QRectF &rect)
+{
+ QSGGeometry::updateRectGeometry(&m_geometry, rect);
+ markDirty(QSGNode::DirtyGeometry);
+}
+
+QRectF QSGD3D12RectangleNode::rect() const
+{
+ const QSGGeometry::Point2D *pts = m_geometry.vertexDataAsPoint2D();
+ return QRectF(pts[0].x,
+ pts[0].y,
+ pts[3].x - pts[0].x,
+ pts[3].y - pts[0].y);
+}
+
+void QSGD3D12RectangleNode::setColor(const QColor &color)
+{
+ if (color != m_material.color()) {
+ m_material.setColor(color);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+}
+
+QColor QSGD3D12RectangleNode::color() const
+{
+ return m_material.color();
+}
+
+QSGD3D12ImageNode::QSGD3D12ImageNode()
+ : m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4),
+ m_texCoordMode(QSGD3D12ImageNode::NoTransform),
+ m_isAtlasTexture(false),
+ m_ownsTexture(false)
+{
+ setGeometry(&m_geometry);
+ setMaterial(&m_material);
+ m_material.setMipmapFiltering(QSGTexture::None);
+#ifdef QSG_RUNTIME_DESCRIPTION
+ qsgnode_set_description(this, QLatin1String("image"));
+#endif
+}
+
+QSGD3D12ImageNode::~QSGD3D12ImageNode()
+{
+ if (m_ownsTexture)
+ delete m_material.texture();
+}
+
+void QSGD3D12ImageNode::setFiltering(QSGTexture::Filtering filtering)
+{
+ if (m_material.filtering() == filtering)
+ return;
+
+ m_material.setFiltering(filtering);
+ markDirty(DirtyMaterial);
+}
+
+QSGTexture::Filtering QSGD3D12ImageNode::filtering() const
+{
+ return m_material.filtering();
+}
+
+void QSGD3D12ImageNode::setMipmapFiltering(QSGTexture::Filtering filtering)
+{
+ if (m_material.mipmapFiltering() == filtering)
+ return;
+
+ m_material.setMipmapFiltering(filtering);
+ markDirty(DirtyMaterial);
+}
+
+QSGTexture::Filtering QSGD3D12ImageNode::mipmapFiltering() const
+{
+ return m_material.mipmapFiltering();
+}
+
+void QSGD3D12ImageNode::setRect(const QRectF &r)
+{
+ if (m_rect == r)
+ return;
+
+ m_rect = r;
+ QSGDefaultImageNode::rebuildGeometry(&m_geometry, texture(), m_rect, m_sourceRect, m_texCoordMode);
+ markDirty(DirtyGeometry);
+}
+
+QRectF QSGD3D12ImageNode::rect() const
+{
+ return m_rect;
+}
+
+void QSGD3D12ImageNode::setSourceRect(const QRectF &r)
+{
+ if (m_sourceRect == r)
+ return;
+
+ m_sourceRect = r;
+ QSGDefaultImageNode::rebuildGeometry(&m_geometry, texture(), m_rect, m_sourceRect, m_texCoordMode);
+ markDirty(DirtyGeometry);
+}
+
+QRectF QSGD3D12ImageNode::sourceRect() const
+{
+ return m_sourceRect;
+}
+
+void QSGD3D12ImageNode::setTexture(QSGTexture *texture)
+{
+ Q_ASSERT(texture);
+
+ if (m_ownsTexture)
+ delete m_material.texture();
+
+ m_material.setTexture(texture);
+ QSGDefaultImageNode::rebuildGeometry(&m_geometry, texture, m_rect, m_sourceRect, m_texCoordMode);
+
+ DirtyState dirty = DirtyMaterial;
+ const bool wasAtlas = m_isAtlasTexture;
+ m_isAtlasTexture = texture->isAtlasTexture();
+ if (wasAtlas || m_isAtlasTexture)
+ dirty |= DirtyGeometry;
+
+ markDirty(dirty);
+}
+
+QSGTexture *QSGD3D12ImageNode::texture() const
+{
+ return m_material.texture();
+}
+
+void QSGD3D12ImageNode::setTextureCoordinatesTransform(TextureCoordinatesTransformMode mode)
+{
+ if (m_texCoordMode == mode)
+ return;
+
+ m_texCoordMode = mode;
+ QSGDefaultImageNode::rebuildGeometry(&m_geometry, texture(), m_rect, m_sourceRect, m_texCoordMode);
+ markDirty(DirtyMaterial);
+}
+
+QSGD3D12ImageNode::TextureCoordinatesTransformMode QSGD3D12ImageNode::textureCoordinatesTransform() const
+{
+ return m_texCoordMode;
+}
+
+void QSGD3D12ImageNode::setOwnsTexture(bool owns)
+{
+ m_ownsTexture = owns;
+}
+
+bool QSGD3D12ImageNode::ownsTexture() const
+{
+ return m_ownsTexture;
+}
+
+QSGD3D12NinePatchNode::QSGD3D12NinePatchNode()
+ : m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4)
+{
+ m_geometry.setDrawingMode(QSGGeometry::DrawTriangleStrip);
+ setGeometry(&m_geometry);
+ setMaterial(&m_material);
+}
+
+QSGD3D12NinePatchNode::~QSGD3D12NinePatchNode()
+{
+ delete m_material.texture();
+}
+
+void QSGD3D12NinePatchNode::setTexture(QSGTexture *texture)
+{
+ delete m_material.texture();
+ m_material.setTexture(texture);
+}
+
+void QSGD3D12NinePatchNode::setBounds(const QRectF &bounds)
+{
+ m_bounds = bounds;
+}
+
+void QSGD3D12NinePatchNode::setDevicePixelRatio(qreal ratio)
+{
+ m_devicePixelRatio = ratio;
+}
+
+void QSGD3D12NinePatchNode::setPadding(qreal left, qreal top, qreal right, qreal bottom)
+{
+ m_padding = QVector4D(left, top, right, bottom);
+}
+
+void QSGD3D12NinePatchNode::update()
+{
+ QSGDefaultNinePatchNode::rebuildGeometry(m_material.texture(), &m_geometry, m_padding, m_bounds, m_devicePixelRatio);
+ markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12publicnodes_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12publicnodes_p.h
new file mode 100644
index 0000000000..6150083aaf
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12publicnodes_p.h
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** 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 QSGD3D12PUBLICNODES_P_H
+#define QSGD3D12PUBLICNODES_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/qsgrectanglenode.h>
+#include <QtQuick/qsgimagenode.h>
+#include <QtQuick/qsgninepatchnode.h>
+#include "qsgd3d12builtinmaterials_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12RectangleNode : public QSGRectangleNode
+{
+public:
+ QSGD3D12RectangleNode();
+
+ void setRect(const QRectF &rect) override;
+ QRectF rect() const override;
+
+ void setColor(const QColor &color) override;
+ QColor color() const override;
+
+private:
+ QSGGeometry m_geometry;
+ QSGD3D12FlatColorMaterial m_material;
+};
+
+class QSGD3D12ImageNode : public QSGImageNode
+{
+public:
+ QSGD3D12ImageNode();
+ ~QSGD3D12ImageNode();
+
+ void setRect(const QRectF &rect) override;
+ QRectF rect() const override;
+
+ void setSourceRect(const QRectF &r) override;
+ QRectF sourceRect() const override;
+
+ void setTexture(QSGTexture *texture) override;
+ QSGTexture *texture() const override;
+
+ void setFiltering(QSGTexture::Filtering filtering) override;
+ QSGTexture::Filtering filtering() const override;
+
+ void setMipmapFiltering(QSGTexture::Filtering filtering) override;
+ QSGTexture::Filtering mipmapFiltering() const override;
+
+ void setTextureCoordinatesTransform(TextureCoordinatesTransformMode mode) override;
+ TextureCoordinatesTransformMode textureCoordinatesTransform() const override;
+
+ void setOwnsTexture(bool owns) override;
+ bool ownsTexture() const override;
+
+private:
+ QSGGeometry m_geometry;
+ QSGD3D12TextureMaterial m_material;
+ QRectF m_rect;
+ QRectF m_sourceRect;
+ TextureCoordinatesTransformMode m_texCoordMode;
+ uint m_isAtlasTexture : 1;
+ uint m_ownsTexture : 1;
+};
+
+class QSGD3D12NinePatchNode : public QSGNinePatchNode
+{
+public:
+ QSGD3D12NinePatchNode();
+ ~QSGD3D12NinePatchNode();
+
+ void setTexture(QSGTexture *texture) override;
+ void setBounds(const QRectF &bounds) override;
+ void setDevicePixelRatio(qreal ratio) override;
+ void setPadding(qreal left, qreal top, qreal right, qreal bottom) override;
+ void update() override;
+
+private:
+ QSGGeometry m_geometry;
+ QSGD3D12TextureMaterial m_material;
+ QRectF m_bounds;
+ qreal m_devicePixelRatio;
+ QVector4D m_padding;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12rendercontext.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12rendercontext.cpp
new file mode 100644
index 0000000000..4ee4656e63
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12rendercontext.cpp
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12rendercontext_p.h"
+#include "qsgd3d12renderer_p.h"
+#include "qsgd3d12texture_p.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)
+
+QSGD3D12RenderContext::QSGD3D12RenderContext(QSGContext *ctx)
+ : QSGRenderContext(ctx)
+{
+}
+
+bool QSGD3D12RenderContext::isValid() const
+{
+ // The render thread sets an engine when it starts up and resets when it
+ // quits. The rc is initialized and functional between those two points,
+ // regardless of any calls to invalidate(). See setEngine().
+ return m_engine != nullptr;
+}
+
+void QSGD3D12RenderContext::initialize(void *)
+{
+ if (m_initialized)
+ return;
+
+ m_initialized = true;
+ emit initialized();
+}
+
+void QSGD3D12RenderContext::invalidate()
+{
+ if (!m_initialized)
+ return;
+
+ m_initialized = false;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug("rendercontext invalidate engine %p, %d/%d/%d", m_engine,
+ m_texturesToDelete.count(), m_textures.count(), m_fontEnginesToClean.count());
+
+ qDeleteAll(m_texturesToDelete);
+ m_texturesToDelete.clear();
+
+ qDeleteAll(m_textures);
+ m_textures.clear();
+
+ for (QSet<QFontEngine *>::const_iterator it = m_fontEnginesToClean.constBegin(),
+ end = m_fontEnginesToClean.constEnd(); it != end; ++it) {
+ (*it)->clearGlyphCache(m_engine);
+ if (!(*it)->ref.deref())
+ delete *it;
+ }
+ m_fontEnginesToClean.clear();
+
+ m_sg->renderContextInvalidated(this);
+ emit invalidated();
+}
+
+QSGTexture *QSGD3D12RenderContext::createTexture(const QImage &image, uint flags) const
+{
+ Q_ASSERT(m_engine);
+ QSGD3D12Texture *t = new QSGD3D12Texture(m_engine);
+ t->create(image, flags);
+ return t;
+}
+
+QSGRenderer *QSGD3D12RenderContext::createRenderer()
+{
+ return new QSGD3D12Renderer(this);
+}
+
+int QSGD3D12RenderContext::maxTextureSize() const
+{
+ return 16384; // D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION
+}
+
+void QSGD3D12RenderContext::renderNextFrame(QSGRenderer *renderer, uint fbo)
+{
+ static_cast<QSGD3D12Renderer *>(renderer)->renderScene(fbo);
+}
+
+void QSGD3D12RenderContext::setEngine(QSGD3D12Engine *engine)
+{
+ if (m_engine == engine)
+ return;
+
+ m_engine = engine;
+
+ if (m_engine)
+ initialize(nullptr);
+}
+
+QSGRendererInterface::GraphicsApi QSGD3D12RenderContext::graphicsApi() const
+{
+ return Direct3D12;
+}
+
+void *QSGD3D12RenderContext::getResource(QQuickWindow *window, Resource resource) const
+{
+ if (!m_engine) {
+ qWarning("getResource: No D3D12 engine available yet (window not exposed?)");
+ return nullptr;
+ }
+ // window can be ignored since the rendercontext and engine are both per window
+ return m_engine->getResource(window, resource);
+}
+
+QSGRendererInterface::ShaderType QSGD3D12RenderContext::shaderType() const
+{
+ return HLSL;
+}
+
+QSGRendererInterface::ShaderCompilationTypes QSGD3D12RenderContext::shaderCompilationType() const
+{
+ return RuntimeCompilation | OfflineCompilation;
+}
+
+QSGRendererInterface::ShaderSourceTypes QSGD3D12RenderContext::shaderSourceType() const
+{
+ return ShaderSourceString | ShaderSourceFile | ShaderByteCode;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12rendercontext_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12rendercontext_p.h
new file mode 100644
index 0000000000..35aca100f4
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12rendercontext_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** 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 QSGD3D12RENDERCONTEXT_P_H
+#define QSGD3D12RENDERCONTEXT_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/qsgcontext_p.h>
+#include <qsgrendererinterface.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Engine;
+
+class QSGD3D12RenderContext : public QSGRenderContext, public QSGRendererInterface
+{
+public:
+ QSGD3D12RenderContext(QSGContext *ctx);
+ bool isValid() const override;
+ void initialize(void *context) override;
+ void invalidate() override;
+ void renderNextFrame(QSGRenderer *renderer, uint fbo) override;
+ QSGTexture *createTexture(const QImage &image, uint flags) const override;
+ QSGRenderer *createRenderer() override;
+ int maxTextureSize() const override;
+
+ void setEngine(QSGD3D12Engine *engine);
+ QSGD3D12Engine *engine() { return m_engine; }
+
+ // QSGRendererInterface
+ GraphicsApi graphicsApi() const override;
+ void *getResource(QQuickWindow *window, Resource resource) const override;
+ ShaderType shaderType() const override;
+ ShaderCompilationTypes shaderCompilationType() const override;
+ ShaderSourceTypes shaderSourceType() const override;
+
+private:
+ QSGD3D12Engine *m_engine = nullptr;
+ bool m_initialized = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12RENDERCONTEXT_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12renderer.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12renderer.cpp
new file mode 100644
index 0000000000..c0f111ee83
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12renderer.cpp
@@ -0,0 +1,785 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12renderer_p.h"
+#include "qsgd3d12rendercontext_p.h"
+#include <private/qsgnodeupdater_p.h>
+#include <private/qsgrendernode_p.h>
+
+#include "vs_stencilclip.hlslh"
+#include "ps_stencilclip.hlslh"
+
+//#define I_LIKE_STENCIL
+
+QT_BEGIN_NAMESPACE
+
+#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
+
+// 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(build)
+DECLARE_DEBUG_VAR(change)
+DECLARE_DEBUG_VAR(render)
+
+class DummyUpdater : public QSGNodeUpdater
+{
+public:
+ void updateState(QSGNode *) { };
+};
+
+QSGD3D12Renderer::QSGD3D12Renderer(QSGRenderContext *context)
+ : QSGRenderer(context),
+ m_renderList(16),
+ m_vboData(1024),
+ m_iboData(256),
+ m_cboData(4096)
+{
+ setNodeUpdater(new DummyUpdater);
+}
+
+QSGD3D12Renderer::~QSGD3D12Renderer()
+{
+ if (m_engine) {
+ m_engine->releaseBuffer(m_vertexBuf);
+ m_engine->releaseBuffer(m_indexBuf);
+ m_engine->releaseBuffer(m_constantBuf);
+ }
+}
+
+void QSGD3D12Renderer::renderScene(GLuint fboId)
+{
+ m_renderTarget = fboId;
+
+ struct DummyBindable : public QSGBindable {
+ void bind() const { }
+ } bindable;
+
+ QSGRenderer::renderScene(bindable); // calls back render()
+}
+
+// Search through the node set and remove nodes that are descendants of other
+// nodes in the same set.
+static QSet<QSGNode *> qsg_removeDescendants(const QSet<QSGNode *> &nodes, QSGRootNode *root)
+{
+ QSet<QSGNode *> result = nodes;
+ for (QSGNode *node : nodes) {
+ QSGNode *n = node;
+ while (n != root) {
+ if (n != node && result.contains(n)) {
+ result.remove(node);
+ break;
+ }
+ n = n->parent();
+ }
+ }
+ return result;
+}
+
+void QSGD3D12Renderer::updateMatrices(QSGNode *node, QSGTransformNode *xform)
+{
+ if (node->isSubtreeBlocked())
+ return;
+
+ if (node->type() == QSGNode::TransformNodeType) {
+ QSGTransformNode *tn = static_cast<QSGTransformNode *>(node);
+ if (xform)
+ tn->setCombinedMatrix(xform->combinedMatrix() * tn->matrix());
+ else
+ tn->setCombinedMatrix(tn->matrix());
+ QSGNODE_TRAVERSE(node)
+ updateMatrices(child, tn);
+ } else {
+ if (node->type() == QSGNode::GeometryNodeType || node->type() == QSGNode::ClipNodeType) {
+ m_nodeDirtyMap[node] |= QSGD3D12MaterialRenderState::DirtyMatrix;
+ QSGBasicGeometryNode *gnode = static_cast<QSGBasicGeometryNode *>(node);
+ const QMatrix4x4 *newMatrix = xform ? &xform->combinedMatrix() : nullptr;
+ // NB the newMatrix ptr is usually the same as before as it just
+ // references the transform node's own matrix.
+ gnode->setRendererMatrix(newMatrix);
+ }
+ QSGNODE_TRAVERSE(node)
+ updateMatrices(child, xform);
+ }
+}
+
+void QSGD3D12Renderer::updateOpacities(QSGNode *node, float inheritedOpacity)
+{
+ if (node->isSubtreeBlocked())
+ return;
+
+ if (node->type() == QSGNode::OpacityNodeType) {
+ QSGOpacityNode *on = static_cast<QSGOpacityNode *>(node);
+ float combined = inheritedOpacity * on->opacity();
+ on->setCombinedOpacity(combined);
+ QSGNODE_TRAVERSE(node)
+ updateOpacities(child, combined);
+ } else {
+ if (node->type() == QSGNode::GeometryNodeType) {
+ m_nodeDirtyMap[node] |= QSGD3D12MaterialRenderState::DirtyOpacity;
+ QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
+ gn->setInheritedOpacity(inheritedOpacity);
+ }
+ QSGNODE_TRAVERSE(node)
+ updateOpacities(child, inheritedOpacity);
+ }
+}
+
+void QSGD3D12Renderer::buildRenderList(QSGNode *node, QSGClipNode *clip)
+{
+ if (node->isSubtreeBlocked())
+ return;
+
+ if (node->type() == QSGNode::GeometryNodeType || node->type() == QSGNode::ClipNodeType) {
+ QSGBasicGeometryNode *gn = static_cast<QSGBasicGeometryNode *>(node);
+ QSGGeometry *g = gn->geometry();
+
+ Element e;
+ e.node = gn;
+
+ if (g->vertexCount() > 0) {
+ e.vboOffset = m_vboData.size();
+ const int vertexSize = g->sizeOfVertex() * g->vertexCount();
+ m_vboData.resize(m_vboData.size() + vertexSize);
+ memcpy(m_vboData.data() + e.vboOffset, g->vertexData(), vertexSize);
+ }
+
+ if (g->indexCount() > 0) {
+ e.iboOffset = m_iboData.size();
+ e.iboStride = g->sizeOfIndex();
+ const int indexSize = e.iboStride * g->indexCount();
+ m_iboData.resize(m_iboData.size() + indexSize);
+ memcpy(m_iboData.data() + e.iboOffset, g->indexData(), indexSize);
+ }
+
+ e.cboOffset = m_cboData.size();
+ if (node->type() == QSGNode::GeometryNodeType) {
+ QSGD3D12Material *m = static_cast<QSGD3D12Material *>(static_cast<QSGGeometryNode *>(node)->activeMaterial());
+ e.cboSize = m->constantBufferSize();
+ } else {
+ // Stencil-based clipping needs a 4x4 matrix.
+ e.cboSize = QSGD3D12Engine::alignedConstantBufferSize(16 * sizeof(float));
+ }
+ m_cboData.resize(m_cboData.size() + e.cboSize);
+
+ m_renderList.add(e);
+
+ gn->setRendererClipList(clip);
+ if (node->type() == QSGNode::ClipNodeType)
+ clip = static_cast<QSGClipNode *>(node);
+ } else if (node->type() == QSGNode::RenderNodeType) {
+ QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
+ Element e;
+ e.node = rn;
+ m_renderList.add(e);
+ }
+
+ QSGNODE_TRAVERSE(node)
+ buildRenderList(child, clip);
+}
+
+void QSGD3D12Renderer::render()
+{
+ QSGD3D12RenderContext *rc = static_cast<QSGD3D12RenderContext *>(context());
+ m_engine = rc->engine();
+ if (!m_layerRenderer)
+ m_engine->beginFrame();
+ else
+ m_engine->beginLayer();
+
+ m_activeScissorRect = QRect();
+
+ if (m_rebuild) {
+ m_rebuild = false;
+
+ m_dirtyTransformNodes.clear();
+ m_dirtyTransformNodes.insert(rootNode());
+ m_dirtyOpacityNodes.clear();
+ m_dirtyOpacityNodes.insert(rootNode());
+
+ m_renderList.reset();
+ m_vboData.reset();
+ m_iboData.reset();
+ m_cboData.reset();
+
+ buildRenderList(rootNode(), nullptr);
+
+ if (!m_vertexBuf)
+ m_vertexBuf = m_engine->genBuffer();
+ m_engine->resetBuffer(m_vertexBuf, m_vboData.data(), m_vboData.size());
+
+ if (!m_constantBuf)
+ m_constantBuf = m_engine->genBuffer();
+ m_engine->resetBuffer(m_constantBuf, m_cboData.data(), m_cboData.size());
+
+ if (m_iboData.size()) {
+ if (!m_indexBuf)
+ m_indexBuf = m_engine->genBuffer();
+ m_engine->resetBuffer(m_indexBuf, m_iboData.data(), m_iboData.size());
+ } else if (m_indexBuf) {
+ m_engine->releaseBuffer(m_indexBuf);
+ m_indexBuf = 0;
+ }
+
+ if (Q_UNLIKELY(debug_build())) {
+ qDebug("renderList: %d elements in total", m_renderList.size());
+ for (int i = 0; i < m_renderList.size(); ++i) {
+ const Element &e = m_renderList.at(i);
+ qDebug() << " - " << e.vboOffset << e.iboOffset << e.cboOffset << e.cboSize << e.node;
+ }
+ }
+ }
+
+ const QRect devRect = deviceRect();
+ m_projectionChangedDueToDeviceSize = devRect != m_lastDeviceRect;
+ if (m_projectionChangedDueToDeviceSize)
+ m_lastDeviceRect = devRect;
+
+ if (m_dirtyTransformNodes.size()) {
+ const QSet<QSGNode *> subTreeRoots = qsg_removeDescendants(m_dirtyTransformNodes, rootNode());
+ for (QSGNode *node : subTreeRoots) {
+ // First find the parent transform so we have the accumulated
+ // matrix up until this point.
+ QSGTransformNode *xform = 0;
+ QSGNode *n = node;
+ if (n->type() == QSGNode::TransformNodeType)
+ n = node->parent();
+ while (n != rootNode() && n->type() != QSGNode::TransformNodeType)
+ n = n->parent();
+ if (n != rootNode())
+ xform = static_cast<QSGTransformNode *>(n);
+
+ // Then update in the subtree
+ updateMatrices(node, xform);
+ }
+ }
+
+ if (m_dirtyOpacityNodes.size()) {
+ const QSet<QSGNode *> subTreeRoots = qsg_removeDescendants(m_dirtyOpacityNodes, rootNode());
+ for (QSGNode *node : subTreeRoots) {
+ float opacity = 1.0f;
+ QSGNode *n = node;
+ if (n->type() == QSGNode::OpacityNodeType)
+ n = node->parent();
+ while (n != rootNode() && n->type() != QSGNode::OpacityNodeType)
+ n = n->parent();
+ if (n != rootNode())
+ opacity = static_cast<QSGOpacityNode *>(n)->combinedOpacity();
+
+ updateOpacities(node, opacity);
+ }
+ m_dirtyOpaqueElements = true;
+ }
+
+ if (m_dirtyOpaqueElements) {
+ m_dirtyOpaqueElements = false;
+ m_opaqueElements.clear();
+ m_opaqueElements.resize(m_renderList.size());
+ for (int i = 0; i < m_renderList.size(); ++i) {
+ const Element &e = m_renderList.at(i);
+ if (e.node->type() == QSGNode::GeometryNodeType) {
+ const QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(e.node);
+ if (gn->inheritedOpacity() > 0.999f && ((gn->activeMaterial()->flags() & QSGMaterial::Blending) == 0))
+ m_opaqueElements.setBit(i);
+ }
+ // QSGRenderNodes are always treated as non-opaque
+ }
+ }
+
+ // Build pipeline state and draw calls.
+ renderElements();
+
+ m_dirtyTransformNodes.clear();
+ m_dirtyOpacityNodes.clear();
+ m_dirtyOpaqueElements = false;
+ m_nodeDirtyMap.clear();
+
+ // Finalize buffers and execute commands.
+ if (!m_layerRenderer)
+ m_engine->endFrame();
+ else
+ m_engine->endLayer();
+}
+
+void QSGD3D12Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
+{
+ // note that with DirtyNodeRemoved the window and all the graphics engine may already be gone
+
+ if (Q_UNLIKELY(debug_change())) {
+ QDebug debug = qDebug();
+ debug << "dirty:";
+ if (state & QSGNode::DirtyGeometry)
+ debug << "Geometry";
+ if (state & QSGNode::DirtyMaterial)
+ debug << "Material";
+ if (state & QSGNode::DirtyMatrix)
+ debug << "Matrix";
+ if (state & QSGNode::DirtyNodeAdded)
+ debug << "Added";
+ if (state & QSGNode::DirtyNodeRemoved)
+ debug << "Removed";
+ if (state & QSGNode::DirtyOpacity)
+ debug << "Opacity";
+ if (state & QSGNode::DirtySubtreeBlocked)
+ debug << "SubtreeBlocked";
+ if (state & QSGNode::DirtyForceUpdate)
+ debug << "ForceUpdate";
+
+ // when removed, some parts of the node could already have been destroyed
+ // so don't debug it out.
+ if (state & QSGNode::DirtyNodeRemoved)
+ debug << (void *) node << node->type();
+ else
+ debug << node;
+ }
+
+ if (state & (QSGNode::DirtyNodeAdded
+ | QSGNode::DirtyNodeRemoved
+ | QSGNode::DirtySubtreeBlocked
+ | QSGNode::DirtyGeometry
+ | QSGNode::DirtyForceUpdate))
+ m_rebuild = true;
+
+ if (state & QSGNode::DirtyMatrix)
+ m_dirtyTransformNodes << node;
+
+ if (state & QSGNode::DirtyOpacity)
+ m_dirtyOpacityNodes << node;
+
+ if (state & QSGNode::DirtyMaterial)
+ m_dirtyOpaqueElements = true;
+
+ QSGRenderer::nodeChanged(node, state);
+}
+
+void QSGD3D12Renderer::renderElements()
+{
+ m_engine->queueSetRenderTarget(m_renderTarget);
+ m_engine->queueViewport(viewportRect());
+ m_engine->queueClearRenderTarget(clearColor());
+ m_engine->queueClearDepthStencil(1, 0, QSGD3D12Engine::ClearDepth | QSGD3D12Engine::ClearStencil);
+
+ m_pipelineState.blend = m_freshPipelineState.blend = QSGD3D12PipelineState::BlendNone;
+ m_pipelineState.depthEnable = m_freshPipelineState.depthEnable = true;
+ m_pipelineState.depthWrite = m_freshPipelineState.depthWrite = true;
+
+ // First do opaque...
+ // The algorithm is quite simple. We traverse the list back-to-front, and
+ // for every item we start a second traversal and draw all elements which
+ // have identical material. Then we clear the bit for this in the rendered
+ // list so we don't draw it again when we come to that index.
+ QBitArray rendered = m_opaqueElements;
+ for (int i = m_renderList.size() - 1; i >= 0; --i) {
+ if (rendered.testBit(i)) {
+ renderElement(i);
+ for (int j = i - 1; j >= 0; --j) {
+ if (rendered.testBit(j)) {
+ const QSGGeometryNode *gni = static_cast<QSGGeometryNode *>(m_renderList.at(i).node);
+ const QSGGeometryNode *gnj = static_cast<QSGGeometryNode *>(m_renderList.at(j).node);
+ if (gni->clipList() == gnj->clipList()
+ && gni->inheritedOpacity() == gnj->inheritedOpacity()
+ && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
+ && gni->geometry()->attributes() == gnj->geometry()->attributes()) {
+ const QSGMaterial *ami = gni->activeMaterial();
+ const QSGMaterial *amj = gnj->activeMaterial();
+ if (ami->type() == amj->type()
+ && ami->flags() == amj->flags()
+ && ami->compare(amj) == 0) {
+ renderElement(j);
+ rendered.clearBit(j);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ m_pipelineState.blend = m_freshPipelineState.blend = QSGD3D12PipelineState::BlendPremul;
+ m_pipelineState.depthWrite = m_freshPipelineState.depthWrite = false;
+
+ // ...then the alpha ones
+ for (int i = 0; i < m_renderList.size(); ++i) {
+ if ((m_renderList.at(i).node->type() == QSGNode::GeometryNodeType && !m_opaqueElements.testBit(i))
+ || m_renderList.at(i).node->type() == QSGNode::RenderNodeType)
+ renderElement(i);
+ }
+}
+
+struct RenderNodeState : public QSGRenderNode::RenderState
+{
+ const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
+ QRect scissorRect() const { return m_scissorRect; }
+ bool scissorEnabled() const { return m_scissorEnabled; }
+ int stencilValue() const { return m_stencilValue; }
+ bool stencilEnabled() const { return m_stencilEnabled; }
+ const QRegion *clipRegion() const override { return nullptr; }
+
+ const QMatrix4x4 *m_projectionMatrix;
+ QRect m_scissorRect;
+ bool m_scissorEnabled;
+ int m_stencilValue;
+ bool m_stencilEnabled;
+};
+
+void QSGD3D12Renderer::renderElement(int elementIndex)
+{
+ Element &e = m_renderList.at(elementIndex);
+ Q_ASSERT(e.node->type() == QSGNode::GeometryNodeType || e.node->type() == QSGNode::RenderNodeType);
+
+ if (e.node->type() == QSGNode::RenderNodeType) {
+ renderRenderNode(static_cast<QSGRenderNode *>(e.node), elementIndex);
+ return;
+ }
+
+ if (e.vboOffset < 0)
+ return;
+
+ Q_ASSERT(e.cboOffset >= 0);
+
+ const QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(e.node);
+ if (Q_UNLIKELY(debug_render()))
+ qDebug() << "renderElement:" << elementIndex << gn << e.vboOffset << e.iboOffset << gn->inheritedOpacity() << gn->clipList();
+
+ if (gn->inheritedOpacity() < 0.001f) // pretty much invisible, don't draw it
+ return;
+
+ // Update the QSGRenderer members which the materials will access.
+ m_current_projection_matrix = projectionMatrix();
+ const float scale = 1.0 / m_renderList.size();
+ m_current_projection_matrix(2, 2) = scale;
+ m_current_projection_matrix(2, 3) = 1.0f - (elementIndex + 1) * scale;
+ m_current_model_view_matrix = gn->matrix() ? *gn->matrix() : QMatrix4x4();
+ m_current_determinant = m_current_model_view_matrix.determinant();
+ m_current_opacity = gn->inheritedOpacity();
+
+ const QSGGeometry *g = gn->geometry();
+ QSGD3D12Material *m = static_cast<QSGD3D12Material *>(gn->activeMaterial());
+
+ if (m->type() != m_lastMaterialType) {
+ m_pipelineState = m_freshPipelineState;
+ m->preparePipeline(&m_pipelineState);
+ }
+
+ QSGD3D12MaterialRenderState::DirtyStates dirtyState = m_nodeDirtyMap.value(e.node);
+
+ // After a rebuild everything in the cbuffer has to be updated.
+ if (!e.cboPrepared) {
+ e.cboPrepared = true;
+ dirtyState = QSGD3D12MaterialRenderState::DirtyAll;
+ }
+
+ // DirtyMatrix does not include projection matrix changes that can arise
+ // due to changing the render target's size (and there is no rebuild).
+ // Accommodate for this.
+ if (m_projectionChangedDueToDeviceSize)
+ dirtyState |= QSGD3D12MaterialRenderState::DirtyMatrix;
+
+ quint8 *cboPtr = nullptr;
+ if (e.cboSize > 0)
+ cboPtr = m_cboData.data() + e.cboOffset;
+
+ if (Q_UNLIKELY(debug_render()))
+ qDebug() << "dirty state for" << e.node << "is" << dirtyState;
+
+ QSGD3D12Material::ExtraState extraState;
+ QSGD3D12Material::UpdateResults updRes = m->updatePipeline(state(dirtyState),
+ &m_pipelineState,
+ &extraState,
+ cboPtr);
+
+ if (updRes.testFlag(QSGD3D12Material::UpdatedConstantBuffer))
+ m_engine->markBufferDirty(m_constantBuf, e.cboOffset, e.cboSize);
+
+ if (updRes.testFlag(QSGD3D12Material::UpdatedBlendFactor))
+ m_engine->queueSetBlendFactor(extraState.blendFactor);
+
+ setInputLayout(g, &m_pipelineState);
+
+ m_lastMaterialType = m->type();
+
+ setupClipping(gn->clipList(), elementIndex);
+
+ // ### Lines and points with sizes other than 1 have to be implemented in some other way. Just ignore for now.
+ if (g->drawingMode() == QSGGeometry::DrawLineStrip || g->drawingMode() == QSGGeometry::DrawLines) {
+ if (g->lineWidth() != 1.0f)
+ qWarning("QSGD3D12Renderer: Line widths other than 1 are not supported by this renderer");
+ } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
+ if (g->lineWidth() != 1.0f)
+ qWarning("QSGD3D12Renderer: Point sprites are not supported by this renderer");
+ }
+
+ m_engine->finalizePipeline(m_pipelineState);
+
+ queueDrawCall(g, e);
+}
+
+void QSGD3D12Renderer::setInputLayout(const QSGGeometry *g, QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->inputElementCount = g->attributeCount();
+ const QSGGeometry::Attribute *attrs = g->attributes();
+ quint32 offset = 0;
+ for (int i = 0; i < g->attributeCount(); ++i) {
+ QSGD3D12InputElement &ie(pipelineState->inputElements[i]);
+ static const char *semanticNames[] = { "UNKNOWN", "POSITION", "COLOR", "TEXCOORD", "TEXCOORD", "TEXCOORD" };
+ static const int semanticIndices[] = { 0, 0, 0, 0, 1, 2 };
+ const int semantic = attrs[i].attributeType;
+ Q_ASSERT(semantic >= 1 && semantic < _countof(semanticNames));
+ const int tupleSize = attrs[i].tupleSize;
+ ie.semanticName = semanticNames[semantic];
+ ie.semanticIndex = semanticIndices[semantic];
+ ie.offset = offset;
+ int bytesPerTuple = 0;
+ ie.format = QSGD3D12Engine::toDXGIFormat(QSGGeometry::Type(attrs[i].type), tupleSize, &bytesPerTuple);
+ if (ie.format == FmtUnknown)
+ qFatal("QSGD3D12Renderer: unsupported tuple size for attribute type 0x%x", attrs[i].type);
+ offset += bytesPerTuple;
+ // There is one buffer with interleaved data so the slot is always 0.
+ ie.slot = 0;
+ }
+}
+
+void QSGD3D12Renderer::queueDrawCall(const QSGGeometry *g, const QSGD3D12Renderer::Element &e)
+{
+ QSGD3D12Engine::DrawParams dp;
+ dp.mode = QSGGeometry::DrawingMode(g->drawingMode());
+ dp.vertexBuf = m_vertexBuf;
+ dp.constantBuf = m_constantBuf;
+ dp.vboOffset = e.vboOffset;
+ dp.vboSize = g->vertexCount() * g->sizeOfVertex();
+ dp.vboStride = g->sizeOfVertex();
+ dp.cboOffset = e.cboOffset;
+
+ if (e.iboOffset >= 0) {
+ const QSGGeometry::Type indexType = QSGGeometry::Type(g->indexType());
+ const QSGD3D12Format indexFormat = QSGD3D12Engine::toDXGIFormat(indexType);
+ if (indexFormat == FmtUnknown)
+ qFatal("QSGD3D12Renderer: unsupported index type 0x%x", indexType);
+ dp.count = g->indexCount();
+ dp.indexBuf = m_indexBuf;
+ dp.startIndexIndex = e.iboOffset / e.iboStride;
+ dp.indexFormat = indexFormat;
+ } else {
+ dp.count = g->vertexCount();
+ }
+
+ m_engine->queueDraw(dp);
+}
+
+void QSGD3D12Renderer::setupClipping(const QSGClipNode *clip, int elementIndex)
+{
+ const QRect devRect = deviceRect();
+ QRect scissorRect;
+ int clipTypes = 0;
+ quint32 stencilValue = 0;
+
+ while (clip) {
+ QMatrix4x4 m = projectionMatrix();
+ if (clip->matrix())
+ m *= *clip->matrix();
+
+#ifndef I_LIKE_STENCIL
+ const bool isRectangleWithNoPerspective = clip->isRectangular()
+ && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
+ const bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
+ const bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
+
+ if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
+ QRectF bbox = clip->clipRect();
+ float invW = 1.0f / m(3, 3);
+ float fx1, fy1, fx2, fy2;
+ if (noRotate) {
+ fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
+ fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
+ fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
+ fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
+ } else {
+ Q_ASSERT(isRotate90);
+ fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
+ fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
+ fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
+ fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
+ }
+
+ if (fx1 > fx2)
+ qSwap(fx1, fx2);
+ if (fy1 > fy2)
+ qSwap(fy1, fy2);
+
+ int ix1 = qRound((fx1 + 1) * devRect.width() * 0.5f);
+ int iy1 = qRound((fy1 + 1) * devRect.height() * 0.5f);
+ int ix2 = qRound((fx2 + 1) * devRect.width() * 0.5f);
+ int iy2 = qRound((fy2 + 1) * devRect.height() * 0.5f);
+
+ if (!(clipTypes & ClipScissor)) {
+ scissorRect = QRect(ix1, devRect.height() - iy2, ix2 - ix1, iy2 - iy1);
+ clipTypes |= ClipScissor;
+ } else {
+ scissorRect &= QRect(ix1, devRect.height() - iy2, ix2 - ix1, iy2 - iy1);
+ }
+ } else
+#endif
+ {
+ clipTypes |= ClipStencil;
+ renderStencilClip(clip, elementIndex, m, stencilValue);
+ }
+
+ clip = clip->clipList();
+ }
+
+ setScissor((clipTypes & ClipScissor) ? scissorRect : viewportRect());
+
+ if (clipTypes & ClipStencil) {
+ m_pipelineState.stencilEnable = true;
+ m_engine->queueSetStencilRef(stencilValue);
+ m_currentStencilValue = stencilValue;
+ } else {
+ m_pipelineState.stencilEnable = false;
+ m_currentStencilValue = 0;
+ }
+
+ m_currentClipTypes = clipTypes;
+}
+
+void QSGD3D12Renderer::setScissor(const QRect &r)
+{
+ if (m_activeScissorRect == r)
+ return;
+
+ m_activeScissorRect = r;
+ m_engine->queueScissor(r);
+}
+
+void QSGD3D12Renderer::renderStencilClip(const QSGClipNode *clip, int elementIndex,
+ const QMatrix4x4 &m, quint32 &stencilValue)
+{
+ QSGD3D12PipelineState sps;
+ sps.shaders.vs = g_VS_StencilClip;
+ sps.shaders.vsSize = sizeof(g_VS_StencilClip);
+ sps.shaders.ps = g_PS_StencilClip;
+ sps.shaders.psSize = sizeof(g_PS_StencilClip);
+
+ m_engine->queueClearDepthStencil(1, 0, QSGD3D12Engine::ClearStencil);
+ sps.stencilEnable = true;
+ sps.colorWrite = false;
+ sps.depthWrite = false;
+
+ sps.stencilFunc = QSGD3D12PipelineState::CompareEqual;
+ sps.stencilFailOp = QSGD3D12PipelineState::StencilKeep;
+ sps.stencilDepthFailOp = QSGD3D12PipelineState::StencilKeep;
+ sps.stencilPassOp = QSGD3D12PipelineState::StencilIncr;
+
+ m_engine->queueSetStencilRef(stencilValue);
+
+ int clipIndex = elementIndex;
+ while (m_renderList.at(--clipIndex).node != clip) {
+ Q_ASSERT(clipIndex >= 0);
+ }
+ const Element &ce = m_renderList.at(clipIndex);
+ Q_ASSERT(ce.node == clip);
+
+ const QSGGeometry *g = clip->geometry();
+ Q_ASSERT(g->attributeCount() == 1);
+ Q_ASSERT(g->attributes()[0].tupleSize == 2);
+ Q_ASSERT(g->attributes()[0].type == QSGGeometry::FloatType);
+
+ setInputLayout(g, &sps);
+ m_engine->finalizePipeline(sps);
+
+ Q_ASSERT(ce.cboSize > 0);
+ quint8 *p = m_cboData.data() + ce.cboOffset;
+ memcpy(p, m.constData(), 16 * sizeof(float));
+ m_engine->markBufferDirty(m_constantBuf, ce.cboOffset, ce.cboSize);
+
+ queueDrawCall(g, ce);
+
+ ++stencilValue;
+}
+
+void QSGD3D12Renderer::renderRenderNode(QSGRenderNode *node, int elementIndex)
+{
+ QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node);
+ RenderNodeState state;
+
+ setupClipping(rd->m_clip_list, elementIndex);
+
+ QMatrix4x4 pm = projectionMatrix();
+ state.m_projectionMatrix = &pm;
+ state.m_scissorEnabled = m_currentClipTypes & ClipScissor;
+ state.m_stencilEnabled = m_currentClipTypes & ClipStencil;
+ state.m_scissorRect = m_activeScissorRect;
+ state.m_stencilValue = m_currentStencilValue;
+
+ // ### rendernodes do not have the QSGBasicGeometryNode infrastructure
+ // for storing combined matrices, opacity and such, but perhaps they should.
+ QSGNode *xform = node->parent();
+ QSGNode *root = rootNode();
+ QMatrix4x4 modelview;
+ while (xform != root) {
+ if (xform->type() == QSGNode::TransformNodeType) {
+ modelview *= static_cast<QSGTransformNode *>(xform)->combinedMatrix();
+ break;
+ }
+ xform = xform->parent();
+ }
+ rd->m_matrix = &modelview;
+
+ QSGNode *opacity = node->parent();
+ rd->m_opacity = 1.0;
+ while (opacity != rootNode()) {
+ if (opacity->type() == QSGNode::OpacityNodeType) {
+ rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
+ break;
+ }
+ opacity = opacity->parent();
+ }
+
+ node->render(&state);
+
+ m_engine->invalidateCachedFrameState();
+ // For simplicity, reset viewport, scissor, blend factor, stencil ref when
+ // any of them got changed. This will likely be rare so skip these otherwise.
+ // Render target, pipeline state, draw call related stuff will be reset always.
+ const bool restoreMinimal = node->changedStates() == 0;
+ m_engine->restoreFrameState(restoreMinimal);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12renderer_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12renderer_p.h
new file mode 100644
index 0000000000..df30a49f0d
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12renderer_p.h
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** 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 QSGD3D12RENDERER_P_H
+#define QSGD3D12RENDERER_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/qsgrenderer_p.h>
+#include <QtGui/private/qdatabuffer_p.h>
+#include <QtCore/qbitarray.h>
+#include "qsgd3d12engine_p.h"
+#include "qsgd3d12material_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGRenderNode;
+
+class QSGD3D12Renderer : public QSGRenderer
+{
+public:
+ QSGD3D12Renderer(QSGRenderContext *context);
+ ~QSGD3D12Renderer();
+
+ void renderScene(GLuint fboId) override;
+ void render() override;
+ void nodeChanged(QSGNode *node, QSGNode::DirtyState state) override;
+
+ void turnToLayerRenderer() { m_layerRenderer = true; }
+
+private:
+ void updateMatrices(QSGNode *node, QSGTransformNode *xform);
+ void updateOpacities(QSGNode *node, float inheritedOpacity);
+ void buildRenderList(QSGNode *node, QSGClipNode *clip);
+ void renderElements();
+ void renderElement(int elementIndex);
+ void setInputLayout(const QSGGeometry *g, QSGD3D12PipelineState *pipelineState);
+ void setupClipping(const QSGClipNode *clip, int elementIndex);
+ void setScissor(const QRect &r);
+ void renderStencilClip(const QSGClipNode *clip, int elementIndex, const QMatrix4x4 &m, quint32 &stencilValue);
+ void renderRenderNode(QSGRenderNode *node, int elementIndex);
+
+ struct Element {
+ QSGNode *node = nullptr;
+ qint32 vboOffset = -1;
+ qint32 iboOffset = -1;
+ quint32 iboStride = 0;
+ qint32 cboOffset = -1;
+ quint32 cboSize = 0;
+ bool cboPrepared = false;
+ };
+
+ void queueDrawCall(const QSGGeometry *g, const Element &e);
+
+ bool m_layerRenderer = false;
+ QSet<QSGNode *> m_dirtyTransformNodes;
+ QSet<QSGNode *> m_dirtyOpacityNodes;
+ QBitArray m_opaqueElements;
+ bool m_rebuild = true;
+ bool m_dirtyOpaqueElements = true;
+ QDataBuffer<quint8> m_vboData;
+ QDataBuffer<quint8> m_iboData;
+ QDataBuffer<quint8> m_cboData;
+ QDataBuffer<Element> m_renderList;
+ uint m_vertexBuf = 0;
+ uint m_indexBuf = 0;
+ uint m_constantBuf = 0;
+ QSGD3D12Engine *m_engine = nullptr;
+
+ QSGMaterialType *m_lastMaterialType = nullptr;
+ QSGD3D12PipelineState m_pipelineState;
+ QSGD3D12PipelineState m_freshPipelineState;
+
+ typedef QHash<QSGNode *, QSGD3D12MaterialRenderState::DirtyStates> NodeDirtyMap;
+ NodeDirtyMap m_nodeDirtyMap;
+
+ QRect m_activeScissorRect;
+ QRect m_lastDeviceRect;
+ bool m_projectionChangedDueToDeviceSize;
+
+ uint m_renderTarget = 0;
+ quint32 m_currentStencilValue;
+ enum ClipType {
+ ClipScissor = 0x1,
+ ClipStencil = 0x2
+ };
+ int m_currentClipTypes;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12RENDERER_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp
new file mode 100644
index 0000000000..c53a1fa6c1
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp
@@ -0,0 +1,534 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12renderloop_p.h"
+#include "qsgd3d12engine_p.h"
+#include "qsgd3d12context_p.h"
+#include "qsgd3d12rendercontext_p.h"
+#include "qsgd3d12shadereffectnode_p.h"
+#include <private/qquickwindow_p.h>
+#include <private/qquickprofiler_p.h>
+#include <private/qquickanimatorcontroller_p.h>
+#include <QElapsedTimer>
+#include <QGuiApplication>
+#include <QScreen>
+
+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(loop)
+DECLARE_DEBUG_VAR(time)
+
+
+// This render loop operates on the gui (main) thread.
+// Conceptually it matches the OpenGL 'windows' render loop.
+
+static inline int qsgrl_animation_interval()
+{
+ const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0;
+ return refreshRate < 1 ? 16 : int(1000 / refreshRate);
+}
+
+QSGD3D12RenderLoop::QSGD3D12RenderLoop()
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("new d3d12 render loop");
+
+ sg = new QSGD3D12Context;
+
+ m_anims = sg->createAnimationDriver(this);
+ connect(m_anims, &QAnimationDriver::started, this, &QSGD3D12RenderLoop::onAnimationStarted);
+ connect(m_anims, &QAnimationDriver::stopped, this, &QSGD3D12RenderLoop::onAnimationStopped);
+ m_anims->install();
+
+ m_vsyncDelta = qsgrl_animation_interval();
+}
+
+QSGD3D12RenderLoop::~QSGD3D12RenderLoop()
+{
+ delete sg;
+}
+
+void QSGD3D12RenderLoop::show(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "show" << window;
+}
+
+void QSGD3D12RenderLoop::hide(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "hide" << window;
+}
+
+void QSGD3D12RenderLoop::resize(QQuickWindow *window)
+{
+ if (!m_windows.contains(window) || window->size().isEmpty())
+ return;
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "resize" << window;
+
+ const WindowData &data(m_windows[window]);
+
+ if (!data.exposed)
+ return;
+
+ if (data.engine)
+ data.engine->setWindowSize(window->size(), window->effectiveDevicePixelRatio());
+}
+
+void QSGD3D12RenderLoop::windowDestroyed(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "window destroyed" << window;
+
+ if (!m_windows.contains(window))
+ return;
+
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
+ wd->fireAboutToStop();
+
+ WindowData &data(m_windows[window]);
+ QSGD3D12Engine *engine = data.engine;
+ QSGD3D12RenderContext *rc = data.rc;
+ m_windows.remove(window);
+
+ // QSGNode destruction may release graphics resources in use so wait first.
+ engine->waitGPU();
+
+ // Bye bye nodes...
+ wd->cleanupNodesOnShutdown();
+
+ QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
+
+ rc->invalidate();
+
+ if (m_windows.isEmpty())
+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+
+ delete rc;
+ delete engine;
+
+ delete wd->animationController;
+}
+
+void QSGD3D12RenderLoop::exposeWindow(QQuickWindow *window)
+{
+ WindowData data;
+ data.exposed = true;
+ data.engine = new QSGD3D12Engine;
+ data.rc = static_cast<QSGD3D12RenderContext *>(QQuickWindowPrivate::get(window)->context);
+ data.rc->setEngine(data.engine);
+ m_windows[window] = data;
+
+ const int samples = window->format().samples();
+ const bool alpha = window->format().alphaBufferSize() > 0;
+ const qreal dpr = window->effectiveDevicePixelRatio();
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "initializing D3D12 engine" << window << window->size() << dpr << samples << alpha;
+
+ data.engine->attachToWindow(window->winId(), window->size(), dpr, samples, alpha);
+}
+
+void QSGD3D12RenderLoop::obscureWindow(QQuickWindow *window)
+{
+ m_windows[window].exposed = false;
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
+ wd->fireAboutToStop();
+}
+
+void QSGD3D12RenderLoop::exposureChanged(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "exposure changed" << window << window->isExposed();
+
+ if (window->isExposed()) {
+ if (!m_windows.contains(window))
+ exposeWindow(window);
+
+ // Stop non-visual animation timer as we now have a window rendering.
+ if (m_animationTimer && somethingVisible()) {
+ killTimer(m_animationTimer);
+ m_animationTimer = 0;
+ }
+ // If we have a pending timer and we get an expose, we need to stop it.
+ // Otherwise we get two frames and two animation ticks in the same time interval.
+ if (m_updateTimer) {
+ killTimer(m_updateTimer);
+ m_updateTimer = 0;
+ }
+
+ WindowData &data(m_windows[window]);
+ data.exposed = true;
+ data.updatePending = true;
+
+ render();
+
+ } else if (m_windows.contains(window)) {
+ obscureWindow(window);
+
+ // Potentially start the non-visual animation timer if nobody is rendering.
+ if (m_anims->isRunning() && !somethingVisible() && !m_animationTimer)
+ m_animationTimer = startTimer(m_vsyncDelta);
+ }
+}
+
+QImage QSGD3D12RenderLoop::grab(QQuickWindow *window)
+{
+ const bool tempExpose = !m_windows.contains(window);
+ if (tempExpose)
+ exposeWindow(window);
+
+ m_windows[window].grabOnly = true;
+
+ renderWindow(window);
+
+ QImage grabbed = m_grabContent;
+ m_grabContent = QImage();
+
+ if (tempExpose)
+ obscureWindow(window);
+
+ return grabbed;
+}
+
+bool QSGD3D12RenderLoop::somethingVisible() const
+{
+ for (auto it = m_windows.constBegin(), itEnd = m_windows.constEnd(); it != itEnd; ++it) {
+ if (it.key()->isVisible() && it.key()->isExposed())
+ return true;
+ }
+ return false;
+}
+
+void QSGD3D12RenderLoop::maybePostUpdateTimer()
+{
+ if (!m_updateTimer) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("starting update timer");
+ m_updateTimer = startTimer(m_vsyncDelta / 3);
+ }
+}
+
+void QSGD3D12RenderLoop::update(QQuickWindow *window)
+{
+ maybeUpdate(window);
+}
+
+void QSGD3D12RenderLoop::maybeUpdate(QQuickWindow *window)
+{
+ if (!m_windows.contains(window) || !somethingVisible())
+ return;
+
+ m_windows[window].updatePending = true;
+ maybePostUpdateTimer();
+}
+
+QAnimationDriver *QSGD3D12RenderLoop::animationDriver() const
+{
+ return m_anims;
+}
+
+QSGContext *QSGD3D12RenderLoop::sceneGraphContext() const
+{
+ return sg;
+}
+
+QSGRenderContext *QSGD3D12RenderLoop::createRenderContext(QSGContext *) const
+{
+ // The rendercontext and engine are per-window, like with the threaded
+ // loop, but unlike the non-threaded OpenGL variants.
+ return sg->createRenderContext();
+}
+
+void QSGD3D12RenderLoop::releaseResources(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "releaseResources" << window;
+}
+
+void QSGD3D12RenderLoop::postJob(QQuickWindow *window, QRunnable *job)
+{
+ Q_UNUSED(window);
+ Q_ASSERT(job);
+ Q_ASSERT(window);
+ job->run();
+ delete job;
+}
+
+QSurface::SurfaceType QSGD3D12RenderLoop::windowSurfaceType() const
+{
+ return QSurface::OpenGLSurface;
+}
+
+bool QSGD3D12RenderLoop::interleaveIncubation() const
+{
+ return m_anims->isRunning() && somethingVisible();
+}
+
+void QSGD3D12RenderLoop::onAnimationStarted()
+{
+ if (!somethingVisible()) {
+ if (!m_animationTimer) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("starting non-visual animation timer");
+ m_animationTimer = startTimer(m_vsyncDelta);
+ }
+ } else {
+ maybePostUpdateTimer();
+ }
+}
+
+void QSGD3D12RenderLoop::onAnimationStopped()
+{
+ if (m_animationTimer) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("stopping non-visual animation timer");
+ killTimer(m_animationTimer);
+ m_animationTimer = 0;
+ }
+}
+
+bool QSGD3D12RenderLoop::event(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::Timer:
+ {
+ QTimerEvent *te = static_cast<QTimerEvent *>(event);
+ if (te->timerId() == m_animationTimer) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("animation tick while no windows exposed");
+ m_anims->advance();
+ } else if (te->timerId() == m_updateTimer) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("update timeout - rendering");
+ killTimer(m_updateTimer);
+ m_updateTimer = 0;
+ render();
+ }
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return QObject::event(event);
+}
+
+void QSGD3D12RenderLoop::render()
+{
+ bool rendered = false;
+ for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
+ if (it->updatePending) {
+ it->updatePending = false;
+ renderWindow(it.key());
+ rendered = true;
+ }
+ }
+
+ if (!rendered) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("render - no changes, sleep");
+ QThread::msleep(m_vsyncDelta);
+ }
+
+ if (m_anims->isRunning()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("render - advancing animations");
+
+ m_anims->advance();
+
+ // It is not given that animations triggered another maybeUpdate()
+ // and thus another render pass, so to keep things running,
+ // make sure there is another frame pending.
+ maybePostUpdateTimer();
+
+ emit timeToIncubate();
+ }
+}
+
+void QSGD3D12RenderLoop::renderWindow(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "renderWindow" << window;
+
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
+ if (!m_windows.contains(window) || !window->geometry().isValid())
+ return;
+
+ WindowData &data(m_windows[window]);
+ if (!data.exposed) { // not the same as window->isExposed(), when grabbing invisible windows for instance
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("renderWindow - not exposed, abort");
+ return;
+ }
+
+ if (!data.grabOnly)
+ wd->flushFrameSynchronousEvents();
+
+ QElapsedTimer renderTimer;
+ qint64 renderTime = 0, syncTime = 0, polishTime = 0;
+ const bool profileFrames = debug_time();
+ if (profileFrames)
+ renderTimer.start();
+ Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishFrame);
+
+ wd->polishItems();
+
+ if (profileFrames)
+ polishTime = renderTimer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_SWITCH(QQuickProfiler::SceneGraphPolishFrame,
+ QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ emit window->afterAnimating();
+
+ // The native window may change in some (quite artificial) cases, e.g. due
+ // to a hide - destroy - show on the QWindow.
+ bool needsWindow = !data.engine->window();
+ if (data.engine->window() && data.engine->window() != window->winId()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("sync - native window handle changes for active engine");
+ data.engine->waitGPU();
+ wd->cleanupNodesOnShutdown();
+ QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
+ data.rc->invalidate();
+ data.engine->releaseResources();
+ needsWindow = true;
+ }
+ if (needsWindow) {
+ // Must only ever get here when there is no window or releaseResources() has been called.
+ const int samples = window->format().samples();
+ const bool alpha = window->format().alphaBufferSize() > 0;
+ const qreal dpr = window->effectiveDevicePixelRatio();
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "sync - reinitializing D3D12 engine" << window << window->size() << dpr << samples << alpha;
+ data.engine->attachToWindow(window->winId(), window->size(), dpr, samples, alpha);
+ }
+
+ // Recover from device loss.
+ if (!data.engine->hasResources()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("sync - device was lost, resetting scenegraph");
+ wd->cleanupNodesOnShutdown();
+ QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
+ data.rc->invalidate();
+ }
+
+ data.rc->initialize(nullptr);
+
+ wd->syncSceneGraph();
+
+ if (profileFrames)
+ syncTime = renderTimer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ wd->renderSceneGraph(window->size());
+
+ if (profileFrames)
+ renderTime = renderTimer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ if (!data.grabOnly) {
+ // The engine is able to have multiple frames in flight. This in effect is
+ // similar to BufferQueueingOpenGL. Provide an env var to force the
+ // traditional blocking swap behavior, just in case.
+ static bool blockOnEachFrame = qEnvironmentVariableIntValue("QT_D3D_BLOCKING_PRESENT") != 0;
+
+ if (window->isVisible()) {
+ data.engine->present();
+ if (blockOnEachFrame)
+ data.engine->waitGPU();
+ // The concept of "frame swaps" is quite misleading by default, when
+ // blockOnEachFrame is not used, but emit it for compatibility.
+ wd->fireFrameSwapped();
+ } else {
+ if (blockOnEachFrame)
+ data.engine->waitGPU();
+ }
+ } else {
+ m_grabContent = data.engine->executeAndWaitReadbackRenderTarget();
+ data.grabOnly = false;
+ }
+
+ qint64 swapTime = 0;
+ if (profileFrames)
+ swapTime = renderTimer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ if (Q_UNLIKELY(debug_time())) {
+ static QTime lastFrameTime = QTime::currentTime();
+ qDebug("Frame rendered with 'd3d12' renderloop in %dms, polish=%d, sync=%d, render=%d, swap=%d, frameDelta=%d",
+ int(swapTime / 1000000),
+ int(polishTime / 1000000),
+ int((syncTime - polishTime) / 1000000),
+ int((renderTime - syncTime) / 1000000),
+ int((swapTime - renderTime) / 10000000),
+ int(lastFrameTime.msecsTo(QTime::currentTime())));
+ lastFrameTime = QTime::currentTime();
+ }
+
+ // Simulate device loss if requested.
+ static int devLossTest = qEnvironmentVariableIntValue("QT_D3D_TEST_DEVICE_LOSS");
+ if (devLossTest > 0) {
+ static QElapsedTimer kt;
+ static bool timerRunning = false;
+ if (!timerRunning) {
+ kt.start();
+ timerRunning = true;
+ } else if (kt.elapsed() > 5000) {
+ --devLossTest;
+ kt.restart();
+ data.engine->simulateDeviceLoss();
+ }
+ }
+}
+
+int QSGD3D12RenderLoop::flags() const
+{
+ return SupportsGrabWithoutExpose;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h
new file mode 100644
index 0000000000..c0333ffad0
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** 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 QSGD3D12RENDERLOOP_P_H
+#define QSGD3D12RENDERLOOP_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/qsgrenderloop_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Engine;
+class QSGD3D12Context;
+class QSGD3D12RenderContext;
+
+class QSGD3D12RenderLoop : public QSGRenderLoop
+{
+ Q_OBJECT
+
+public:
+ QSGD3D12RenderLoop();
+ ~QSGD3D12RenderLoop();
+
+ void show(QQuickWindow *window) override;
+ void hide(QQuickWindow *window) override;
+ void resize(QQuickWindow *window) override;
+
+ void windowDestroyed(QQuickWindow *window) override;
+
+ void exposureChanged(QQuickWindow *window) override;
+
+ QImage grab(QQuickWindow *window) override;
+
+ void update(QQuickWindow *window) override;
+ void maybeUpdate(QQuickWindow *window) override;
+
+ QAnimationDriver *animationDriver() const override;
+
+ QSGContext *sceneGraphContext() const override;
+ QSGRenderContext *createRenderContext(QSGContext *) const override;
+
+ void releaseResources(QQuickWindow *window) override;
+ void postJob(QQuickWindow *window, QRunnable *job) override;
+
+ QSurface::SurfaceType windowSurfaceType() const override;
+ bool interleaveIncubation() const override;
+ int flags() const override;
+
+ bool event(QEvent *event) override;
+
+public Q_SLOTS:
+ void onAnimationStarted();
+ void onAnimationStopped();
+
+private:
+ void exposeWindow(QQuickWindow *window);
+ void obscureWindow(QQuickWindow *window);
+ void renderWindow(QQuickWindow *window);
+ void render();
+ void maybePostUpdateTimer();
+ bool somethingVisible() const;
+
+ QSGD3D12Context *sg;
+ QAnimationDriver *m_anims;
+ int m_vsyncDelta;
+ int m_updateTimer = 0;
+ int m_animationTimer = 0;
+
+ struct WindowData {
+ QSGD3D12RenderContext *rc = nullptr;
+ QSGD3D12Engine *engine = nullptr;
+ bool updatePending = false;
+ bool grabOnly = false;
+ bool exposed = false;
+ };
+
+ QHash<QQuickWindow *, WindowData> m_windows;
+
+ QImage m_grabContent;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12RENDERLOOP_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode.cpp
new file mode 100644
index 0000000000..62771eb8f9
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode.cpp
@@ -0,0 +1,1047 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12texture_p.h"
+#include "qsgd3d12engine_p.h"
+#include <QtCore/qthreadpool.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileselector.h>
+#include <QtQml/qqmlfile.h>
+#include <qsgtextureprovider.h>
+
+#include <d3d12shader.h>
+#include <d3dcompiler.h>
+
+#include "vs_shadereffectdefault.hlslh"
+#include "ps_shadereffectdefault.hlslh"
+
+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(shader)
+
+void QSGD3D12ShaderLinker::reset(const QByteArray &vertBlob, const QByteArray &fragBlob)
+{
+ Q_ASSERT(!vertBlob.isEmpty() && !fragBlob.isEmpty());
+ vs = vertBlob;
+ fs = fragBlob;
+
+ error = false;
+
+ constantBufferSize = 0;
+ constants.clear();
+ samplers.clear();
+ textures.clear();
+ textureNameMap.clear();
+}
+
+void QSGD3D12ShaderLinker::feedVertexInput(const QSGShaderEffectNode::ShaderData &shader)
+{
+ bool foundPos = false, foundTexCoord = false;
+
+ for (const auto &ip : qAsConst(shader.shaderInfo.inputParameters)) {
+ if (ip.semanticName == QByteArrayLiteral("POSITION"))
+ foundPos = true;
+ else if (ip.semanticName == QByteArrayLiteral("TEXCOORD"))
+ foundTexCoord = true;
+ }
+
+ if (!foundPos) {
+ qWarning("ShaderEffect: No POSITION input found.");
+ error = true;
+ }
+ if (!foundTexCoord) {
+ qWarning("ShaderEffect: No TEXCOORD input found.");
+ error = true;
+ }
+
+ // Nothing else to do here, the QSGGeometry::AttributeSet decides anyway
+ // and that is already generated by QQuickShaderEffectMesh via
+ // QSGGeometry::defaultAttributes_TexturedPoint2D() and has the semantics
+ // so it will just work.
+}
+
+void QSGD3D12ShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
+{
+ Q_ASSERT(shader.shaderInfo.variables.count() == shader.varData.count());
+ if (!dirtyIndices) {
+ constantBufferSize = qMax(constantBufferSize, shader.shaderInfo.constantDataSize);
+ for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) {
+ const auto &var(shader.shaderInfo.variables.at(i));
+ if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Constant) {
+ const auto &vd(shader.varData.at(i));
+ Constant c;
+ c.size = var.size;
+ c.specialType = vd.specialType;
+ if (c.specialType != QSGShaderEffectNode::VariableData::SubRect) {
+ c.value = vd.value;
+ } else {
+ Q_ASSERT(var.name.startsWith(QByteArrayLiteral("qt_SubRect_")));
+ c.value = var.name.mid(11);
+ }
+ constants[var.offset] = c;
+ }
+ }
+ } else {
+ for (int idx : *dirtyIndices)
+ constants[shader.shaderInfo.variables.at(idx).offset].value = shader.varData.at(idx).value;
+ }
+}
+
+void QSGD3D12ShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &shader)
+{
+ for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) {
+ const auto &var(shader.shaderInfo.variables.at(i));
+ if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
+ Q_ASSERT(shader.varData.at(i).specialType == QSGShaderEffectNode::VariableData::Unused);
+ samplers.insert(var.bindPoint);
+ }
+ }
+}
+
+void QSGD3D12ShaderLinker::feedTextures(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices)
+{
+ if (!dirtyIndices) {
+ for (int i = 0; i < shader.shaderInfo.variables.count(); ++i) {
+ const auto &var(shader.shaderInfo.variables.at(i));
+ const auto &vd(shader.varData.at(i));
+ if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) {
+ Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Source);
+ textures.insert(var.bindPoint, vd.value);
+ textureNameMap.insert(var.name, var.bindPoint);
+ }
+ }
+ } else {
+ for (int idx : *dirtyIndices) {
+ const auto &var(shader.shaderInfo.variables.at(idx));
+ const auto &vd(shader.varData.at(idx));
+ textures.insert(var.bindPoint, vd.value);
+ textureNameMap.insert(var.name, var.bindPoint);
+ }
+ }
+}
+
+void QSGD3D12ShaderLinker::linkTextureSubRects()
+{
+ // feedConstants stores <name> in Constant::value for subrect entries. Now
+ // that both constants and textures are known, replace the name with the
+ // texture bind point.
+ for (Constant &c : constants) {
+ if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
+ if (c.value.type() == QMetaType::QByteArray) {
+ const QByteArray name = c.value.toByteArray();
+ if (!textureNameMap.contains(name))
+ qWarning("ShaderEffect: qt_SubRect_%s refers to unknown source texture", qPrintable(name));
+ c.value = textureNameMap[name];
+ }
+ }
+ }
+}
+
+void QSGD3D12ShaderLinker::dump()
+{
+ if (error) {
+ qDebug() << "Failed to generate program data";
+ return;
+ }
+ qDebug() << "Combined shader data" << vs.size() << fs.size() << "cbuffer size" << constantBufferSize;
+ qDebug() << " - constants" << constants;
+ qDebug() << " - samplers" << samplers;
+ qDebug() << " - textures" << textures;
+}
+
+QDebug operator<<(QDebug debug, const QSGD3D12ShaderLinker::Constant &c)
+{
+ QDebugStateSaver saver(debug);
+ debug.space();
+ debug << "size" << c.size;
+ if (c.specialType != QSGShaderEffectNode::VariableData::None)
+ debug << "special" << c.specialType;
+ else
+ debug << "value" << c.value;
+ return debug;
+}
+
+QSGD3D12ShaderEffectMaterial::QSGD3D12ShaderEffectMaterial(QSGD3D12ShaderEffectNode *node)
+ : node(node)
+{
+ setFlag(Blending | RequiresFullMatrix, true); // may be changed in sync()
+}
+
+QSGD3D12ShaderEffectMaterial::~QSGD3D12ShaderEffectMaterial()
+{
+ delete dummy;
+}
+
+struct QSGD3D12ShaderMaterialTypeCache
+{
+ QSGMaterialType *get(const QByteArray &vs, const QByteArray &fs);
+ void reset() { qDeleteAll(m_types); m_types.clear(); }
+
+ struct Key {
+ QByteArray blob[2];
+ Key() { }
+ Key(const QByteArray &vs, const QByteArray &fs) { blob[0] = vs; blob[1] = fs; }
+ bool operator==(const Key &other) const {
+ return blob[0] == other.blob[0] && blob[1] == other.blob[1];
+ }
+ };
+ QHash<Key, QSGMaterialType *> m_types;
+};
+
+uint qHash(const QSGD3D12ShaderMaterialTypeCache::Key &key, uint seed = 0)
+{
+ uint hash = seed;
+ for (int i = 0; i < 2; ++i)
+ hash = hash * 31337 + qHash(key.blob[i]);
+ return hash;
+}
+
+QSGMaterialType *QSGD3D12ShaderMaterialTypeCache::get(const QByteArray &vs, const QByteArray &fs)
+{
+ const Key k(vs, fs);
+ if (m_types.contains(k))
+ return m_types.value(k);
+
+ QSGMaterialType *t = new QSGMaterialType;
+ m_types.insert(k, t);
+ return t;
+}
+
+Q_GLOBAL_STATIC(QSGD3D12ShaderMaterialTypeCache, shaderMaterialTypeCache)
+
+void QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache()
+{
+ shaderMaterialTypeCache()->reset();
+}
+
+QSGMaterialType *QSGD3D12ShaderEffectMaterial::type() const
+{
+ return mtype;
+}
+
+static bool hasAtlasTexture(const QVector<QSGTextureProvider *> &textureProviders)
+{
+ for (int i = 0; i < textureProviders.count(); ++i) {
+ QSGTextureProvider *t = textureProviders.at(i);
+ if (t && t->texture() && t->texture()->isAtlasTexture())
+ return true;
+ }
+ return false;
+}
+
+int QSGD3D12ShaderEffectMaterial::compare(const QSGMaterial *other) const
+{
+ Q_ASSERT(other && type() == other->type());
+ const QSGD3D12ShaderEffectMaterial *o = static_cast<const QSGD3D12ShaderEffectMaterial *>(other);
+
+ if (int diff = cullMode - o->cullMode)
+ return diff;
+
+ if (int diff = textureProviders.count() - o->textureProviders.count())
+ return diff;
+
+ if (linker.constants != o->linker.constants)
+ return 1;
+
+ if ((hasAtlasTexture(textureProviders) && !geometryUsesTextureSubRect)
+ || (hasAtlasTexture(o->textureProviders) && !o->geometryUsesTextureSubRect))
+ return 1;
+
+ for (int i = 0; i < textureProviders.count(); ++i) {
+ QSGTextureProvider *tp1 = textureProviders.at(i);
+ QSGTextureProvider *tp2 = o->textureProviders.at(i);
+ if (!tp1 || !tp2)
+ return tp1 == tp2 ? 0 : 1;
+ QSGTexture *t1 = tp1->texture();
+ QSGTexture *t2 = tp2->texture();
+ if (!t1 || !t2)
+ return t1 == t2 ? 0 : 1;
+ if (int diff = t1->textureId() - t2->textureId())
+ return diff;
+ }
+
+ return 0;
+}
+
+int QSGD3D12ShaderEffectMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(linker.constantBufferSize);
+}
+
+void QSGD3D12ShaderEffectMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->shaders.vs = reinterpret_cast<const quint8 *>(linker.vs.constData());
+ pipelineState->shaders.vsSize = linker.vs.size();
+ pipelineState->shaders.ps = reinterpret_cast<const quint8 *>(linker.fs.constData());
+ pipelineState->shaders.psSize = linker.fs.size();
+
+ pipelineState->shaders.rootSig.textureViewCount = textureProviders.count();
+}
+
+static inline QColor qsg_premultiply_color(const QColor &c)
+{
+ return QColor::fromRgbF(c.redF() * c.alphaF(), c.greenF() * c.alphaF(), c.blueF() * c.alphaF(), c.alphaF());
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12ShaderEffectMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = 0;
+ quint8 *p = constantBuffer;
+
+ for (auto it = linker.constants.constBegin(), itEnd = linker.constants.constEnd(); it != itEnd; ++it) {
+ quint8 *dst = p + it.key();
+ const QSGD3D12ShaderLinker::Constant &c(it.value());
+ if (c.specialType == QSGShaderEffectNode::VariableData::Opacity) {
+ if (state.isOpacityDirty()) {
+ const float f = state.opacity();
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, &f, sizeof(f));
+ r |= UpdatedConstantBuffer;
+ }
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) {
+ if (state.isMatrixDirty()) {
+ const int sz = 16 * sizeof(float);
+ Q_ASSERT(sz == c.size);
+ memcpy(dst, state.combinedMatrix().constData(), sz);
+ r |= UpdatedConstantBuffer;
+ }
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
+ // float4
+ QRectF subRect(0, 0, 1, 1);
+ int srcBindPoint = c.value.toInt(); // filled in by linkTextureSubRects
+ if (QSGTexture *t = textureProviders.at(srcBindPoint)->texture())
+ subRect = t->normalizedTextureSubRect();
+ const float f[4] = { float(subRect.x()), float(subRect.y()),
+ float(subRect.width()), float(subRect.height()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ } else if (c.specialType == QSGShaderEffectNode::VariableData::None) {
+ r |= UpdatedConstantBuffer;
+ switch (c.value.type()) {
+ case QMetaType::QColor: {
+ const QColor v = qsg_premultiply_color(qvariant_cast<QColor>(c.value));
+ const float f[4] = { float(v.redF()), float(v.greenF()), float(v.blueF()), float(v.alphaF()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::Float: {
+ const float f = qvariant_cast<float>(c.value);
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, &f, sizeof(f));
+ break;
+ }
+ case QMetaType::Double: {
+ const float f = float(qvariant_cast<double>(c.value));
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, &f, sizeof(f));
+ break;
+ }
+ case QMetaType::Int: {
+ const int i = c.value.toInt();
+ Q_ASSERT(sizeof(i) == c.size);
+ memcpy(dst, &i, sizeof(i));
+ break;
+ }
+ case QMetaType::Bool: {
+ const bool b = c.value.toBool();
+ Q_ASSERT(sizeof(b) == c.size);
+ memcpy(dst, &b, sizeof(b));
+ break;
+ }
+ case QMetaType::QTransform: { // float3x3
+ const QTransform v = qvariant_cast<QTransform>(c.value);
+ const float m[3][3] = {
+ { float(v.m11()), float(v.m12()), float(v.m13()) },
+ { float(v.m21()), float(v.m22()), float(v.m23()) },
+ { float(v.m31()), float(v.m32()), float(v.m33()) }
+ };
+ Q_ASSERT(sizeof(m) == c.size);
+ memcpy(dst, m[0], sizeof(m));
+ break;
+ }
+ case QMetaType::QSize:
+ case QMetaType::QSizeF: { // float2
+ const QSizeF v = c.value.toSizeF();
+ const float f[2] = { float(v.width()), float(v.height()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QPoint:
+ case QMetaType::QPointF: { // float2
+ const QPointF v = c.value.toPointF();
+ const float f[2] = { float(v.x()), float(v.y()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QRect:
+ case QMetaType::QRectF: { // float4
+ const QRectF v = c.value.toRectF();
+ const float f[4] = { float(v.x()), float(v.y()), float(v.width()), float(v.height()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QVector2D: { // float2
+ const QVector2D v = qvariant_cast<QVector2D>(c.value);
+ const float f[2] = { float(v.x()), float(v.y()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QVector3D: { // float3
+ const QVector3D v = qvariant_cast<QVector3D>(c.value);
+ const float f[3] = { float(v.x()), float(v.y()), float(v.z()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QVector4D: { // float4
+ const QVector4D v = qvariant_cast<QVector4D>(c.value);
+ const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.w()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QQuaternion: { // float4
+ const QQuaternion v = qvariant_cast<QQuaternion>(c.value);
+ const float f[4] = { float(v.x()), float(v.y()), float(v.z()), float(v.scalar()) };
+ Q_ASSERT(sizeof(f) == c.size);
+ memcpy(dst, f, sizeof(f));
+ break;
+ }
+ case QMetaType::QMatrix4x4: { // float4x4
+ const QMatrix4x4 v = qvariant_cast<QMatrix4x4>(c.value);
+ const int sz = 16 * sizeof(float);
+ Q_ASSERT(sz == c.size);
+ memcpy(dst, v.constData(), sz);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ for (int i = 0; i < textureProviders.count(); ++i) {
+ QSGTextureProvider *tp = textureProviders[i];
+ QSGD3D12TextureView &tv(pipelineState->shaders.rootSig.textureViews[i]);
+ if (tp) {
+ if (QSGTexture *t = tp->texture()) {
+ if (t->isAtlasTexture() && !geometryUsesTextureSubRect) {
+ QSGTexture *newTexture = t->removedFromAtlas();
+ if (newTexture)
+ t = newTexture;
+ }
+ tv.filter = t->filtering() == QSGTexture::Linear
+ ? QSGD3D12TextureView::FilterLinear : QSGD3D12TextureView::FilterNearest;
+ tv.addressModeHoriz = t->horizontalWrapMode() == QSGTexture::ClampToEdge
+ ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap;
+ tv.addressModeVert = t->verticalWrapMode() == QSGTexture::ClampToEdge
+ ? QSGD3D12TextureView::AddressClamp : QSGD3D12TextureView::AddressWrap;
+ t->bind();
+ continue;
+ }
+ }
+ if (!dummy) {
+ dummy = new QSGD3D12Texture(node->renderContext()->engine());
+ QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
+ img.fill(0);
+ dummy->create(img, QSGRenderContext::CreateTexture_Alpha);
+ }
+ tv.filter = QSGD3D12TextureView::FilterNearest;
+ tv.addressModeHoriz = QSGD3D12TextureView::AddressWrap;
+ tv.addressModeVert = QSGD3D12TextureView::AddressWrap;
+ dummy->bind();
+ }
+
+ switch (cullMode) {
+ case QSGShaderEffectNode::FrontFaceCulling:
+ pipelineState->cullMode = QSGD3D12PipelineState::CullFront;
+ break;
+ case QSGShaderEffectNode::BackFaceCulling:
+ pipelineState->cullMode = QSGD3D12PipelineState::CullBack;
+ break;
+ default:
+ pipelineState->cullMode = QSGD3D12PipelineState::CullNone;
+ break;
+ }
+
+ return r;
+}
+
+void QSGD3D12ShaderEffectMaterial::updateTextureProviders(bool layoutChange)
+{
+ if (layoutChange) {
+ for (QSGTextureProvider *tp : textureProviders) {
+ if (tp) {
+ QObject::disconnect(tp, SIGNAL(textureChanged()), node,
+ SLOT(handleTextureChange()));
+ QObject::disconnect(tp, SIGNAL(destroyed(QObject*)), node,
+ SLOT(handleTextureProviderDestroyed(QObject*)));
+ }
+ }
+
+ textureProviders.fill(nullptr, linker.textures.count());
+ }
+
+ for (auto it = linker.textures.constBegin(), itEnd = linker.textures.constEnd(); it != itEnd; ++it) {
+ const int bindPoint = it.key();
+ // Now that the linker has merged the textures, we can switch over to a
+ // simple vector indexed by the binding point for textureProviders.
+ Q_ASSERT(bindPoint >= 0 && bindPoint < textureProviders.count());
+ QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(it.value()));
+ QSGTextureProvider *newProvider = source && source->isTextureProvider() ? source->textureProvider() : nullptr;
+ QSGTextureProvider *&activeProvider(textureProviders[bindPoint]);
+ if (newProvider != activeProvider) {
+ if (activeProvider) {
+ QObject::disconnect(activeProvider, SIGNAL(textureChanged()), node,
+ SLOT(handleTextureChange()));
+ QObject::disconnect(activeProvider, SIGNAL(destroyed(QObject*)), node,
+ SLOT(handleTextureProviderDestroyed(QObject*)));
+ }
+ if (newProvider) {
+ Q_ASSERT_X(newProvider->thread() == QThread::currentThread(),
+ "QSGD3D12ShaderEffectMaterial::updateTextureProviders",
+ "Texture provider must belong to the rendering thread");
+ QObject::connect(newProvider, SIGNAL(textureChanged()), node, SLOT(handleTextureChange()));
+ QObject::connect(newProvider, SIGNAL(destroyed(QObject*)), node,
+ SLOT(handleTextureProviderDestroyed(QObject*)));
+ } else {
+ const char *typeName = source ? source->metaObject()->className() : it.value().typeName();
+ qWarning("ShaderEffect: Texture t%d is not assigned a valid texture provider (%s).",
+ bindPoint, typeName);
+ }
+ activeProvider = newProvider;
+ }
+ }
+}
+
+QSGD3D12ShaderEffectNode::QSGD3D12ShaderEffectNode(QSGD3D12RenderContext *rc, QSGD3D12GuiThreadShaderEffectManager *mgr)
+ : QSGShaderEffectNode(mgr),
+ m_rc(rc),
+ m_mgr(mgr),
+ m_material(this)
+{
+ setFlag(UsePreprocess, true);
+ setMaterial(&m_material);
+}
+
+QRectF QSGD3D12ShaderEffectNode::updateNormalizedTextureSubRect(bool supportsAtlasTextures)
+{
+ QRectF srcRect(0, 0, 1, 1);
+ bool geometryUsesTextureSubRect = false;
+ if (supportsAtlasTextures && m_material.textureProviders.count() == 1) {
+ QSGTextureProvider *provider = m_material.textureProviders.at(0);
+ if (provider->texture()) {
+ srcRect = provider->texture()->normalizedTextureSubRect();
+ geometryUsesTextureSubRect = true;
+ }
+ }
+
+ if (m_material.geometryUsesTextureSubRect != geometryUsesTextureSubRect) {
+ m_material.geometryUsesTextureSubRect = geometryUsesTextureSubRect;
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ return srcRect;
+}
+
+void QSGD3D12ShaderEffectNode::syncMaterial(SyncData *syncData)
+{
+ if (Q_UNLIKELY(debug_shader()))
+ qDebug() << "shadereffect node sync" << syncData->dirty;
+
+ if (bool(m_material.flags() & QSGMaterial::Blending) != syncData->blending) {
+ m_material.setFlag(QSGMaterial::Blending, syncData->blending);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (m_material.cullMode != syncData->cullMode) {
+ m_material.cullMode = syncData->cullMode;
+ markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (syncData->dirty & QSGShaderEffectNode::DirtyShaders) {
+ QByteArray vertBlob, fragBlob;
+
+ m_material.hasCustomVertexShader = syncData->vertex.shader->hasShaderCode;
+ if (m_material.hasCustomVertexShader) {
+ vertBlob = syncData->vertex.shader->shaderInfo.blob;
+ } else {
+ vertBlob = QByteArray::fromRawData(reinterpret_cast<const char *>(g_VS_DefaultShaderEffect),
+ sizeof(g_VS_DefaultShaderEffect));
+ }
+
+ m_material.hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode;
+ if (m_material.hasCustomFragmentShader) {
+ fragBlob = syncData->fragment.shader->shaderInfo.blob;
+ } else {
+ fragBlob = QByteArray::fromRawData(reinterpret_cast<const char *>(g_PS_DefaultShaderEffect),
+ sizeof(g_PS_DefaultShaderEffect));
+ }
+
+ m_material.mtype = shaderMaterialTypeCache()->get(vertBlob, fragBlob);
+ m_material.linker.reset(vertBlob, fragBlob);
+
+ if (m_material.hasCustomVertexShader) {
+ m_material.linker.feedVertexInput(*syncData->vertex.shader);
+ m_material.linker.feedConstants(*syncData->vertex.shader);
+ } else {
+ QSGShaderEffectNode::ShaderData defaultSD;
+ defaultSD.shaderInfo.blob = vertBlob;
+ defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex;
+
+ QSGGuiThreadShaderEffectManager::ShaderInfo::InputParameter ip;
+ ip.semanticName = QByteArrayLiteral("POSITION");
+ defaultSD.shaderInfo.inputParameters.append(ip);
+ ip.semanticName = QByteArrayLiteral("TEXCOORD");
+ defaultSD.shaderInfo.inputParameters.append(ip);
+
+ // { float4x4 qt_Matrix; float qt_Opacity; } where only the matrix is used
+ QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
+ v.name = QByteArrayLiteral("qt_Matrix");
+ v.offset = 0;
+ v.size = 16 * sizeof(float);
+ defaultSD.shaderInfo.variables.append(v);
+ QSGShaderEffectNode::VariableData vd;
+ vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
+ defaultSD.varData.append(vd);
+
+ defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
+
+ m_material.linker.feedVertexInput(defaultSD);
+ m_material.linker.feedConstants(defaultSD);
+ }
+
+ m_material.linker.feedSamplers(*syncData->vertex.shader);
+ m_material.linker.feedTextures(*syncData->vertex.shader);
+
+ if (m_material.hasCustomFragmentShader) {
+ m_material.linker.feedConstants(*syncData->fragment.shader);
+ } else {
+ QSGShaderEffectNode::ShaderData defaultSD;
+ defaultSD.shaderInfo.blob = fragBlob;
+ defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
+
+ // { float4x4 qt_Matrix; float qt_Opacity; } where only the opacity is used
+ QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
+ v.name = QByteArrayLiteral("qt_Opacity");
+ v.offset = 16 * sizeof(float);
+ v.size = sizeof(float);
+ defaultSD.shaderInfo.variables.append(v);
+ QSGShaderEffectNode::VariableData vd;
+ vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
+ defaultSD.varData.append(vd);
+
+ v.name = QByteArrayLiteral("source");
+ v.bindPoint = 0;
+ v.type = QSGGuiThreadShaderEffectManager::ShaderInfo::Texture;
+ defaultSD.shaderInfo.variables.append(v);
+ vd.specialType = QSGShaderEffectNode::VariableData::Source;
+ defaultSD.varData.append(vd);
+
+ v.name = QByteArrayLiteral("sourceSampler");
+ v.bindPoint = 0;
+ v.type = QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
+ defaultSD.shaderInfo.variables.append(v);
+ vd.specialType = QSGShaderEffectNode::VariableData::Unused;
+ defaultSD.varData.append(vd);
+
+ defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
+
+ m_material.linker.feedConstants(defaultSD);
+ m_material.linker.feedSamplers(defaultSD);
+ m_material.linker.feedTextures(defaultSD);
+ }
+
+ // While this may seem unnecessary for the built-in shaders, the value
+ // of 'source' is still in there and we have to process it.
+ m_material.linker.feedSamplers(*syncData->fragment.shader);
+ m_material.linker.feedTextures(*syncData->fragment.shader);
+
+ m_material.linker.linkTextureSubRects();
+
+ m_material.updateTextureProviders(true);
+
+ markDirty(QSGNode::DirtyMaterial);
+
+ if (Q_UNLIKELY(debug_shader()))
+ m_material.linker.dump();
+ } else {
+ if (syncData->dirty & QSGShaderEffectNode::DirtyShaderConstant) {
+ if (!syncData->vertex.dirtyConstants->isEmpty())
+ m_material.linker.feedConstants(*syncData->vertex.shader, syncData->vertex.dirtyConstants);
+ if (!syncData->fragment.dirtyConstants->isEmpty())
+ m_material.linker.feedConstants(*syncData->fragment.shader, syncData->fragment.dirtyConstants);
+ markDirty(QSGNode::DirtyMaterial);
+ if (Q_UNLIKELY(debug_shader()))
+ m_material.linker.dump();
+ }
+
+ if (syncData->dirty & QSGShaderEffectNode::DirtyShaderTexture) {
+ if (!syncData->vertex.dirtyTextures->isEmpty())
+ m_material.linker.feedTextures(*syncData->vertex.shader, syncData->vertex.dirtyTextures);
+ if (!syncData->fragment.dirtyTextures->isEmpty())
+ m_material.linker.feedTextures(*syncData->fragment.shader, syncData->fragment.dirtyTextures);
+ m_material.linker.linkTextureSubRects();
+ m_material.updateTextureProviders(false);
+ markDirty(QSGNode::DirtyMaterial);
+ if (Q_UNLIKELY(debug_shader()))
+ m_material.linker.dump();
+ }
+ }
+
+ if (bool(m_material.flags() & QSGMaterial::RequiresFullMatrix) != m_material.hasCustomVertexShader) {
+ m_material.setFlag(QSGMaterial::RequiresFullMatrix, m_material.hasCustomVertexShader);
+ markDirty(QSGNode::DirtyMaterial);
+ }
+}
+
+void QSGD3D12ShaderEffectNode::handleTextureChange()
+{
+ markDirty(QSGNode::DirtyMaterial);
+ emit m_mgr->textureChanged();
+}
+
+void QSGD3D12ShaderEffectNode::handleTextureProviderDestroyed(QObject *object)
+{
+ for (QSGTextureProvider *&tp : m_material.textureProviders) {
+ if (tp == object)
+ tp = nullptr;
+ }
+}
+
+void QSGD3D12ShaderEffectNode::preprocess()
+{
+ for (QSGTextureProvider *tp : m_material.textureProviders) {
+ if (tp) {
+ if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(tp->texture()))
+ texture->updateTexture();
+ }
+ }
+}
+
+bool QSGD3D12GuiThreadShaderEffectManager::hasSeparateSamplerAndTextureObjects() const
+{
+ return true;
+}
+
+QString QSGD3D12GuiThreadShaderEffectManager::log() const
+{
+ return m_log;
+}
+
+QSGGuiThreadShaderEffectManager::Status QSGD3D12GuiThreadShaderEffectManager::status() const
+{
+ return m_status;
+}
+
+struct RefGuard {
+ RefGuard(IUnknown *p) : p(p) { }
+ ~RefGuard() { p->Release(); }
+ IUnknown *p;
+};
+
+class QSGD3D12ShaderCompileTask : public QRunnable
+{
+public:
+ QSGD3D12ShaderCompileTask(QSGD3D12GuiThreadShaderEffectManager *mgr,
+ QSGD3D12GuiThreadShaderEffectManager::ShaderInfo::Type typeHint,
+ const QByteArray &src,
+ QSGD3D12GuiThreadShaderEffectManager::ShaderInfo *result)
+ : mgr(mgr), typeHint(typeHint), src(src), result(result) { }
+
+ void run() override;
+
+private:
+ QSGD3D12GuiThreadShaderEffectManager *mgr;
+ QSGD3D12GuiThreadShaderEffectManager::ShaderInfo::Type typeHint;
+ QByteArray src;
+ QSGD3D12GuiThreadShaderEffectManager::ShaderInfo *result;
+};
+
+void QSGD3D12ShaderCompileTask::run()
+{
+ const char *target = typeHint == QSGD3D12GuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? "vs_5_0" : "ps_5_0";
+ ID3DBlob *bytecode = nullptr;
+ ID3DBlob *errors = nullptr;
+ HRESULT hr = D3DCompile(src.constData(), src.size(), nullptr, nullptr, nullptr,
+ "main", target, 0, 0, &bytecode, &errors);
+ if (FAILED(hr) || !bytecode) {
+ qWarning("HLSL shader compilation failed: 0x%x", hr);
+ if (errors) {
+ mgr->m_log += QString::fromUtf8(static_cast<const char *>(errors->GetBufferPointer()), errors->GetBufferSize());
+ errors->Release();
+ }
+ mgr->m_status = QSGGuiThreadShaderEffectManager::Error;
+ emit mgr->shaderCodePrepared(false, typeHint, src, result);
+ emit mgr->logAndStatusChanged();
+ return;
+ }
+
+ result->blob.resize(bytecode->GetBufferSize());
+ memcpy(result->blob.data(), bytecode->GetBufferPointer(), result->blob.size());
+ bytecode->Release();
+
+ const bool ok = mgr->reflect(result);
+ mgr->m_status = ok ? QSGGuiThreadShaderEffectManager::Compiled : QSGGuiThreadShaderEffectManager::Error;
+ emit mgr->shaderCodePrepared(ok, typeHint, src, result);
+ emit mgr->logAndStatusChanged();
+}
+
+static const int BYTECODE_MAGIC = 0x43425844; // 'DXBC'
+
+void QSGD3D12GuiThreadShaderEffectManager::prepareShaderCode(ShaderInfo::Type typeHint, const QByteArray &src, ShaderInfo *result)
+{
+ // The D3D12 backend's ShaderEffect implementation supports both HLSL
+ // source strings and bytecode or source in files as input. Bytecode is
+ // strongly recommended, but in order to make ShaderEffect users' (and
+ // anything that stiches shader strings together dynamically, e.g.
+ // qtgraphicaleffects) life easier, and since we link to d3dcompiler
+ // anyways, compiling from source is also supported.
+
+ QByteArray shaderSourceCode = src;
+ QUrl srcUrl(QString::fromUtf8(src));
+ if (!srcUrl.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || srcUrl.isLocalFile()) {
+ if (!m_fileSelector) {
+ m_fileSelector = new QFileSelector(this);
+ m_fileSelector->setExtraSelectors(QStringList() << QStringLiteral("hlsl"));
+ }
+ const QString fn = m_fileSelector->select(QQmlFile::urlToLocalFileOrQrc(srcUrl));
+ QFile f(fn);
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning("ShaderEffect: Failed to read %s", qPrintable(fn));
+ emit shaderCodePrepared(false, typeHint, src, result);
+ return;
+ }
+ QByteArray blob = f.readAll();
+ f.close();
+ if (blob.size() > 4) {
+ const quint32 *p = reinterpret_cast<const quint32 *>(blob.constData());
+ if (*p == BYTECODE_MAGIC) {
+ // already compiled D3D bytecode, skip straight to reflection
+ result->blob = blob;
+ const bool ok = reflect(result);
+ m_status = ok ? Compiled : Error;
+ emit shaderCodePrepared(ok, typeHint, src, result);
+ emit logAndStatusChanged();
+ return;
+ }
+ // assume the file contained HLSL source code
+ shaderSourceCode = blob;
+ }
+ }
+
+ QThreadPool::globalInstance()->start(new QSGD3D12ShaderCompileTask(this, typeHint, shaderSourceCode, result));
+}
+
+bool QSGD3D12GuiThreadShaderEffectManager::reflect(ShaderInfo *result)
+{
+ 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;
+
+ result->constantDataSize = 0;
+
+ 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_shader()))
+ 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;
+ result->constantDataSize = bufDesc.Size;
+ 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;
+ }
+ // we report the full size of the buffer but only return variables that are actually used by this shader
+ if (!(varDesc.uFlags & D3D_SVF_USED))
+ continue;
+ 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_shader())) {
+ qDebug() << "Input:" << result->inputParameters;
+ qDebug() << "Variables:" << result->variables << "cbuffer size" << result->constantDataSize;
+ }
+
+ 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..ee17e59130
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12shadereffectnode_p.h
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12material_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12RenderContext;
+class QSGD3D12GuiThreadShaderEffectManager;
+class QSGD3D12ShaderEffectNode;
+class QSGD3D12Texture;
+class QFileSelector;
+
+class QSGD3D12ShaderLinker
+{
+public:
+ void reset(const QByteArray &vertBlob, const QByteArray &fragBlob);
+
+ void feedVertexInput(const QSGShaderEffectNode::ShaderData &shader);
+ void feedConstants(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices = nullptr);
+ void feedSamplers(const QSGShaderEffectNode::ShaderData &shader);
+ void feedTextures(const QSGShaderEffectNode::ShaderData &shader, const QSet<int> *dirtyIndices = nullptr);
+ void linkTextureSubRects();
+
+ void dump();
+
+ struct Constant {
+ uint size;
+ QSGShaderEffectNode::VariableData::SpecialType specialType;
+ QVariant value;
+ bool operator==(const Constant &other) const {
+ return size == other.size && specialType == other.specialType
+ && (specialType == QSGShaderEffectNode::VariableData::None ? value == other.value : true);
+ }
+ };
+
+ bool error;
+ QByteArray vs;
+ QByteArray fs;
+ uint constantBufferSize;
+ QHash<uint, Constant> constants; // offset -> Constant
+ QSet<int> samplers; // bindpoint
+ QHash<int, QVariant> textures; // bindpoint -> value (source ref)
+ QHash<QByteArray, int> textureNameMap; // name -> bindpoint
+};
+
+QDebug operator<<(QDebug debug, const QSGD3D12ShaderLinker::Constant &c);
+
+class QSGD3D12ShaderEffectMaterial : public QSGD3D12Material
+{
+public:
+ QSGD3D12ShaderEffectMaterial(QSGD3D12ShaderEffectNode *node);
+ ~QSGD3D12ShaderEffectMaterial();
+
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+ void updateTextureProviders(bool layoutChange);
+
+ QSGD3D12ShaderEffectNode *node;
+ bool valid = false;
+ QSGShaderEffectNode::CullMode cullMode = QSGShaderEffectNode::NoCulling;
+ bool hasCustomVertexShader = false;
+ bool hasCustomFragmentShader = false;
+ QSGD3D12ShaderLinker linker;
+ QSGMaterialType *mtype = nullptr;
+ QVector<QSGTextureProvider *> textureProviders;
+ QSGD3D12Texture *dummy = nullptr;
+ bool geometryUsesTextureSubRect = false;
+};
+
+class QSGD3D12ShaderEffectNode : public QObject, public QSGShaderEffectNode
+{
+ Q_OBJECT
+
+public:
+ QSGD3D12ShaderEffectNode(QSGD3D12RenderContext *rc, QSGD3D12GuiThreadShaderEffectManager *mgr);
+
+ QRectF updateNormalizedTextureSubRect(bool supportsAtlasTextures) override;
+ void syncMaterial(SyncData *syncData) override;
+
+ static void cleanupMaterialTypeCache();
+
+ void preprocess() override;
+
+ QSGD3D12RenderContext *renderContext() { return m_rc; }
+
+private Q_SLOTS:
+ void handleTextureChange();
+ void handleTextureProviderDestroyed(QObject *object);
+
+private:
+ QSGD3D12RenderContext *m_rc;
+ QSGD3D12GuiThreadShaderEffectManager *m_mgr;
+ QSGD3D12ShaderEffectMaterial m_material;
+};
+
+class QSGD3D12GuiThreadShaderEffectManager : public QSGGuiThreadShaderEffectManager
+{
+public:
+ bool hasSeparateSamplerAndTextureObjects() const override;
+
+ QString log() const override;
+ Status status() const override;
+
+ void prepareShaderCode(ShaderInfo::Type typeHint, const QByteArray &src, ShaderInfo *result) override;
+
+private:
+ bool reflect(ShaderInfo *result);
+ QString m_log;
+ Status m_status = Uncompiled;
+ QFileSelector *m_fileSelector = nullptr;
+
+ friend class QSGD3D12ShaderCompileTask;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12SHADEREFFECTNODE_P_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12spritenode.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12spritenode.cpp
new file mode 100644
index 0000000000..807fbcdcec
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12spritenode.cpp
@@ -0,0 +1,314 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12spritenode_p.h"
+#include "qsgd3d12material_p.h"
+
+#include "vs_sprite.hlslh"
+#include "ps_sprite.hlslh"
+
+QT_BEGIN_NAMESPACE
+
+struct SpriteVertex
+{
+ float x;
+ float y;
+ float tx;
+ float ty;
+};
+
+struct SpriteVertices
+{
+ SpriteVertex v1;
+ SpriteVertex v2;
+ SpriteVertex v3;
+ SpriteVertex v4;
+};
+
+class QSGD3D12SpriteMaterial : public QSGD3D12Material
+{
+public:
+ QSGD3D12SpriteMaterial();
+ ~QSGD3D12SpriteMaterial();
+
+ QSGMaterialType *type() const override { static QSGMaterialType type; return &type; }
+
+ int compare(const QSGMaterial *other) const override
+ {
+ return this - static_cast<const QSGD3D12SpriteMaterial *>(other);
+ }
+
+ int constantBufferSize() const override;
+ void preparePipeline(QSGD3D12PipelineState *pipelineState) override;
+ UpdateResults updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *pipelineState,
+ ExtraState *extraState,
+ quint8 *constantBuffer) override;
+
+ QSGTexture *texture;
+
+ float animT;
+ float animX1;
+ float animY1;
+ float animX2;
+ float animY2;
+ float animW;
+ float animH;
+};
+
+QSGD3D12SpriteMaterial::QSGD3D12SpriteMaterial()
+ : texture(nullptr),
+ animT(0.0f),
+ animX1(0.0f),
+ animY1(0.0f),
+ animX2(0.0f),
+ animY2(0.0f),
+ animW(1.0f),
+ animH(1.0f)
+{
+ setFlag(Blending, true);
+}
+
+QSGD3D12SpriteMaterial::~QSGD3D12SpriteMaterial()
+{
+ delete texture;
+}
+
+static const int SPRITE_CB_SIZE_0 = 16 * sizeof(float); // float4x4
+static const int SPRITE_CB_SIZE_1 = 4 * sizeof(float); // float4
+static const int SPRITE_CB_SIZE_2 = 3 * sizeof(float); // float3
+static const int SPRITE_CB_SIZE_3 = sizeof(float); // float
+static const int SPRITE_CB_SIZE = SPRITE_CB_SIZE_0 + SPRITE_CB_SIZE_1 + SPRITE_CB_SIZE_2 + SPRITE_CB_SIZE_3;
+
+int QSGD3D12SpriteMaterial::constantBufferSize() const
+{
+ return QSGD3D12Engine::alignedConstantBufferSize(SPRITE_CB_SIZE);
+}
+
+void QSGD3D12SpriteMaterial::preparePipeline(QSGD3D12PipelineState *pipelineState)
+{
+ pipelineState->shaders.vs = g_VS_Sprite;
+ pipelineState->shaders.vsSize = sizeof(g_VS_Sprite);
+ pipelineState->shaders.ps = g_PS_Sprite;
+ pipelineState->shaders.psSize = sizeof(g_PS_Sprite);
+
+ pipelineState->shaders.rootSig.textureViewCount = 1;
+}
+
+QSGD3D12Material::UpdateResults QSGD3D12SpriteMaterial::updatePipeline(const QSGD3D12MaterialRenderState &state,
+ QSGD3D12PipelineState *,
+ ExtraState *,
+ quint8 *constantBuffer)
+{
+ QSGD3D12Material::UpdateResults r = UpdatedConstantBuffer;
+ quint8 *p = constantBuffer;
+
+ if (state.isMatrixDirty())
+ memcpy(p, state.combinedMatrix().constData(), SPRITE_CB_SIZE_0);
+ p += SPRITE_CB_SIZE_0;
+
+ {
+ const float v[] = { animX1, animY1, animX2, animY2 };
+ memcpy(p, v, SPRITE_CB_SIZE_1);
+ }
+ p += SPRITE_CB_SIZE_1;
+
+ {
+ const float v[] = { animW, animH, animT };
+ memcpy(p, v, SPRITE_CB_SIZE_2);
+ }
+ p += SPRITE_CB_SIZE_2;
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(p, &opacity, SPRITE_CB_SIZE_3);
+ }
+
+ texture->bind();
+
+ return r;
+}
+
+static QSGGeometry::Attribute Sprite_Attributes[] = {
+ QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
+};
+
+static QSGGeometry::AttributeSet Sprite_AttributeSet = { 2, 4 * sizeof(float), Sprite_Attributes };
+
+QSGD3D12SpriteNode::QSGD3D12SpriteNode()
+ : m_material(new QSGD3D12SpriteMaterial)
+ , m_geometryDirty(true)
+ , m_sheetSize(QSize(64, 64))
+{
+ m_geometry = new QSGGeometry(Sprite_AttributeSet, 4, 6);
+ m_geometry->setDrawingMode(QSGGeometry::DrawTriangles);
+
+ quint16 *indices = m_geometry->indexDataAsUShort();
+ indices[0] = 0;
+ indices[1] = 1;
+ indices[2] = 2;
+ indices[3] = 1;
+ indices[4] = 3;
+ indices[5] = 2;
+
+ setGeometry(m_geometry);
+ setMaterial(m_material);
+ setFlag(OwnsGeometry, true);
+ setFlag(OwnsMaterial, true);
+}
+
+void QSGD3D12SpriteNode::setTexture(QSGTexture *texture)
+{
+ m_material->texture = texture;
+ m_geometryDirty = true;
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12SpriteNode::setTime(float time)
+{
+ m_material->animT = time;
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12SpriteNode::setSourceA(const QPoint &source)
+{
+ if (m_sourceA != source) {
+ m_sourceA = source;
+ m_material->animX1 = static_cast<float>(source.x()) / m_sheetSize.width();
+ m_material->animY1 = static_cast<float>(source.y()) / m_sheetSize.height();
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGD3D12SpriteNode::setSourceB(const QPoint &source)
+{
+ if (m_sourceB != source) {
+ m_sourceB = source;
+ m_material->animX2 = static_cast<float>(source.x()) / m_sheetSize.width();
+ m_material->animY2 = static_cast<float>(source.y()) / m_sheetSize.height();
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGD3D12SpriteNode::setSpriteSize(const QSize &size)
+{
+ if (m_spriteSize != size) {
+ m_spriteSize = size;
+ m_material->animW = static_cast<float>(size.width()) / m_sheetSize.width();
+ m_material->animH = static_cast<float>(size.height()) / m_sheetSize.height();
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGD3D12SpriteNode::setSheetSize(const QSize &size)
+{
+ if (m_sheetSize != size) {
+ m_sheetSize = size;
+
+ // Update all dependent properties
+ m_material->animX1 = static_cast<float>(m_sourceA.x()) / m_sheetSize.width();
+ m_material->animY1 = static_cast<float>(m_sourceA.y()) / m_sheetSize.height();
+ m_material->animX2 = static_cast<float>(m_sourceB.x()) / m_sheetSize.width();
+ m_material->animY2 = static_cast<float>(m_sourceB.y()) / m_sheetSize.height();
+ m_material->animW = static_cast<float>(m_spriteSize.width()) / m_sheetSize.width();
+ m_material->animH = static_cast<float>(m_spriteSize.height()) / m_sheetSize.height();
+
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGD3D12SpriteNode::setSize(const QSizeF &size)
+{
+ if (m_size != size) {
+ m_size = size;
+ m_geometryDirty = true;
+ }
+}
+
+void QSGD3D12SpriteNode::setFiltering(QSGTexture::Filtering filtering)
+{
+ m_material->texture->setFiltering(filtering);
+ markDirty(DirtyMaterial);
+}
+
+void QSGD3D12SpriteNode::update()
+{
+ if (m_geometryDirty) {
+ m_geometryDirty = false;
+ updateGeometry();
+ }
+}
+
+void QSGD3D12SpriteNode::updateGeometry()
+{
+ if (!m_material->texture)
+ return;
+
+ SpriteVertices *p = static_cast<SpriteVertices *>(m_geometry->vertexData());
+ const QRectF texRect = m_material->texture->normalizedTextureSubRect();
+
+ p->v1.tx = texRect.topLeft().x();
+ p->v1.ty = texRect.topLeft().y();
+
+ p->v2.tx = texRect.topRight().x();
+ p->v2.ty = texRect.topRight().y();
+
+ p->v3.tx = texRect.bottomLeft().x();
+ p->v3.ty = texRect.bottomLeft().y();
+
+ p->v4.tx = texRect.bottomRight().x();
+ p->v4.ty = texRect.bottomRight().y();
+
+ p->v1.x = 0;
+ p->v1.y = 0;
+
+ p->v2.x = m_size.width();
+ p->v2.y = 0;
+
+ p->v3.x = 0;
+ p->v3.y = m_size.height();
+
+ p->v4.x = m_size.width();
+ p->v4.y = m_size.height();
+
+ markDirty(DirtyGeometry);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12spritenode_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12spritenode_p.h
new file mode 100644
index 0000000000..265bec7c78
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12spritenode_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** 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 QSGD3D12SPRITENODE_H
+#define QSGD3D12SPRITENODE_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>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12SpriteMaterial;
+
+class QSGD3D12SpriteNode : public QSGSpriteNode
+{
+public:
+ QSGD3D12SpriteNode();
+
+ void setTexture(QSGTexture *texture) override;
+ void setTime(float time) override;
+ void setSourceA(const QPoint &source) override;
+ void setSourceB(const QPoint &source) override;
+ void setSpriteSize(const QSize &size) override;
+ void setSheetSize(const QSize &size) override;
+ void setSize(const QSizeF &size) override;
+ void setFiltering(QSGTexture::Filtering filtering) override;
+ void update() override;
+
+private:
+ void updateGeometry();
+
+ QSGD3D12SpriteMaterial *m_material;
+ QSGGeometry *m_geometry;
+ bool m_geometryDirty;
+ QPoint m_sourceA;
+ QPoint m_sourceB;
+ QSize m_spriteSize;
+ QSize m_sheetSize;
+ QSizeF m_size;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12SPRITENODE_H
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12texture.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12texture.cpp
new file mode 100644
index 0000000000..a5f3eb7a31
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12texture.cpp
@@ -0,0 +1,145 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12texture_p.h"
+#include "qsgd3d12engine_p.h"
+#include <private/qsgcontext_p.h>
+
+QT_BEGIN_NAMESPACE
+
+#define RETAIN_IMAGE
+
+void QSGD3D12Texture::create(const QImage &image, uint flags)
+{
+ // ### atlas?
+
+ const bool alphaRequest = flags & QSGRenderContext::CreateTexture_Alpha;
+ m_alphaWanted = alphaRequest && image.hasAlphaChannel();
+
+ // The engine maps 8-bit formats to R8. This is fine for glyphs and such
+ // but may not be what apps expect for ordinary image data. The OpenGL
+ // implementation maps these to ARGB32_Pre so let's follow suit.
+ if (image.depth() != 8)
+ m_image = image;
+ else
+ m_image = image.convertToFormat(m_alphaWanted ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32);
+
+ m_id = m_engine->genTexture();
+ Q_ASSERT(m_id);
+
+ // We could kick off the texture creation and the async upload right here.
+ // Unfortunately we cannot tell at this stage if mipmaps will be enabled
+ // via an Image element's mipmap property...so defer to bind().
+ m_createPending = true;
+}
+
+QSGD3D12Texture::~QSGD3D12Texture()
+{
+ if (m_id)
+ m_engine->releaseTexture(m_id);
+}
+
+int QSGD3D12Texture::textureId() const
+{
+ return m_id;
+}
+
+QSize QSGD3D12Texture::textureSize() const
+{
+ return m_image.size();
+}
+
+bool QSGD3D12Texture::hasAlphaChannel() const
+{
+ return m_alphaWanted;
+}
+
+bool QSGD3D12Texture::hasMipmaps() const
+{
+ return mipmapFiltering() != QSGTexture::None;
+}
+
+QRectF QSGD3D12Texture::normalizedTextureSubRect() const
+{
+ return QRectF(0, 0, 1, 1);
+}
+
+void QSGD3D12Texture::bind()
+{
+ // Called when the texture material updates the pipeline state.
+
+ if (!m_createPending && hasMipmaps() != m_createdWithMipMaps) {
+#ifdef RETAIN_IMAGE
+ m_engine->releaseTexture(m_id);
+ m_id = m_engine->genTexture();
+ Q_ASSERT(m_id);
+ m_createPending = true;
+#else
+ // ### this can be made working some day (something similar to
+ // queueTextureResize) but skip for now
+ qWarning("D3D12: mipmap property cannot be changed once the texture is created");
+#endif
+ }
+
+ if (m_createPending) {
+ m_createPending = false;
+
+ QSGD3D12Engine::TextureCreateFlags createFlags = 0;
+ if (m_alphaWanted)
+ createFlags |= QSGD3D12Engine::TextureWithAlpha;
+
+ m_createdWithMipMaps = hasMipmaps();
+ if (m_createdWithMipMaps)
+ createFlags |= QSGD3D12Engine::TextureWithMipMaps;
+
+ m_engine->createTexture(m_id, m_image.size(), m_image.format(), createFlags);
+ m_engine->queueTextureUpload(m_id, m_image);
+
+#ifndef RETAIN_IMAGE
+ m_image = QImage();
+#endif
+ }
+
+ // Here we know that the texture is going to be used in the current frame
+ // by the next draw call. Notify the engine so that it can wait for
+ // possible pending uploads and set up the pipeline accordingly.
+ m_engine->useTexture(m_id);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12texture_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12texture_p.h
new file mode 100644
index 0000000000..3d0e226ddb
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12texture_p.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** 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 QSGD3D12TEXTURE_P_H
+#define QSGD3D12TEXTURE_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 <qsgtexture.h>
+#include <basetsd.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Engine;
+
+class QSGD3D12Texture : public QSGTexture
+{
+public:
+ QSGD3D12Texture(QSGD3D12Engine *engine) : m_engine(engine) { }
+ ~QSGD3D12Texture();
+
+ void create(const QImage &image, uint flags);
+
+ int textureId() const override;
+ QSize textureSize() const override;
+ bool hasAlphaChannel() const override;
+ bool hasMipmaps() const override;
+ QRectF normalizedTextureSubRect() const override;
+ void bind() override;
+
+protected:
+ QSGD3D12Engine *m_engine;
+ QImage m_image;
+ bool m_createPending = false;
+ bool m_createdWithMipMaps = false;
+ uint m_id = 0;
+ bool m_alphaWanted = false;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp
new file mode 100644
index 0000000000..a803f67380
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp
@@ -0,0 +1,1183 @@
+/****************************************************************************
+**
+** 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 "qsgd3d12threadedrenderloop_p.h"
+#include "qsgd3d12engine_p.h"
+#include "qsgd3d12context_p.h"
+#include "qsgd3d12rendercontext_p.h"
+#include "qsgd3d12shadereffectnode_p.h"
+#include <private/qsgrenderer_p.h>
+#include <private/qquickwindow_p.h>
+#include <private/qquickanimatorcontroller_p.h>
+#include <private/qquickprofiler_p.h>
+#include <private/qqmldebugserviceinterfaces_p.h>
+#include <private/qqmldebugconnector_p.h>
+#include <QElapsedTimer>
+#include <QQueue>
+#include <QGuiApplication>
+
+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(loop)
+DECLARE_DEBUG_VAR(time)
+
+
+// NOTE: The threaded renderloop is not currently safe to use in practice as it
+// is prone to deadlocks, in particular when multiple windows are active. This
+// is because DXGI's limitation of relying on the gui message pump in certain
+// cases. See
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ee417025(v=vs.85).aspx#multithreading_and_dxgi
+//
+// This means that if swap chain functions like create, release, and
+// potentially even Present, are called outside the gui thread, then the
+// application must ensure the gui thread does not ever block and wait for the
+// render thread - since on the render thread a DXGI call may be in turn
+// waiting for the gui thread to deliver a window message...
+//
+// Ensuring this is impossible with the current design where the gui thread
+// must block at certain points, waiting for the render thread. Qt moves out
+// rendering from the main thread, in order to make application's life easier,
+// whereas the typical DXGI-compatible model would require moving work, but not
+// windowing and presenting, out to additional threads.
+
+
+/*
+ The D3D render loop mostly mirrors the threaded OpenGL render loop.
+
+ There are two classes here. QSGD3D12ThreadedRenderLoop and
+ QSGD3D12RenderThread. All communication between the two is based on event
+ passing and we have a number of custom events.
+
+ Render loop is per process, render thread is per window. The
+ QSGD3D12RenderContext and QSGD3D12Engine are per window as well. The former
+ is created (but not owned) by QQuickWindow. The D3D device is per process.
+
+ In this implementation, the render thread is never blocked and the GUI
+ thread will initiate a polishAndSync which will block and wait for the
+ render thread to pick it up and release the block only after the render
+ thread is done syncing. The reason for this is:
+
+ 1. Clear blocking paradigm. We only have one real "block" point
+ (polishAndSync()) and all blocking is initiated by GUI and picked up by
+ Render at specific times based on events. This makes the execution
+ deterministic.
+
+ 2. Render does not have to interact with GUI. This is done so that the
+ render thread can run its own animation system which stays alive even when
+ the GUI thread is blocked doing I/O, object instantiation, QPainter-painting
+ or any other non-trivial task.
+
+ The render thread has affinity to the GUI thread until a window is shown.
+ From that moment and until the window is destroyed, it will have affinity to
+ the render thread. (moved back at the end of run for cleanup).
+ */
+
+// Passed from the RL to the RT when a window is removed obscured and should be
+// removed from the render loop.
+const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
+
+// Passed from the RL to RT when GUI has been locked, waiting for sync.
+const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
+
+// Passed by the RT to itself to trigger another render pass. This is typically
+// a result of QQuickWindow::update().
+const QEvent::Type WM_RequestRepaint = QEvent::Type(QEvent::User + 3);
+
+// Passed by the RL to the RT to maybe release resource if no windows are
+// rendering.
+const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
+
+// Passed by the RL to the RT when a QQuickWindow::grabWindow() is called.
+const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
+
+// Passed by the window when there is a render job to run.
+const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
+
+class QSGD3D12WindowEvent : public QEvent
+{
+public:
+ QSGD3D12WindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
+ QQuickWindow *window;
+};
+
+class QSGD3D12TryReleaseEvent : public QSGD3D12WindowEvent
+{
+public:
+ QSGD3D12TryReleaseEvent(QQuickWindow *win, bool destroy)
+ : QSGD3D12WindowEvent(win, WM_TryRelease), destroying(destroy) { }
+ bool destroying;
+};
+
+class QSGD3D12SyncEvent : public QSGD3D12WindowEvent
+{
+public:
+ QSGD3D12SyncEvent(QQuickWindow *c, bool inExpose, bool force)
+ : QSGD3D12WindowEvent(c, WM_RequestSync)
+ , size(c->size())
+ , dpr(c->effectiveDevicePixelRatio())
+ , syncInExpose(inExpose)
+ , forceRenderPass(force) { }
+ QSize size;
+ float dpr;
+ bool syncInExpose;
+ bool forceRenderPass;
+};
+
+class QSGD3D12GrabEvent : public QSGD3D12WindowEvent
+{
+public:
+ QSGD3D12GrabEvent(QQuickWindow *c, QImage *result)
+ : QSGD3D12WindowEvent(c, WM_Grab), image(result) { }
+ QImage *image;
+};
+
+class QSGD3D12JobEvent : public QSGD3D12WindowEvent
+{
+public:
+ QSGD3D12JobEvent(QQuickWindow *c, QRunnable *postedJob)
+ : QSGD3D12WindowEvent(c, WM_PostJob), job(postedJob) { }
+ ~QSGD3D12JobEvent() { delete job; }
+ QRunnable *job;
+};
+
+class QSGD3D12EventQueue : public QQueue<QEvent *>
+{
+public:
+ void addEvent(QEvent *e) {
+ mutex.lock();
+ enqueue(e);
+ if (waiting)
+ condition.wakeOne();
+ mutex.unlock();
+ }
+
+ QEvent *takeEvent(bool wait) {
+ mutex.lock();
+ if (isEmpty() && wait) {
+ waiting = true;
+ condition.wait(&mutex);
+ waiting = false;
+ }
+ QEvent *e = dequeue();
+ mutex.unlock();
+ return e;
+ }
+
+ bool hasMoreEvents() {
+ mutex.lock();
+ bool has = !isEmpty();
+ mutex.unlock();
+ return has;
+ }
+
+private:
+ QMutex mutex;
+ QWaitCondition condition;
+ bool waiting = false;
+};
+
+static inline int qsgrl_animation_interval()
+{
+ const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0;
+ return refreshRate < 1 ? 16 : int(1000 / refreshRate);
+}
+
+class QSGD3D12RenderThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ QSGD3D12RenderThread(QSGD3D12ThreadedRenderLoop *rl, QSGRenderContext *renderContext)
+ : renderLoop(rl)
+ {
+ rc = static_cast<QSGD3D12RenderContext *>(renderContext);
+ vsyncDelta = qsgrl_animation_interval();
+ }
+
+ ~QSGD3D12RenderThread()
+ {
+ delete rc;
+ }
+
+ bool event(QEvent *e);
+ void run();
+
+ void syncAndRender();
+ void sync(bool inExpose);
+
+ void requestRepaint()
+ {
+ if (sleeping)
+ stopEventProcessing = true;
+ if (exposedWindow)
+ pendingUpdate |= RepaintRequest;
+ }
+
+ void processEventsAndWaitForMore();
+ void processEvents();
+ void postEvent(QEvent *e);
+
+ enum UpdateRequest {
+ SyncRequest = 0x01,
+ RepaintRequest = 0x02,
+ ExposeRequest = 0x04 | RepaintRequest | SyncRequest
+ };
+
+ QSGD3D12Engine *engine = nullptr;
+ QSGD3D12ThreadedRenderLoop *renderLoop;
+ QSGD3D12RenderContext *rc;
+ QAnimationDriver *rtAnim = nullptr;
+ volatile bool active = false;
+ uint pendingUpdate = 0;
+ bool sleeping = false;
+ bool syncResultedInChanges = false;
+ float vsyncDelta;
+ QMutex mutex;
+ QWaitCondition waitCondition;
+ QQuickWindow *exposedWindow = nullptr;
+ bool stopEventProcessing = false;
+ QSGD3D12EventQueue eventQueue;
+ QElapsedTimer threadTimer;
+ qint64 syncTime;
+ qint64 renderTime;
+ qint64 sinceLastTime;
+
+public slots:
+ void onSceneGraphChanged() {
+ syncResultedInChanges = true;
+ }
+};
+
+bool QSGD3D12RenderThread::event(QEvent *e)
+{
+ switch (e->type()) {
+
+ case WM_Obscure:
+ Q_ASSERT(!exposedWindow || exposedWindow == static_cast<QSGD3D12WindowEvent *>(e)->window);
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "RT - WM_Obscure" << exposedWindow;
+ mutex.lock();
+ if (exposedWindow) {
+ QQuickWindowPrivate::get(exposedWindow)->fireAboutToStop();
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_Obscure - window removed");
+ exposedWindow = nullptr;
+ }
+ waitCondition.wakeOne();
+ mutex.unlock();
+ return true;
+
+ case WM_RequestSync: {
+ QSGD3D12SyncEvent *wme = static_cast<QSGD3D12SyncEvent *>(e);
+ if (sleeping)
+ stopEventProcessing = true;
+ // One thread+engine for each window. However, the native window may
+ // change in some (quite artificial) cases, e.g. due to a hide -
+ // destroy - show on the QWindow.
+ bool needsWindow = !engine->window();
+ if (engine->window() && engine->window() != wme->window->winId()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_RequestSync - native window handle changes for active engine");
+ engine->waitGPU();
+ QQuickWindowPrivate::get(wme->window)->cleanupNodesOnShutdown();
+ QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
+ rc->invalidate();
+ engine->releaseResources();
+ needsWindow = true;
+ }
+ if (needsWindow) {
+ // Must only ever get here when there is no window or releaseResources() has been called.
+ const int samples = wme->window->format().samples();
+ const bool alpha = wme->window->format().alphaBufferSize() > 0;
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "RT - WM_RequestSync - initializing D3D12 engine" << wme->window
+ << wme->size << wme->dpr << samples << alpha;
+ engine->attachToWindow(wme->window->winId(), wme->size, wme->dpr, samples, alpha);
+ }
+ exposedWindow = wme->window;
+ engine->setWindowSize(wme->size, wme->dpr);
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "RT - WM_RequestSync" << exposedWindow;
+ pendingUpdate |= SyncRequest;
+ if (wme->syncInExpose) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_RequestSync - triggered from expose");
+ pendingUpdate |= ExposeRequest;
+ }
+ if (wme->forceRenderPass) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_RequestSync - repaint regardless");
+ pendingUpdate |= RepaintRequest;
+ }
+ return true;
+ }
+
+ case WM_TryRelease: {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_TryRelease");
+ mutex.lock();
+ renderLoop->lockedForSync = true;
+ QSGD3D12TryReleaseEvent *wme = static_cast<QSGD3D12TryReleaseEvent *>(e);
+ // Only when no windows are exposed anymore or we are shutting down.
+ if (!exposedWindow || wme->destroying) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_TryRelease - invalidating rc");
+ if (wme->window) {
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
+ if (wme->destroying) {
+ // QSGNode destruction may release graphics resources in use so wait first.
+ engine->waitGPU();
+ // Bye bye nodes...
+ wd->cleanupNodesOnShutdown();
+ QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
+ }
+ rc->invalidate();
+ QCoreApplication::processEvents();
+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+ if (wme->destroying)
+ delete wd->animationController;
+ }
+ if (wme->destroying)
+ active = false;
+ if (sleeping)
+ stopEventProcessing = true;
+ } else {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_TryRelease - not releasing because window is still active");
+ }
+ waitCondition.wakeOne();
+ renderLoop->lockedForSync = false;
+ mutex.unlock();
+ return true;
+ }
+
+ case WM_Grab: {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_Grab");
+ QSGD3D12GrabEvent *wme = static_cast<QSGD3D12GrabEvent *>(e);
+ Q_ASSERT(wme->window);
+ Q_ASSERT(wme->window == exposedWindow || !exposedWindow);
+ mutex.lock();
+ if (wme->window) {
+ // Grabbing is generally done by rendering a frame and reading the
+ // color buffer contents back, without presenting, and then
+ // creating a QImage from the returned data. It is terribly
+ // inefficient since it involves a full blocking wait for the GPU.
+ // However, our hands are tied by the existing, synchronous APIs of
+ // QQuickWindow and such.
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
+ rc->initialize(nullptr);
+ wd->syncSceneGraph();
+ wd->renderSceneGraph(wme->window->size());
+ *wme->image = engine->executeAndWaitReadbackRenderTarget();
+ }
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_Grab - waking gui to handle result");
+ waitCondition.wakeOne();
+ mutex.unlock();
+ return true;
+ }
+
+ case WM_PostJob: {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_PostJob");
+ QSGD3D12JobEvent *wme = static_cast<QSGD3D12JobEvent *>(e);
+ Q_ASSERT(wme->window == exposedWindow);
+ if (exposedWindow) {
+ wme->job->run();
+ delete wme->job;
+ wme->job = nullptr;
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_PostJob - job done");
+ }
+ return true;
+ }
+
+ case WM_RequestRepaint:
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - WM_RequestPaint");
+ // When GUI posts this event, it is followed by a polishAndSync, so we
+ // must not exit the event loop yet.
+ pendingUpdate |= RepaintRequest;
+ break;
+
+ default:
+ break;
+ }
+
+ return QThread::event(e);
+}
+
+void QSGD3D12RenderThread::postEvent(QEvent *e)
+{
+ eventQueue.addEvent(e);
+}
+
+void QSGD3D12RenderThread::processEvents()
+{
+ while (eventQueue.hasMoreEvents()) {
+ QEvent *e = eventQueue.takeEvent(false);
+ event(e);
+ delete e;
+ }
+}
+
+void QSGD3D12RenderThread::processEventsAndWaitForMore()
+{
+ stopEventProcessing = false;
+ while (!stopEventProcessing) {
+ QEvent *e = eventQueue.takeEvent(true);
+ event(e);
+ delete e;
+ }
+}
+
+void QSGD3D12RenderThread::run()
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - run()");
+
+ engine = new QSGD3D12Engine;
+ rc->setEngine(engine);
+
+ rtAnim = rc->sceneGraphContext()->createAnimationDriver(nullptr);
+ rtAnim->install();
+
+ if (QQmlDebugConnector::service<QQmlProfilerService>())
+ QQuickProfiler::registerAnimationCallback();
+
+ while (active) {
+ if (exposedWindow)
+ syncAndRender();
+
+ processEvents();
+ QCoreApplication::processEvents();
+
+ if (pendingUpdate == 0 || !exposedWindow) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - done drawing, sleep");
+ sleeping = true;
+ processEventsAndWaitForMore();
+ sleeping = false;
+ }
+ }
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - run() exiting");
+
+ delete rtAnim;
+ rtAnim = nullptr;
+
+ rc->moveToThread(renderLoop->thread());
+ moveToThread(renderLoop->thread());
+
+ rc->setEngine(nullptr);
+ delete engine;
+ engine = nullptr;
+}
+
+void QSGD3D12RenderThread::sync(bool inExpose)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - sync");
+
+ mutex.lock();
+ Q_ASSERT_X(renderLoop->lockedForSync, "QSGD3D12RenderThread::sync()", "sync triggered with gui not locked");
+
+ // Recover from device loss.
+ if (!engine->hasResources()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - sync - device was lost, resetting scenegraph");
+ QQuickWindowPrivate::get(exposedWindow)->cleanupNodesOnShutdown();
+ QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
+ rc->invalidate();
+ }
+
+ if (engine->window()) {
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
+ bool hadRenderer = wd->renderer != nullptr;
+ // If the scene graph was touched since the last sync() make sure it sends the
+ // changed signal.
+ if (wd->renderer)
+ wd->renderer->clearChangedFlag();
+
+ rc->initialize(nullptr);
+ wd->syncSceneGraph();
+
+ if (!hadRenderer && wd->renderer) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - created renderer");
+ syncResultedInChanges = true;
+ connect(wd->renderer, &QSGRenderer::sceneGraphChanged, this,
+ &QSGD3D12RenderThread::onSceneGraphChanged, Qt::DirectConnection);
+ }
+
+ // Process deferred deletes now, directly after the sync as deleteLater
+ // on the GUI must now also have resulted in SG changes and the delete
+ // is a safe operation.
+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+ }
+
+ if (!inExpose) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - sync complete, waking gui");
+ waitCondition.wakeOne();
+ mutex.unlock();
+ }
+}
+
+void QSGD3D12RenderThread::syncAndRender()
+{
+ if (Q_UNLIKELY(debug_time())) {
+ sinceLastTime = threadTimer.nsecsElapsed();
+ threadTimer.start();
+ }
+ Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ QElapsedTimer waitTimer;
+ waitTimer.start();
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - syncAndRender()");
+
+ syncResultedInChanges = false;
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
+
+ const bool repaintRequested = (pendingUpdate & RepaintRequest) || wd->customRenderStage;
+ const bool syncRequested = pendingUpdate & SyncRequest;
+ const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
+ pendingUpdate = 0;
+
+ if (syncRequested)
+ sync(exposeRequested);
+
+#ifndef QSG_NO_RENDER_TIMING
+ if (Q_UNLIKELY(debug_time()))
+ syncTime = threadTimer.nsecsElapsed();
+#endif
+ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ if (!syncResultedInChanges && !repaintRequested) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - no changes, render aborted");
+ int waitTime = vsyncDelta - (int) waitTimer.elapsed();
+ if (waitTime > 0)
+ msleep(waitTime);
+ return;
+ }
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - rendering started");
+
+ if (rtAnim->isRunning()) {
+ wd->animationController->lock();
+ rtAnim->advance();
+ wd->animationController->unlock();
+ }
+
+ bool canRender = wd->renderer != nullptr;
+ // Recover from device loss.
+ if (!engine->hasResources()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - syncAndRender - device was lost, posting FullUpdateRequest");
+ // Cannot do anything here because gui is not locked. Request a new
+ // sync+render round on the gui thread and let the sync handle it.
+ QCoreApplication::postEvent(exposedWindow, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
+ canRender = false;
+ }
+
+ if (canRender) {
+ wd->renderSceneGraph(engine->windowSize());
+ if (Q_UNLIKELY(debug_time()))
+ renderTime = threadTimer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ // The engine is able to have multiple frames in flight. This in effect is
+ // similar to BufferQueueingOpenGL. Provide an env var to force the
+ // traditional blocking swap behavior, just in case.
+ static bool blockOnEachFrame = qEnvironmentVariableIntValue("QT_D3D_BLOCKING_PRESENT") != 0;
+
+ if (!wd->customRenderStage || !wd->customRenderStage->swap())
+ engine->present();
+
+ if (blockOnEachFrame)
+ engine->waitGPU();
+
+ // The concept of "frame swaps" is quite misleading by default, when
+ // blockOnEachFrame is not used, but emit it for compatibility.
+ wd->fireFrameSwapped();
+ } else {
+ Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame, 1);
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - window not ready, skipping render");
+ }
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - rendering done");
+
+ if (exposeRequested) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("RT - wake gui after initial expose");
+ waitCondition.wakeOne();
+ mutex.unlock();
+ }
+
+ if (Q_UNLIKELY(debug_time()))
+ qDebug("Frame rendered with 'd3d12' renderloop in %dms, sync=%d, render=%d, swap=%d - (on render thread)",
+ int(threadTimer.elapsed()),
+ int((syncTime/1000000)),
+ int((renderTime - syncTime) / 1000000),
+ int(threadTimer.elapsed() - renderTime / 1000000));
+
+ Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame);
+
+ static int devLossTest = qEnvironmentVariableIntValue("QT_D3D_TEST_DEVICE_LOSS");
+ if (devLossTest > 0) {
+ static QElapsedTimer kt;
+ static bool timerRunning = false;
+ if (!timerRunning) {
+ kt.start();
+ timerRunning = true;
+ } else if (kt.elapsed() > 5000) {
+ --devLossTest;
+ kt.restart();
+ engine->simulateDeviceLoss();
+ }
+ }
+}
+
+template<class T> T *windowFor(const QVector<T> &list, QQuickWindow *window)
+{
+ for (const T &t : list) {
+ if (t.window == window)
+ return const_cast<T *>(&t);
+ }
+ return nullptr;
+}
+
+QSGD3D12ThreadedRenderLoop::QSGD3D12ThreadedRenderLoop()
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("d3d12 THREADED render loop ctor");
+
+ sg = new QSGD3D12Context;
+
+ anim = sg->createAnimationDriver(this);
+ connect(anim, &QAnimationDriver::started, this, &QSGD3D12ThreadedRenderLoop::onAnimationStarted);
+ connect(anim, &QAnimationDriver::stopped, this, &QSGD3D12ThreadedRenderLoop::onAnimationStopped);
+ anim->install();
+}
+
+QSGD3D12ThreadedRenderLoop::~QSGD3D12ThreadedRenderLoop()
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("d3d12 THREADED render loop dtor");
+
+ delete sg;
+}
+
+void QSGD3D12ThreadedRenderLoop::show(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "show" << window;
+}
+
+void QSGD3D12ThreadedRenderLoop::hide(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "hide" << window;
+
+ if (window->isExposed())
+ handleObscurity(windowFor(windows, window));
+
+ releaseResources(window);
+}
+
+void QSGD3D12ThreadedRenderLoop::resize(QQuickWindow *window)
+{
+ if (!window->isExposed() || window->size().isEmpty())
+ return;
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "resize" << window << window->size();
+}
+
+void QSGD3D12ThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "window destroyed" << window;
+
+ WindowData *w = windowFor(windows, window);
+ if (!w)
+ return;
+
+ handleObscurity(w);
+ handleResourceRelease(w, true);
+
+ QSGD3D12RenderThread *thread = w->thread;
+ while (thread->isRunning())
+ QThread::yieldCurrentThread();
+
+ Q_ASSERT(thread->thread() == QThread::currentThread());
+ delete thread;
+
+ for (int i = 0; i < windows.size(); ++i) {
+ if (windows.at(i).window == window) {
+ windows.removeAt(i);
+ break;
+ }
+ }
+}
+
+void QSGD3D12ThreadedRenderLoop::exposureChanged(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "exposure changed" << window;
+
+ if (window->isExposed()) {
+ handleExposure(window);
+ } else {
+ WindowData *w = windowFor(windows, window);
+ if (w)
+ handleObscurity(w);
+ }
+}
+
+QImage QSGD3D12ThreadedRenderLoop::grab(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "grab" << window;
+
+ WindowData *w = windowFor(windows, window);
+ // Have to support invisible (but created()'ed) windows as well.
+ // Unlike with GL, leaving that case for QQuickWindow to handle is not feasible.
+ const bool tempExpose = !w;
+ if (tempExpose) {
+ handleExposure(window);
+ w = windowFor(windows, window);
+ Q_ASSERT(w);
+ }
+
+ if (!w->thread->isRunning())
+ return QImage();
+
+ if (!window->handle())
+ window->create();
+
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
+ wd->polishItems();
+
+ QImage result;
+ w->thread->mutex.lock();
+ lockedForSync = true;
+ w->thread->postEvent(new QSGD3D12GrabEvent(window, &result));
+ w->thread->waitCondition.wait(&w->thread->mutex);
+ lockedForSync = false;
+ w->thread->mutex.unlock();
+
+ result.setDevicePixelRatio(window->effectiveDevicePixelRatio());
+
+ if (tempExpose)
+ handleObscurity(w);
+
+ return result;
+}
+
+void QSGD3D12ThreadedRenderLoop::update(QQuickWindow *window)
+{
+ WindowData *w = windowFor(windows, window);
+ if (!w)
+ return;
+
+ if (w->thread == QThread::currentThread()) {
+ w->thread->requestRepaint();
+ return;
+ }
+
+ // We set forceRenderPass because we want to make sure the QQuickWindow
+ // actually does a full render pass after the next sync.
+ w->forceRenderPass = true;
+ scheduleUpdate(w);
+}
+
+void QSGD3D12ThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
+{
+ WindowData *w = windowFor(windows, window);
+ if (w)
+ scheduleUpdate(w);
+}
+
+// called in response to window->requestUpdate()
+void QSGD3D12ThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "handleUpdateRequest" << window;
+
+ WindowData *w = windowFor(windows, window);
+ if (w)
+ polishAndSync(w, false);
+}
+
+QAnimationDriver *QSGD3D12ThreadedRenderLoop::animationDriver() const
+{
+ return anim;
+}
+
+QSGContext *QSGD3D12ThreadedRenderLoop::sceneGraphContext() const
+{
+ return sg;
+}
+
+QSGRenderContext *QSGD3D12ThreadedRenderLoop::createRenderContext(QSGContext *) const
+{
+ return sg->createRenderContext();
+}
+
+void QSGD3D12ThreadedRenderLoop::releaseResources(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "releaseResources" << window;
+
+ WindowData *w = windowFor(windows, window);
+ if (w)
+ handleResourceRelease(w, false);
+}
+
+void QSGD3D12ThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
+{
+ WindowData *w = windowFor(windows, window);
+ if (w && w->thread && w->thread->exposedWindow)
+ w->thread->postEvent(new QSGD3D12JobEvent(window, job));
+ else
+ delete job;
+}
+
+QSurface::SurfaceType QSGD3D12ThreadedRenderLoop::windowSurfaceType() const
+{
+ return QSurface::OpenGLSurface;
+}
+
+bool QSGD3D12ThreadedRenderLoop::interleaveIncubation() const
+{
+ bool somethingVisible = false;
+ for (const WindowData &w : windows) {
+ if (w.window->isVisible() && w.window->isExposed()) {
+ somethingVisible = true;
+ break;
+ }
+ }
+ return somethingVisible && anim->isRunning();
+}
+
+int QSGD3D12ThreadedRenderLoop::flags() const
+{
+ return SupportsGrabWithoutExpose;
+}
+
+bool QSGD3D12ThreadedRenderLoop::event(QEvent *e)
+{
+ if (e->type() == QEvent::Timer) {
+ QTimerEvent *te = static_cast<QTimerEvent *>(e);
+ if (te->timerId() == animationTimer) {
+ anim->advance();
+ emit timeToIncubate();
+ return true;
+ }
+ }
+
+ return QObject::event(e);
+}
+
+void QSGD3D12ThreadedRenderLoop::onAnimationStarted()
+{
+ startOrStopAnimationTimer();
+
+ for (const WindowData &w : qAsConst(windows))
+ w.window->requestUpdate();
+}
+
+void QSGD3D12ThreadedRenderLoop::onAnimationStopped()
+{
+ startOrStopAnimationTimer();
+}
+
+void QSGD3D12ThreadedRenderLoop::startOrStopAnimationTimer()
+{
+ int exposedWindowCount = 0;
+ const WindowData *exposed = nullptr;
+
+ for (int i = 0; i < windows.size(); ++i) {
+ const WindowData &w(windows[i]);
+ if (w.window->isVisible() && w.window->isExposed()) {
+ ++exposedWindowCount;
+ exposed = &w;
+ }
+ }
+
+ if (animationTimer && (exposedWindowCount == 1 || !anim->isRunning())) {
+ killTimer(animationTimer);
+ animationTimer = 0;
+ // If animations are running, make sure we keep on animating
+ if (anim->isRunning())
+ exposed->window->requestUpdate();
+ } else if (!animationTimer && exposedWindowCount != 1 && anim->isRunning()) {
+ animationTimer = startTimer(qsgrl_animation_interval());
+ }
+}
+
+void QSGD3D12ThreadedRenderLoop::handleExposure(QQuickWindow *window)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "handleExposure" << window;
+
+ WindowData *w = windowFor(windows, window);
+ if (!w) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("adding window to list");
+ WindowData win;
+ win.window = window;
+ QSGRenderContext *rc = QQuickWindowPrivate::get(window)->context; // will transfer ownership
+ win.thread = new QSGD3D12RenderThread(this, rc);
+ win.updateDuringSync = false;
+ win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
+ windows.append(win);
+ w = &windows.last();
+ }
+
+ // set this early as we'll be rendering shortly anyway and this avoids
+ // special casing exposure in polishAndSync.
+ w->thread->exposedWindow = window;
+
+ if (w->window->size().isEmpty()
+ || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
+#ifndef QT_NO_DEBUG
+ qWarning().noquote().nospace() << "QSGD3D12ThreadedRenderLoop: expose event received for window "
+ << w->window << " with invalid geometry: " << w->window->geometry()
+ << " on " << w->window->screen();
+#endif
+ }
+
+ if (!w->window->handle())
+ w->window->create();
+
+ // Start render thread if it is not running
+ if (!w->thread->isRunning()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("starting render thread");
+ // Push a few things to the render thread.
+ QQuickAnimatorController *controller = QQuickWindowPrivate::get(w->window)->animationController;
+ if (controller->thread() != w->thread)
+ controller->moveToThread(w->thread);
+ if (w->thread->thread() == QThread::currentThread()) {
+ w->thread->rc->moveToThread(w->thread);
+ w->thread->moveToThread(w->thread);
+ }
+
+ w->thread->active = true;
+ w->thread->start();
+
+ if (!w->thread->isRunning())
+ qFatal("Render thread failed to start, aborting application.");
+ }
+
+ polishAndSync(w, true);
+
+ startOrStopAnimationTimer();
+}
+
+void QSGD3D12ThreadedRenderLoop::handleObscurity(WindowData *w)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "handleObscurity" << w->window;
+
+ if (w->thread->isRunning()) {
+ w->thread->mutex.lock();
+ w->thread->postEvent(new QSGD3D12WindowEvent(w->window, WM_Obscure));
+ w->thread->waitCondition.wait(&w->thread->mutex);
+ w->thread->mutex.unlock();
+ }
+
+ startOrStopAnimationTimer();
+}
+
+void QSGD3D12ThreadedRenderLoop::scheduleUpdate(WindowData *w)
+{
+ if (!QCoreApplication::instance())
+ return;
+
+ if (!w || !w->thread->isRunning())
+ return;
+
+ QThread *current = QThread::currentThread();
+ if (current != QCoreApplication::instance()->thread() && (current != w->thread || !lockedForSync)) {
+ qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
+ return;
+ }
+
+ if (current == w->thread) {
+ w->updateDuringSync = true;
+ return;
+ }
+
+ w->window->requestUpdate();
+}
+
+void QSGD3D12ThreadedRenderLoop::handleResourceRelease(WindowData *w, bool destroying)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "handleResourceRelease" << (destroying ? "destroying" : "hide/releaseResources") << w->window;
+
+ w->thread->mutex.lock();
+ if (w->thread->isRunning() && w->thread->active) {
+ QQuickWindow *window = w->window;
+
+ // Note that window->handle() is typically null by this time because
+ // the platform window is already destroyed. This should not be a
+ // problem for the D3D cleanup.
+
+ w->thread->postEvent(new QSGD3D12TryReleaseEvent(window, destroying));
+ w->thread->waitCondition.wait(&w->thread->mutex);
+
+ // Avoid a shutdown race condition.
+ // If SG is invalidated and 'active' becomes false, the thread's run()
+ // method will exit. handleExposure() relies on QThread::isRunning() (because it
+ // potentially needs to start the thread again) and our mutex cannot be used to
+ // track the thread stopping, so we wait a few nanoseconds extra so the thread
+ // can exit properly.
+ if (!w->thread->active)
+ w->thread->wait();
+ }
+ w->thread->mutex.unlock();
+}
+
+void QSGD3D12ThreadedRenderLoop::polishAndSync(WindowData *w, bool inExpose)
+{
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug() << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
+
+ QQuickWindow *window = w->window;
+ if (!w->thread || !w->thread->exposedWindow) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("polishAndSync - not exposed, abort");
+ return;
+ }
+
+ // Flush pending touch events.
+ QQuickWindowPrivate::get(window)->flushFrameSynchronousEvents();
+ // The delivery of the event might have caused the window to stop rendering
+ w = windowFor(windows, window);
+ if (!w || !w->thread || !w->thread->exposedWindow) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("polishAndSync - removed after touch event flushing, abort");
+ return;
+ }
+
+ QElapsedTimer timer;
+ qint64 polishTime = 0;
+ qint64 waitTime = 0;
+ qint64 syncTime = 0;
+ if (Q_UNLIKELY(debug_time()))
+ timer.start();
+ Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
+
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
+ wd->polishItems();
+
+ if (Q_UNLIKELY(debug_time()))
+ polishTime = timer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync);
+
+ w->updateDuringSync = false;
+
+ emit window->afterAnimating();
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("polishAndSync - lock for sync");
+ w->thread->mutex.lock();
+ lockedForSync = true;
+ w->thread->postEvent(new QSGD3D12SyncEvent(window, inExpose, w->forceRenderPass));
+ w->forceRenderPass = false;
+
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("polishAndSync - wait for sync");
+ if (Q_UNLIKELY(debug_time()))
+ waitTime = timer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync);
+ w->thread->waitCondition.wait(&w->thread->mutex);
+ lockedForSync = false;
+ w->thread->mutex.unlock();
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("polishAndSync - unlock after sync");
+
+ if (Q_UNLIKELY(debug_time()))
+ syncTime = timer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync);
+
+ if (!animationTimer && anim->isRunning()) {
+ if (Q_UNLIKELY(debug_loop()))
+ qDebug("polishAndSync - advancing animations");
+ anim->advance();
+ // We need to trigger another sync to keep animations running...
+ w->window->requestUpdate();
+ emit timeToIncubate();
+ } else if (w->updateDuringSync) {
+ w->window->requestUpdate();
+ }
+
+ if (Q_UNLIKELY(debug_time()))
+ qDebug().nospace()
+ << "Frame prepared with 'd3d12' renderloop"
+ << ", polish=" << (polishTime / 1000000)
+ << ", lock=" << (waitTime - polishTime) / 1000000
+ << ", blockedForSync=" << (syncTime - waitTime) / 1000000
+ << ", animations=" << (timer.nsecsElapsed() - syncTime) / 1000000
+ << " - (on gui thread) " << window;
+
+ Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync);
+}
+
+#include "qsgd3d12threadedrenderloop.moc"
+
+QT_END_NAMESPACE
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop_p.h
new file mode 100644
index 0000000000..46f62948f1
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop_p.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** 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 QSGD3D12THREADEDRENDERLOOP_P_H
+#define QSGD3D12THREADEDRENDERLOOP_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/qsgrenderloop_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGD3D12Engine;
+class QSGD3D12Context;
+class QSGD3D12RenderContext;
+class QSGD3D12RenderThread;
+
+class QSGD3D12ThreadedRenderLoop : public QSGRenderLoop
+{
+ Q_OBJECT
+
+public:
+ QSGD3D12ThreadedRenderLoop();
+ ~QSGD3D12ThreadedRenderLoop();
+
+ void show(QQuickWindow *window) override;
+ void hide(QQuickWindow *window) override;
+ void resize(QQuickWindow *window) override;
+
+ void windowDestroyed(QQuickWindow *window) override;
+
+ void exposureChanged(QQuickWindow *window) override;
+
+ QImage grab(QQuickWindow *window) override;
+
+ void update(QQuickWindow *window) override;
+ void maybeUpdate(QQuickWindow *window) override;
+ void handleUpdateRequest(QQuickWindow *window) override;
+
+ QAnimationDriver *animationDriver() const override;
+
+ QSGContext *sceneGraphContext() const override;
+ QSGRenderContext *createRenderContext(QSGContext *) const override;
+
+ void releaseResources(QQuickWindow *window) override;
+ void postJob(QQuickWindow *window, QRunnable *job) override;
+
+ QSurface::SurfaceType windowSurfaceType() const override;
+ bool interleaveIncubation() const override;
+ int flags() const override;
+
+ bool event(QEvent *e) override;
+
+public Q_SLOTS:
+ void onAnimationStarted();
+ void onAnimationStopped();
+
+private:
+ struct WindowData {
+ QQuickWindow *window;
+ QSGD3D12RenderThread *thread;
+ uint updateDuringSync : 1;
+ uint forceRenderPass : 1;
+ };
+
+ void startOrStopAnimationTimer();
+ void handleExposure(QQuickWindow *window);
+ void handleObscurity(WindowData *w);
+ void scheduleUpdate(WindowData *w);
+ void handleResourceRelease(WindowData *w, bool destroying);
+ void polishAndSync(WindowData *w, bool inExpose);
+
+ QSGD3D12Context *sg;
+ QAnimationDriver *anim;
+ int animationTimer = 0;
+ bool lockedForSync = false;
+ QVector<WindowData> windows;
+
+ friend class QSGD3D12RenderThread;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGD3D12THREADEDRENDERLOOP_P_H
diff --git a/src/plugins/scenegraph/d3d12/shaders/flatcolor.hlsl b/src/plugins/scenegraph/d3d12/shaders/flatcolor.hlsl
new file mode 100644
index 0000000000..034b51435a
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/flatcolor.hlsl
@@ -0,0 +1,27 @@
+struct VSInput
+{
+ float4 position : POSITION;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+ float4 color;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+};
+
+PSInput VS_FlatColor(VSInput input)
+{
+ PSInput result;
+ result.position = mul(mvp, input.position);
+ return result;
+}
+
+float4 PS_FlatColor(PSInput input) : SV_TARGET
+{
+ return color;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/mipmapgen.hlsl b/src/plugins/scenegraph/d3d12/shaders/mipmapgen.hlsl
new file mode 100644
index 0000000000..6793b534b0
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/mipmapgen.hlsl
@@ -0,0 +1,60 @@
+static const uint GROUP_DIM = 8; // 2 ^ (out_mip_count - 1)
+
+Texture2D tex : register(t0);
+SamplerState samp : register(s0);
+
+cbuffer ConstantBuffer : register(b0)
+{
+ uint2 mip1Size;
+ uint sampleLevel;
+ uint totalMips;
+}
+
+RWTexture2D<float4> mip1 : register(u0);
+RWTexture2D<float4> mip2 : register(u1);
+RWTexture2D<float4> mip3 : register(u2);
+RWTexture2D<float4> mip4 : register(u3);
+
+groupshared float4 groupColor[GROUP_DIM][GROUP_DIM];
+
+[numthreads(GROUP_DIM, GROUP_DIM, 1)]
+void CS_Generate4MipMaps(uint3 localId: SV_GroupThreadId, uint3 globalId: SV_DispatchThreadID)
+{
+ const float2 coord = float2(1.0f / float(mip1Size.x), 1.0f / float(mip1Size.y)) * (globalId.xy + 0.5);
+ float4 c = tex.SampleLevel(samp, coord, sampleLevel);
+
+ mip1[globalId.xy] = c;
+ groupColor[localId.y][localId.x] = c;
+
+ if (sampleLevel + 1 >= totalMips)
+ return;
+
+ GroupMemoryBarrierWithGroupSync();
+
+ if ((localId.x & 1) == 0 && (localId.y & 1) == 0) {
+ c = (c + groupColor[localId.y][localId.x + 1] + groupColor[localId.y + 1][localId.x] + groupColor[localId.y + 1][localId.x + 1]) / 4.0;
+ mip2[globalId.xy / 2] = c;
+ groupColor[localId.y][localId.x] = c;
+ }
+
+ if (sampleLevel + 2 >= totalMips)
+ return;
+
+ GroupMemoryBarrierWithGroupSync();
+
+ if ((localId.x & 3) == 0 && (localId.y & 3) == 0) {
+ c = (c + groupColor[localId.y][localId.x + 2] + groupColor[localId.y + 2][localId.x] + groupColor[localId.y + 2][localId.x + 2]) / 4.0;
+ mip3[globalId.xy / 4] = c;
+ groupColor[localId.y][localId.x] = c;
+ }
+
+ if (sampleLevel + 3 >= totalMips)
+ return;
+
+ GroupMemoryBarrierWithGroupSync();
+
+ if ((localId.x & 7) == 0 && (localId.y & 7) == 0) {
+ c = (c + groupColor[localId.y][localId.x + 3] + groupColor[localId.y + 3][localId.x] + groupColor[localId.y + 3][localId.x + 3]) / 4.0;
+ mip4[globalId.xy / 8] = c;
+ }
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/shadereffectdefault.hlsl b/src/plugins/scenegraph/d3d12/shaders/shadereffectdefault.hlsl
new file mode 100644
index 0000000000..94672d6267
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/shadereffectdefault.hlsl
@@ -0,0 +1,27 @@
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 qt_Matrix;
+ float qt_Opacity;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+ float2 coord : TEXCOORD0;
+};
+
+Texture2D source : register(t0);
+SamplerState sourceSampler : register(s0);
+
+PSInput VS_DefaultShaderEffect(float4 position : POSITION, float2 coord : TEXCOORD0)
+{
+ PSInput result;
+ result.position = mul(qt_Matrix, position);
+ result.coord = coord;
+ return result;
+}
+
+float4 PS_DefaultShaderEffect(PSInput input) : SV_TARGET
+{
+ return source.Sample(sourceSampler, input.coord) * qt_Opacity;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/shaders.pri b/src/plugins/scenegraph/d3d12/shaders/shaders.pri
new file mode 100644
index 0000000000..963f4c5d8c
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/shaders.pri
@@ -0,0 +1,141 @@
+vertexcolor_VSPS = $$PWD/vertexcolor.hlsl
+vertexcolor_vshader.input = vertexcolor_VSPS
+vertexcolor_vshader.header = vs_vertexcolor.hlslh
+vertexcolor_vshader.entry = VS_VertexColor
+vertexcolor_vshader.type = vs_5_0
+vertexcolor_pshader.input = vertexcolor_VSPS
+vertexcolor_pshader.header = ps_vertexcolor.hlslh
+vertexcolor_pshader.entry = PS_VertexColor
+vertexcolor_pshader.type = ps_5_0
+
+flatcolor_VSPS = $$PWD/flatcolor.hlsl
+flatcolor_vshader.input = flatcolor_VSPS
+flatcolor_vshader.header = vs_flatcolor.hlslh
+flatcolor_vshader.entry = VS_FlatColor
+flatcolor_vshader.type = vs_5_0
+flatcolor_pshader.input = flatcolor_VSPS
+flatcolor_pshader.header = ps_flatcolor.hlslh
+flatcolor_pshader.entry = PS_FlatColor
+flatcolor_pshader.type = ps_5_0
+
+stencilclip_VSPS = $$PWD/stencilclip.hlsl
+stencilclip_vshader.input = stencilclip_VSPS
+stencilclip_vshader.header = vs_stencilclip.hlslh
+stencilclip_vshader.entry = VS_StencilClip
+stencilclip_vshader.type = vs_5_0
+stencilclip_pshader.input = stencilclip_VSPS
+stencilclip_pshader.header = ps_stencilclip.hlslh
+stencilclip_pshader.entry = PS_StencilClip
+stencilclip_pshader.type = ps_5_0
+
+smoothcolor_VSPS = $$PWD/smoothcolor.hlsl
+smoothcolor_vshader.input = smoothcolor_VSPS
+smoothcolor_vshader.header = vs_smoothcolor.hlslh
+smoothcolor_vshader.entry = VS_SmoothColor
+smoothcolor_vshader.type = vs_5_0
+smoothcolor_pshader.input = smoothcolor_VSPS
+smoothcolor_pshader.header = ps_smoothcolor.hlslh
+smoothcolor_pshader.entry = PS_SmoothColor
+smoothcolor_pshader.type = ps_5_0
+
+texture_VSPS = $$PWD/texture.hlsl
+texture_vshader.input = texture_VSPS
+texture_vshader.header = vs_texture.hlslh
+texture_vshader.entry = VS_Texture
+texture_vshader.type = vs_5_0
+texture_pshader.input = texture_VSPS
+texture_pshader.header = ps_texture.hlslh
+texture_pshader.entry = PS_Texture
+texture_pshader.type = ps_5_0
+
+smoothtexture_VSPS = $$PWD/smoothtexture.hlsl
+smoothtexture_vshader.input = smoothtexture_VSPS
+smoothtexture_vshader.header = vs_smoothtexture.hlslh
+smoothtexture_vshader.entry = VS_SmoothTexture
+smoothtexture_vshader.type = vs_5_0
+smoothtexture_pshader.input = smoothtexture_VSPS
+smoothtexture_pshader.header = ps_smoothtexture.hlslh
+smoothtexture_pshader.entry = PS_SmoothTexture
+smoothtexture_pshader.type = ps_5_0
+
+mipmapgen_CS = $$PWD/mipmapgen.hlsl
+mipmapgen_cshader.input = mipmapgen_CS
+mipmapgen_cshader.header = cs_mipmapgen.hlslh
+mipmapgen_cshader.entry = CS_Generate4MipMaps
+mipmapgen_cshader.type = cs_5_0
+
+textmask_VSPS = $$PWD/textmask.hlsl
+textmask_vshader.input = textmask_VSPS
+textmask_vshader.header = vs_textmask.hlslh
+textmask_vshader.entry = VS_TextMask
+textmask_vshader.type = vs_5_0
+textmask_pshader24.input = textmask_VSPS
+textmask_pshader24.header = ps_textmask24.hlslh
+textmask_pshader24.entry = PS_TextMask24
+textmask_pshader24.type = ps_5_0
+textmask_pshader32.input = textmask_VSPS
+textmask_pshader32.header = ps_textmask32.hlslh
+textmask_pshader32.entry = PS_TextMask32
+textmask_pshader32.type = ps_5_0
+textmask_pshader8.input = textmask_VSPS
+textmask_pshader8.header = ps_textmask8.hlslh
+textmask_pshader8.entry = PS_TextMask8
+textmask_pshader8.type = ps_5_0
+styledtext_vshader.input = textmask_VSPS
+styledtext_vshader.header = vs_styledtext.hlslh
+styledtext_vshader.entry = VS_StyledText
+styledtext_vshader.type = vs_5_0
+styledtext_pshader.input = textmask_VSPS
+styledtext_pshader.header = ps_styledtext.hlslh
+styledtext_pshader.entry = PS_StyledText
+styledtext_pshader.type = ps_5_0
+outlinedtext_vshader.input = textmask_VSPS
+outlinedtext_vshader.header = vs_outlinedtext.hlslh
+outlinedtext_vshader.entry = VS_OutlinedText
+outlinedtext_vshader.type = vs_5_0
+outlinedtext_pshader.input = textmask_VSPS
+outlinedtext_pshader.header = ps_outlinedtext.hlslh
+outlinedtext_pshader.entry = PS_OutlinedText
+outlinedtext_pshader.type = ps_5_0
+
+shadereffectdefault_VSPS = $$PWD/shadereffectdefault.hlsl
+shadereffectdefault_vshader.input = shadereffectdefault_VSPS
+shadereffectdefault_vshader.header = vs_shadereffectdefault.hlslh
+shadereffectdefault_vshader.entry = VS_DefaultShaderEffect
+shadereffectdefault_vshader.type = vs_5_0
+shadereffectdefault_pshader.input = shadereffectdefault_VSPS
+shadereffectdefault_pshader.header = ps_shadereffectdefault.hlslh
+shadereffectdefault_pshader.entry = PS_DefaultShaderEffect
+shadereffectdefault_pshader.type = ps_5_0
+
+sprite_VSPS = $$PWD/sprite.hlsl
+sprite_vshader.input = sprite_VSPS
+sprite_vshader.header = vs_sprite.hlslh
+sprite_vshader.entry = VS_Sprite
+sprite_vshader.type = vs_5_0
+sprite_pshader.input = sprite_VSPS
+sprite_pshader.header = ps_sprite.hlslh
+sprite_pshader.entry = PS_Sprite
+sprite_pshader.type = ps_5_0
+
+tdr_CS = $$PWD/tdr.hlsl
+tdr_cshader.input = tdr_CS
+tdr_cshader.header = cs_tdr.hlslh
+tdr_cshader.entry = timeout
+tdr_cshader.type = cs_5_0
+
+HLSL_SHADERS = \
+ vertexcolor_vshader vertexcolor_pshader \
+ flatcolor_vshader flatcolor_pshader \
+ stencilclip_vshader stencilclip_pshader \
+ smoothcolor_vshader smoothcolor_pshader \
+ texture_vshader texture_pshader \
+ smoothtexture_vshader smoothtexture_pshader \
+ mipmapgen_cshader \
+ textmask_vshader textmask_pshader24 textmask_pshader32 textmask_pshader8 \
+ styledtext_vshader styledtext_pshader outlinedtext_vshader outlinedtext_pshader \
+ shadereffectdefault_vshader shadereffectdefault_pshader \
+ sprite_vshader sprite_pshader \
+ tdr_cshader
+
+load(hlsl_bytecode_header)
diff --git a/src/plugins/scenegraph/d3d12/shaders/smoothcolor.hlsl b/src/plugins/scenegraph/d3d12/shaders/smoothcolor.hlsl
new file mode 100644
index 0000000000..4f69eea60f
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/smoothcolor.hlsl
@@ -0,0 +1,64 @@
+struct VSInput
+{
+ float4 position : POSITION;
+ float4 color : COLOR;
+ float2 offset : TEXCOORD0;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+ float opacity;
+ float2 pixelSize;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+ float4 color : COLOR;
+};
+
+PSInput VS_SmoothColor(VSInput input)
+{
+ PSInput result;
+
+ float4 pos = mul(mvp, input.position);
+
+ if (input.offset.x != 0.0) {
+ // In HLSL matrix packing is column-major by default (which is good) but the math is row-major (unlike GLSL).
+ float4 delta = float4(mvp._11, mvp._21, mvp._31, mvp._41) * input.offset.x;
+ float2 dir = delta.xy * pos.w - pos.xy * delta.w;
+ float2 ndir = 0.5 * pixelSize * normalize(dir / pixelSize);
+ dir -= ndir * delta.w * pos.w;
+ float numerator = dot(dir, ndir * pos.w * pos.w);
+ float scale = 0.0;
+ if (numerator < 0.0)
+ scale = 1.0;
+ else
+ scale = min(1.0, numerator / dot(dir, dir));
+ pos += scale * delta;
+ }
+
+ if (input.offset.y != 0.0) {
+ float4 delta = float4(mvp._12, mvp._22, mvp._32, mvp._42) * input.offset.y;
+ float2 dir = delta.xy * pos.w - pos.xy * delta.w;
+ float2 ndir = 0.5 * pixelSize * normalize(dir / pixelSize);
+ dir -= ndir * delta.w * pos.w;
+ float numerator = dot(dir, ndir * pos.w * pos.w);
+ float scale = 0.0;
+ if (numerator < 0.0)
+ scale = 1.0;
+ else
+ scale = min(1.0, numerator / dot(dir, dir));
+ pos += scale * delta;
+ }
+
+ result.position = pos;
+ result.color = input.color * opacity;
+ return result;
+}
+
+float4 PS_SmoothColor(PSInput input) : SV_TARGET
+{
+ return input.color;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/smoothtexture.hlsl b/src/plugins/scenegraph/d3d12/shaders/smoothtexture.hlsl
new file mode 100644
index 0000000000..05b1c6e9d4
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/smoothtexture.hlsl
@@ -0,0 +1,77 @@
+struct VSInput
+{
+ float4 position : POSITION;
+ float2 coord : TEXCOORD0;
+ float2 offset : TEXCOORD1;
+ float2 coordOffset : TEXCOORD2;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+ float opacity;
+ float2 pixelSize;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+ float2 coord : TEXCOORD0;
+ float vertexOpacity : TEXCOORD3;
+};
+
+Texture2D tex : register(t0);
+SamplerState samp : register(s0);
+
+PSInput VS_SmoothTexture(VSInput input)
+{
+ PSInput result;
+
+ float4 pos = mul(mvp, input.position);
+ float2 coord = input.coord;
+
+ if (input.offset.x != 0.0) {
+ // In HLSL matrix packing is column-major by default (which is good) but the math is row-major (unlike GLSL).
+ float4 delta = float4(mvp._11, mvp._21, mvp._31, mvp._41) * input.offset.x;
+ float2 dir = delta.xy * pos.w - pos.xy * delta.w;
+ float2 ndir = 0.5 * pixelSize * normalize(dir / pixelSize);
+ dir -= ndir * delta.w * pos.w;
+ float numerator = dot(dir, ndir * pos.w * pos.w);
+ float scale = 0.0;
+ if (numerator < 0.0)
+ scale = 1.0;
+ else
+ scale = min(1.0, numerator / dot(dir, dir));
+ pos += scale * delta;
+ coord.x += scale * input.coordOffset.x;
+ }
+
+ if (input.offset.y != 0.0) {
+ float4 delta = float4(mvp._12, mvp._22, mvp._32, mvp._42) * input.offset.y;
+ float2 dir = delta.xy * pos.w - pos.xy * delta.w;
+ float2 ndir = 0.5 * pixelSize * normalize(dir / pixelSize);
+ dir -= ndir * delta.w * pos.w;
+ float numerator = dot(dir, ndir * pos.w * pos.w);
+ float scale = 0.0;
+ if (numerator < 0.0)
+ scale = 1.0;
+ else
+ scale = min(1.0, numerator / dot(dir, dir));
+ pos += scale * delta;
+ coord.y += scale * input.coordOffset.y;
+ }
+
+ if ((input.offset.x != 0.0 || input.offset.y != 0.0) && (input.coordOffset.x == 0.0 && input.coordOffset.y == 0.0))
+ result.vertexOpacity = 0.0;
+ else
+ result.vertexOpacity = opacity;
+
+ result.position = pos;
+ result.coord = coord;
+ return result;
+}
+
+float4 PS_SmoothTexture(PSInput input) : SV_TARGET
+{
+ return tex.Sample(samp, input.coord) * input.vertexOpacity;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/sprite.hlsl b/src/plugins/scenegraph/d3d12/shaders/sprite.hlsl
new file mode 100644
index 0000000000..d4e3b066ee
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/sprite.hlsl
@@ -0,0 +1,43 @@
+struct VSInput
+{
+ float4 position : POSITION;
+ float2 coord : TEXCOORD0;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+ float4 animPos;
+ float3 animData;
+ float opacity;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+ float4 fTexS : TEXCOORD0;
+ float progress : TEXCOORD1;
+};
+
+Texture2D tex : register(t0);
+SamplerState samp : register(s0);
+
+PSInput VS_Sprite(VSInput input)
+{
+ PSInput result;
+
+ result.position = mul(mvp, input.position);
+ result.progress = animData.z;
+
+ // Calculate frame location in texture
+ result.fTexS.xy = animPos.xy + input.coord.xy * animData.xy;
+ // Next frame is also passed, for interpolation
+ result.fTexS.zw = animPos.zw + input.coord.xy * animData.xy;
+
+ return result;
+}
+
+float4 PS_Sprite(PSInput input) : SV_TARGET
+{
+ return lerp(tex.Sample(samp, input.fTexS.xy), tex.Sample(samp, input.fTexS.zw), input.progress) * opacity;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/stencilclip.hlsl b/src/plugins/scenegraph/d3d12/shaders/stencilclip.hlsl
new file mode 100644
index 0000000000..9aff84d261
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/stencilclip.hlsl
@@ -0,0 +1,26 @@
+struct VSInput
+{
+ float4 position : POSITION;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+};
+
+PSInput VS_StencilClip(VSInput input)
+{
+ PSInput result;
+ result.position = mul(mvp, input.position);
+ return result;
+}
+
+float4 PS_StencilClip(PSInput input) : SV_TARGET
+{
+ return float4(0.81, 0.83, 0.12, 1.0); // Trolltech green ftw!
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/tdr.hlsl b/src/plugins/scenegraph/d3d12/shaders/tdr.hlsl
new file mode 100644
index 0000000000..f32d4fbace
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/tdr.hlsl
@@ -0,0 +1,11 @@
+// http://gamedev.stackexchange.com/questions/108141/how-can-i-test-dxgi-error-device-removed-error-handling
+
+RWBuffer<uint> uav;
+cbuffer ConstantBuffer { uint zero; }
+
+[numthreads(256, 1, 1)]
+void timeout(uint3 id: SV_DispatchThreadID)
+{
+ while (zero == 0)
+ uav[id.x] = zero;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/textmask.hlsl b/src/plugins/scenegraph/d3d12/shaders/textmask.hlsl
new file mode 100644
index 0000000000..bb9381e7c0
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/textmask.hlsl
@@ -0,0 +1,104 @@
+struct VSInput
+{
+ float4 position : POSITION;
+ float2 coord : TEXCOORD0;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+ float2 textureScale;
+ float dpr;
+ float color; // for TextMask24 and 32
+ float4 colorVec; // for TextMask8 and Styled and Outlined
+ float2 shift; // for Styled
+ float4 styleColor; // for Styled and Outlined
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+ float2 coord : TEXCOORD0;
+};
+
+Texture2D tex : register(t0);
+SamplerState samp : register(s0);
+
+PSInput VS_TextMask(VSInput input)
+{
+ PSInput result;
+ result.position = mul(mvp, floor(input.position * dpr + 0.5) / dpr);
+ result.coord = input.coord * textureScale;
+ return result;
+}
+
+float4 PS_TextMask24(PSInput input) : SV_TARGET
+{
+ float4 glyph = tex.Sample(samp, input.coord);
+ return float4(glyph.rgb * color, glyph.a);
+}
+
+float4 PS_TextMask32(PSInput input) : SV_TARGET
+{
+ return tex.Sample(samp, input.coord) * color;
+}
+
+float4 PS_TextMask8(PSInput input) : SV_TARGET
+{
+ return colorVec * tex.Sample(samp, input.coord).a;
+}
+
+struct StyledPSInput
+{
+ float4 position : SV_POSITION;
+ float2 coord : TEXCOORD0;
+ float2 shiftedCoord : TEXCOORD1;
+};
+
+StyledPSInput VS_StyledText(VSInput input)
+{
+ StyledPSInput result;
+ result.position = mul(mvp, floor(input.position * dpr + 0.5) / dpr);
+ result.coord = input.coord * textureScale;
+ result.shiftedCoord = (input.coord - shift) * textureScale;
+ return result;
+}
+
+float4 PS_StyledText(StyledPSInput input) : SV_TARGET
+{
+ float glyph = tex.Sample(samp, input.coord).a;
+ float style = clamp(tex.Sample(samp, input.shiftedCoord).a - glyph, 0.0, 1.0);
+ return style * styleColor + glyph * colorVec;
+}
+
+struct OutlinedPSInput
+{
+ float4 position : SV_POSITION;
+ float2 coord : TEXCOORD0;
+ float2 coordUp : TEXCOORD1;
+ float2 coordDown : TEXCOORD2;
+ float2 coordLeft : TEXCOORD3;
+ float2 coordRight : TEXCOORD4;
+};
+
+OutlinedPSInput VS_OutlinedText(VSInput input)
+{
+ OutlinedPSInput result;
+ result.position = mul(mvp, floor(input.position * dpr + 0.5) / dpr);
+ result.coord = input.coord * textureScale;
+ result.coordUp = (input.coord - float2(0.0, -1.0)) * textureScale;
+ result.coordDown = (input.coord - float2(0.0, 1.0)) * textureScale;
+ result.coordLeft = (input.coord - float2(-1.0, 0.0)) * textureScale;
+ result.coordRight = (input.coord - float2(1.0, 0.0)) * textureScale;
+ return result;
+}
+
+float4 PS_OutlinedText(OutlinedPSInput input) : SV_TARGET
+{
+ float glyph = tex.Sample(samp, input.coord).a;
+ float outline = clamp(clamp(tex.Sample(samp, input.coordUp).a
+ + tex.Sample(samp, input.coordDown).a
+ + tex.Sample(samp, input.coordLeft).a
+ + tex.Sample(samp, input.coordRight).a, 0.0, 1.0) - glyph, 0.0, 1.0);
+ return outline * styleColor + glyph * colorVec;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/texture.hlsl b/src/plugins/scenegraph/d3d12/shaders/texture.hlsl
new file mode 100644
index 0000000000..1ae6579e8d
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/texture.hlsl
@@ -0,0 +1,33 @@
+struct VSInput
+{
+ float4 position : POSITION;
+ float2 coord : TEXCOORD0;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+ float opacity;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+ float2 coord : TEXCOORD0;
+};
+
+Texture2D tex : register(t0);
+SamplerState samp : register(s0);
+
+PSInput VS_Texture(VSInput input)
+{
+ PSInput result;
+ result.position = mul(mvp, input.position);
+ result.coord = input.coord;
+ return result;
+}
+
+float4 PS_Texture(PSInput input) : SV_TARGET
+{
+ return tex.Sample(samp, input.coord) * opacity;
+}
diff --git a/src/plugins/scenegraph/d3d12/shaders/vertexcolor.hlsl b/src/plugins/scenegraph/d3d12/shaders/vertexcolor.hlsl
new file mode 100644
index 0000000000..a0569bb5c1
--- /dev/null
+++ b/src/plugins/scenegraph/d3d12/shaders/vertexcolor.hlsl
@@ -0,0 +1,32 @@
+struct VSInput
+{
+ float4 position : POSITION;
+ float4 color : COLOR;
+};
+
+cbuffer ConstantBuffer : register(b0)
+{
+ float4x4 mvp;
+ float opacity;
+};
+
+struct PSInput
+{
+ float4 position : SV_POSITION;
+ float4 color : COLOR;
+};
+
+PSInput VS_VertexColor(VSInput input)
+{
+ PSInput result;
+
+ result.position = mul(mvp, input.position);
+ result.color = input.color * opacity;
+
+ return result;
+}
+
+float4 PS_VertexColor(PSInput input) : SV_TARGET
+{
+ return input.color;
+}