diff options
Diffstat (limited to 'src/plugins/scenegraph/d3d12')
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 ¶ms) +{ + 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(©CommandQueue)))) { + 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(©CommandAllocator)))) { + 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(©CommandList)))) { + 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 ¶ms) +{ + 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 = ¶m; + + 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 ¶ms); + + 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 ¶ms); + + 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 = ± + 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; +} |