aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp')
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp3331
1 files changed, 2190 insertions, 1141 deletions
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
index afe3380494..6c084ec441 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
@@ -1,48 +1,9 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
-** Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net>
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
+// Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qsgbatchrenderer_p.h"
-#include <private/qsgshadersourcebuilder_p.h>
-
-#include <QQuickWindow>
#include <qmath.h>
@@ -50,24 +11,19 @@
#include <QtCore/QtNumeric>
#include <QtGui/QGuiApplication>
-#include <QtGui/QOpenGLFramebufferObject>
-#include <QtGui/QOpenGLVertexArrayObject>
-#include <QtGui/QOpenGLFunctions_1_0>
-#include <QtGui/QOpenGLFunctions_3_2_Core>
#include <private/qnumeric_p.h>
-#include <private/qquickprofiler_p.h>
#include "qsgmaterialshader_p.h"
-#include <algorithm>
+#include "qsgrhivisualizer_p.h"
-#ifndef GL_DOUBLE
- #define GL_DOUBLE 0x140A
-#endif
+#include <algorithm>
QT_BEGIN_NAMESPACE
-extern QByteArray qsgShaderRewriter_insertZAttributes(const char *input, QSurfaceFormat::OpenGLContextProfile profile);
+#ifndef QT_NO_DEBUG
+Q_QUICK_EXPORT bool qsg_test_and_clear_material_failure();
+#endif
int qt_sg_envInt(const char *name, int defaultValue);
@@ -88,12 +44,10 @@ DECLARE_DEBUG_VAR(noopaque)
DECLARE_DEBUG_VAR(noclip)
#undef DECLARE_DEBUG_VAR
-static QElapsedTimer qsg_renderer_timer;
-
#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
-static inline int size_of_type(GLenum type)
+static inline int size_of_type(int type)
{
static int sizes[] = {
sizeof(char),
@@ -108,8 +62,8 @@ static inline int size_of_type(GLenum type)
4,
sizeof(double)
};
- Q_ASSERT(type >= GL_BYTE && type <= 0x140A); // the value of GL_DOUBLE
- return sizes[type - GL_BYTE];
+ Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
+ return sizes[type - QSGGeometry::ByteType];
}
bool qsg_sort_element_increasing_order(Element *a, Element *b) { return a->order < b->order; }
@@ -120,91 +74,198 @@ bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b) { return a->first->orde
QSGMaterial::Flag QSGMaterial_FullMatrix = (QSGMaterial::Flag) (QSGMaterial::RequiresFullMatrix & ~QSGMaterial::RequiresFullMatrixExceptTranslate);
-struct QMatrix4x4_Accessor
+static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
+static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
+static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
+
+const float OPAQUE_LIMIT = 0.999f;
+
+const uint DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD = 4;
+const int VERTEX_BUFFER_BINDING = 0;
+const int ZORDER_BUFFER_BINDING = VERTEX_BUFFER_BINDING + 1;
+
+const float VIEWPORT_MIN_DEPTH = 0.0f;
+const float VIEWPORT_MAX_DEPTH = 1.0f;
+
+template <class Int>
+inline Int aligned(Int v, Int byteAlign)
{
- float m[4][4];
- int flagBits;
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
- static bool isTranslate(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits <= 0x1; }
- static bool isScale(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits <= 0x2; }
- static bool is2DSafe(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits < 0x8; }
-};
+QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
+{
+ switch (a.type) {
+ case QSGGeometry::FloatType:
+ if (a.tupleSize == 4)
+ return QRhiVertexInputAttribute::Float4;
+ if (a.tupleSize == 3)
+ return QRhiVertexInputAttribute::Float3;
+ if (a.tupleSize == 2)
+ return QRhiVertexInputAttribute::Float2;
+ if (a.tupleSize == 1)
+ return QRhiVertexInputAttribute::Float;
+ break;
+ case QSGGeometry::UnsignedByteType:
+ if (a.tupleSize == 4)
+ return QRhiVertexInputAttribute::UNormByte4;
+ if (a.tupleSize == 2)
+ return QRhiVertexInputAttribute::UNormByte2;
+ if (a.tupleSize == 1)
+ return QRhiVertexInputAttribute::UNormByte;
+ break;
+ default:
+ break;
+ }
+ qWarning("Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
+ Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
+}
-const float OPAQUE_LIMIT = 0.999f;
+static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
+{
+ Q_ASSERT(geometry);
+ const QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
+ if (!sd->vertexShader) {
+ qWarning("No vertex shader in QSGMaterialShader %p", s);
+ return QRhiVertexInputLayout();
+ }
-ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material)
+ const int attrCount = geometry->attributeCount();
+ QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
+ inputAttributes.reserve(attrCount + 1);
+ quint32 offset = 0;
+ for (int i = 0; i < attrCount; ++i) {
+ const QSGGeometry::Attribute &a = geometry->attributes()[i];
+ if (!sd->vertexShader->vertexInputLocations.contains(a.position)) {
+ qWarning("Vertex input %d is present in material but not in shader. This is wrong.",
+ a.position);
+ }
+ inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
+ offset += a.tupleSize * size_of_type(a.type);
+ }
+ if (batchable) {
+ inputAttributes.append(QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
+ QRhiVertexInputAttribute::Float, 0));
+ }
+
+ Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
+ QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
+ inputBindings.append(QRhiVertexInputBinding(geometry->sizeOfVertex()));
+ if (batchable)
+ inputBindings.append(QRhiVertexInputBinding(sizeof(float)));
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend());
+ inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend());
+
+ return inputLayout;
+}
+
+QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
+{
+ switch (geometry->indexType()) {
+ case QSGGeometry::UnsignedShortType:
+ return QRhiCommandBuffer::IndexUInt16;
+ break;
+ case QSGGeometry::UnsignedIntType:
+ return QRhiCommandBuffer::IndexUInt32;
+ break;
+ default:
+ Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
+ }
+}
+
+QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode)
{
+ QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
+ switch (geomDrawMode) {
+ case QSGGeometry::DrawPoints:
+ topology = QRhiGraphicsPipeline::Points;
+ break;
+ case QSGGeometry::DrawLines:
+ topology = QRhiGraphicsPipeline::Lines;
+ break;
+ case QSGGeometry::DrawLineStrip:
+ topology = QRhiGraphicsPipeline::LineStrip;
+ break;
+ case QSGGeometry::DrawTriangles:
+ topology = QRhiGraphicsPipeline::Triangles;
+ break;
+ case QSGGeometry::DrawTriangleStrip:
+ topology = QRhiGraphicsPipeline::TriangleStrip;
+ break;
+ default:
+ qWarning("Primitive topology 0x%x not supported", geomDrawMode);
+ break;
+ }
+ return topology;
+}
+
+void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
+{
+ material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
+ material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
+ material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
+}
+
+ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
+ const QSGGeometry *geometry,
+ QSGRendererInterface::RenderMode renderMode,
+ int multiViewCount)
+{
+ qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
+
QSGMaterialType *type = material->type();
- Shader *shader = rewrittenShaders.value(type, 0);
+ ShaderKey key = { type, renderMode, multiViewCount };
+ Shader *shader = rewrittenShaders.value(key, nullptr);
if (shader)
return shader;
- if (QSG_LOG_TIME_COMPILATION().isDebugEnabled())
- qsg_renderer_timer.start();
- Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphContextFrame);
-
- QSGMaterialShader *s = material->createShader();
- QOpenGLContext *ctx = context->openglContext();
- QSurfaceFormat::OpenGLContextProfile profile = ctx->format().profile();
-
- QOpenGLShaderProgram *p = s->program();
- char const *const *attr = s->attributeNames();
- int i;
- for (i = 0; attr[i]; ++i) {
- if (*attr[i])
- p->bindAttributeLocation(attr[i], i);
- }
- p->bindAttributeLocation("_qt_order", i);
- context->compileShader(s, material, qsgShaderRewriter_insertZAttributes(s->vertexShader(), profile), nullptr);
- context->initializeShader(s);
- if (!p->isLinked())
- return nullptr;
-
shader = new Shader;
- shader->program = s;
- shader->pos_order = i;
- shader->id_zRange = p->uniformLocation("_qt_zRange");
- shader->lastOpacity = 0;
-
- Q_ASSERT(shader->pos_order >= 0);
- Q_ASSERT(shader->id_zRange >= 0);
-
- qCDebug(QSG_LOG_TIME_COMPILATION, "shader compiled in %dms", (int) qsg_renderer_timer.elapsed());
+ QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
+ context->initializeRhiShader(s, QShader::BatchableVertexShader);
+ shader->materialShader = s;
+ shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
+ QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
+ shader->stages = {
+ { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
+ { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
+ };
- Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphContextFrame,
- QQuickProfiler::SceneGraphContextMaterialCompile);
+ shader->lastOpacity = 0;
- rewrittenShaders[type] = shader;
+ rewrittenShaders[key] = shader;
return shader;
}
-ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material)
+ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
+ const QSGGeometry *geometry,
+ QSGRendererInterface::RenderMode renderMode,
+ int multiViewCount)
{
+ qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
+
QSGMaterialType *type = material->type();
- Shader *shader = stockShaders.value(type, 0);
+ ShaderKey key = { type, renderMode, multiViewCount };
+ Shader *shader = stockShaders.value(key, nullptr);
if (shader)
return shader;
- if (QSG_LOG_TIME_COMPILATION().isDebugEnabled())
- qsg_renderer_timer.start();
- Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphContextFrame);
-
- QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader());
- context->compileShader(s, material);
- context->initializeShader(s);
+ shader = new Shader;
+ QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
+ context->initializeRhiShader(s, QShader::StandardShader);
+ shader->materialShader = s;
+ shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
+ QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
+ shader->stages = {
+ { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
+ { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
+ };
- shader = new Shader();
- shader->program = s;
- shader->id_zRange = -1;
- shader->pos_order = -1;
shader->lastOpacity = 0;
- stockShaders[type] = shader;
-
- qCDebug(QSG_LOG_TIME_COMPILATION, "shader compiled in %dms (no rewrite)", (int) qsg_renderer_timer.elapsed());
+ stockShaders[key] = shader;
- Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphContextFrame,
- QQuickProfiler::SceneGraphContextMaterialCompile);
return shader;
}
@@ -214,8 +275,30 @@ void ShaderManager::invalidated()
stockShaders.clear();
qDeleteAll(rewrittenShaders);
rewrittenShaders.clear();
- delete blitProgram;
- blitProgram = nullptr;
+
+ qDeleteAll(pipelineCache);
+ pipelineCache.clear();
+
+ qDeleteAll(srbPool);
+ srbPool.clear();
+}
+
+void ShaderManager::clearCachedRendererData()
+{
+ for (ShaderManager::Shader *sms : std::as_const(stockShaders)) {
+ QSGMaterialShader *s = sms->materialShader;
+ if (s) {
+ QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
+ sd->clearCachedRendererData();
+ }
+ }
+ for (ShaderManager::Shader *sms : std::as_const(rewrittenShaders)) {
+ QSGMaterialShader *s = sms->materialShader;
+ if (s) {
+ QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
+ sd->clearCachedRendererData();
+ }
+ }
}
void qsg_dumpShadowRoots(BatchRootInfo *i, int indent)
@@ -248,13 +331,13 @@ void qsg_dumpShadowRoots(Node *n)
QByteArray ind(indent, ' ');
if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
- qDebug() << ind.constData() << "[X]" << n->sgNode << hex << uint(n->sgNode->flags());
+ qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
qsg_dumpShadowRoots(n->rootInfo(), indent);
} else {
QDebug d = qDebug();
- d << ind.constData() << "[ ]" << n->sgNode << hex << uint(n->sgNode->flags());
+ d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
if (n->type() == QSGNode::GeometryNodeType)
- d << "order" << dec << n->element()->order;
+ d << "order" << Qt::dec << n->element()->order;
}
SHADOWNODE_TRAVERSE(n)
@@ -262,7 +345,7 @@ void qsg_dumpShadowRoots(Node *n)
--indent;
#else
- Q_UNUSED(n)
+ Q_UNUSED(n);
#endif
}
@@ -274,8 +357,6 @@ Updater::Updater(Renderer *r)
m_roots.add(0);
m_combined_matrix_stack.add(&m_identityMatrix);
m_rootMatrices.add(m_identityMatrix);
-
- Q_ASSERT(sizeof(QMatrix4x4_Accessor) == sizeof(QMatrix4x4));
}
void Updater::updateStates(QSGNode *n)
@@ -304,8 +385,8 @@ void Updater::updateStates(QSGNode *n)
qDebug(" - forceupdate");
}
- if (Q_UNLIKELY(renderer->m_visualizeMode == Renderer::VisualizeChanges))
- renderer->visualizeChangesPrepare(sn);
+ if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
+ renderer->m_visualizer->visualizeChangesPrepare(sn);
visitNode(sn);
}
@@ -347,7 +428,7 @@ void Updater::visitNode(Node *n)
m_added = count;
m_force_update = force;
- n->dirtyState = nullptr;
+ n->dirtyState = {};
}
void Updater::visitClipNode(Node *n)
@@ -469,7 +550,7 @@ void Updater::visitGeometryNode(Node *n)
if (m_added) {
Element *e = n->element();
e->root = m_roots.last();
- e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(*gn->matrix());
+ e->translateOnlyToRoot = isTranslate(*gn->matrix());
if (e->root) {
BatchRootInfo *info = renderer->batchRootInfo(e->root);
@@ -492,7 +573,7 @@ void Updater::visitGeometryNode(Node *n)
} else {
if (m_transformChange) {
Element *e = n->element();
- e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(*gn->matrix());
+ e->translateOnlyToRoot = isTranslate(*gn->matrix());
}
if (m_opacityChange) {
Element *e = n->element();
@@ -531,11 +612,12 @@ void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &com
}
}
-int qsg_positionAttribute(QSGGeometry *g) {
+int qsg_positionAttribute(QSGGeometry *g)
+{
int vaOffset = 0;
for (int a=0; a<g->attributeCount(); ++a) {
const QSGGeometry::Attribute &attr = g->attributes()[a];
- if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == GL_FLOAT) {
+ if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
return vaOffset;
}
vaOffset += attr.tupleSize * size_of_type(attr.type);
@@ -547,7 +629,7 @@ int qsg_positionAttribute(QSGGeometry *g) {
void Rect::map(const QMatrix4x4 &matrix)
{
const float *m = matrix.constData();
- if (QMatrix4x4_Accessor::isScale(matrix)) {
+ if (isScale(matrix)) {
tl.x = tl.x * m[0] + m[12];
tl.y = tl.y * m[5] + m[13];
br.x = br.x * m[0] + m[12];
@@ -625,7 +707,7 @@ BatchCompatibility Batch::isMaterialCompatible(Element *e) const
QSGMaterial *m = e->node->activeMaterial();
QSGMaterial *nm = n->node->activeMaterial();
- return (nm->type() == m->type() && nm->compare(m) == 0)
+ return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
? BatchIsCompatible
: BatchBreaksOnCompare;
}
@@ -652,6 +734,9 @@ bool Batch::geometryWasChanged(QSGGeometryNode *gn)
void Batch::cleanupRemovedElements()
{
+ if (!needsPurge)
+ return;
+
// remove from front of batch..
while (first && first->removed) {
first = first->nextInBatch;
@@ -668,6 +753,8 @@ void Batch::cleanupRemovedElements()
}
}
+
+ needsPurge = false;
}
/*
@@ -676,8 +763,6 @@ void Batch::cleanupRemovedElements()
*/
void Batch::invalidate()
{
- // If doing removal here is a performance issue, we might add a "hasRemoved" bit to
- // the batch to do an early out..
cleanupRemovedElements();
Element *e = first;
first = nullptr;
@@ -722,7 +807,7 @@ bool Batch::isSafeToBatch() const {
while (e) {
if (e->boundsOutsideFloatRange)
return false;
- if (!QMatrix4x4_Accessor::is2DSafe(*e->node->matrix()))
+ if (!is2DSafe(*e->node->matrix()))
return false;
e = e->nextInBatch;
}
@@ -749,40 +834,52 @@ static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
return sum;
}
-Renderer::Renderer(QSGDefaultRenderContext *ctx)
+Renderer::Renderer(QSGDefaultRenderContext *ctx, QSGRendererInterface::RenderMode renderMode)
: QSGRenderer(ctx)
, m_context(ctx)
+ , m_renderMode(renderMode)
, m_opaqueRenderList(64)
, m_alphaRenderList(64)
, m_nextRenderOrder(0)
, m_partialRebuild(false)
, m_partialRebuildRoot(nullptr)
- , m_useDepthBuffer(true)
+ , m_forceNoDepthBuffer(false)
, m_opaqueBatches(16)
, m_alphaBatches(16)
, m_batchPool(16)
, m_elementsToDelete(64)
, m_tmpAlphaElements(16)
, m_tmpOpaqueElements(16)
+ , m_vboPool(16)
+ , m_iboPool(16)
, m_rebuild(FullRebuild)
, m_zRange(0)
+#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
, m_renderOrderRebuildLower(-1)
, m_renderOrderRebuildUpper(-1)
+#endif
, m_currentMaterial(nullptr)
, m_currentShader(nullptr)
- , m_currentStencilValue(0)
- , m_clipMatrixId(0)
- , m_currentClip(nullptr)
- , m_currentClipType(NoClip)
, m_vertexUploadPool(256)
, m_indexUploadPool(64)
- , m_vao(nullptr)
- , m_visualizeMode(VisualizeNothing)
{
- initializeOpenGLFunctions();
+ m_rhi = m_context->rhi();
+ Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
+
+ m_ubufAlignment = m_rhi->ubufAlignment();
+
+ m_uint32IndexForRhi = !m_rhi->isFeatureSupported(QRhi::NonFourAlignedEffectiveIndexBufferOffset);
+ if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX"))
+ m_uint32IndexForRhi = true;
+
+ m_visualizer = new RhiVisualizer(this);
+
setNodeUpdater(new Updater(this));
- m_shaderManager = ctx->findChild<ShaderManager *>(QStringLiteral("__qt_ShaderManager"), Qt::FindDirectChildrenOnly);
+ // The shader manager is shared between renderers (think for example Item
+ // layers that create a new Renderer each) with the same rendercontext (and
+ // so same QRhi).
+ m_shaderManager = ctx->findChild<ShaderManager *>(QString(), Qt::FindDirectChildrenOnly);
if (!m_shaderManager) {
m_shaderManager = new ShaderManager(ctx);
m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
@@ -790,40 +887,20 @@ Renderer::Renderer(QSGDefaultRenderContext *ctx)
QObject::connect(ctx, SIGNAL(invalidated()), m_shaderManager, SLOT(invalidated()), Qt::DirectConnection);
}
- m_bufferStrategy = GL_STATIC_DRAW;
- if (Q_UNLIKELY(qEnvironmentVariableIsSet("QSG_RENDERER_BUFFER_STRATEGY"))) {
- const QByteArray strategy = qgetenv("QSG_RENDERER_BUFFER_STRATEGY");
- if (strategy == "dynamic")
- m_bufferStrategy = GL_DYNAMIC_DRAW;
- else if (strategy == "stream")
- m_bufferStrategy = GL_STREAM_DRAW;
- }
-
m_batchNodeThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_NODE_THRESHOLD", 64);
m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD", 1024);
+ m_srbPoolThreshold = qt_sg_envInt("QSG_RENDERER_SRB_POOL_THRESHOLD", 1024);
if (Q_UNLIKELY(debug_build() || debug_render())) {
- qDebug("Batch thresholds: nodes: %d vertices: %d",
- m_batchNodeThreshold, m_batchVertexThreshold);
- qDebug("Using buffer strategy: %s",
- (m_bufferStrategy == GL_STATIC_DRAW
- ? "static" : (m_bufferStrategy == GL_DYNAMIC_DRAW ? "dynamic" : "stream")));
+ qDebug("Batch thresholds: nodes: %d vertices: %d Srb pool threshold: %d",
+ m_batchNodeThreshold, m_batchVertexThreshold, m_srbPoolThreshold);
}
-
- // If rendering with an OpenGL Core profile context, we need to create a VAO
- // to hold our vertex specification state.
- if (m_context->openglContext()->format().profile() == QSurfaceFormat::CoreProfile) {
- m_vao = new QOpenGLVertexArrayObject(this);
- m_vao->create();
- }
-
- bool useDepth = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
- m_useDepthBuffer = useDepth && ctx->openglContext()->format().depthBufferSize() > 0;
}
-static void qsg_wipeBuffer(Buffer *buffer, QOpenGLFunctions *funcs)
+static void qsg_wipeBuffer(Buffer *buffer)
{
- funcs->glDeleteBuffers(1, &buffer->id);
+ delete buffer->buf;
+
// The free here is ok because we're in one of two situations.
// 1. We're using the upload pool in which case unmap will have set the
// data pointer to 0 and calling free on 0 is ok.
@@ -832,42 +909,96 @@ static void qsg_wipeBuffer(Buffer *buffer, QOpenGLFunctions *funcs)
free(buffer->data);
}
-static void qsg_wipeBatch(Batch *batch, QOpenGLFunctions *funcs, bool separateIndexBuffer)
+static void qsg_wipeBatch(Batch *batch)
{
- qsg_wipeBuffer(&batch->vbo, funcs);
- if (separateIndexBuffer)
- qsg_wipeBuffer(&batch->ibo, funcs);
+ qsg_wipeBuffer(&batch->vbo);
+ qsg_wipeBuffer(&batch->ibo);
+ delete batch->ubuf;
+ batch->stencilClipState.reset();
delete batch;
}
Renderer::~Renderer()
{
- if (QOpenGLContext::currentContext()) {
+ if (m_rhi) {
// Clean up batches and buffers
- const bool separateIndexBuffer = m_context->separateIndexBuffer();
for (int i = 0; i < m_opaqueBatches.size(); ++i)
- qsg_wipeBatch(m_opaqueBatches.at(i), this, separateIndexBuffer);
+ qsg_wipeBatch(m_opaqueBatches.at(i));
for (int i = 0; i < m_alphaBatches.size(); ++i)
- qsg_wipeBatch(m_alphaBatches.at(i), this, separateIndexBuffer);
+ qsg_wipeBatch(m_alphaBatches.at(i));
for (int i = 0; i < m_batchPool.size(); ++i)
- qsg_wipeBatch(m_batchPool.at(i), this, separateIndexBuffer);
+ qsg_wipeBatch(m_batchPool.at(i));
+ for (int i = 0; i < m_vboPool.size(); ++i)
+ delete m_vboPool.at(i);
+ for (int i = 0; i < m_iboPool.size(); ++i)
+ delete m_iboPool.at(i);
}
- for (Node *n : qAsConst(m_nodes))
+ for (Node *n : std::as_const(m_nodes)) {
+ if (n->type() == QSGNode::GeometryNodeType) {
+ Element *e = n->element();
+ if (!e->removed)
+ m_elementsToDelete.add(e);
+ }
m_nodeAllocator.release(n);
+ }
// Remaining elements...
- for (int i=0; i<m_elementsToDelete.size(); ++i) {
- Element *e = m_elementsToDelete.at(i);
- if (e->isRenderNode)
- delete static_cast<RenderNodeElement *>(e);
- else
- m_elementAllocator.release(e);
- }
+ for (int i=0; i<m_elementsToDelete.size(); ++i)
+ releaseElement(m_elementsToDelete.at(i), true);
+
+ destroyGraphicsResources();
+
+ delete m_visualizer;
+}
+
+void Renderer::destroyGraphicsResources()
+{
+ // If this is from the dtor, then the shader manager and its already
+ // prepared shaders will stay around for other renderers -> the cached data
+ // in the rhi shaders have to be purged as it may refer to samplers we
+ // are going to destroy.
+ m_shaderManager->clearCachedRendererData();
+
+ qDeleteAll(m_samplers);
+ m_stencilClipCommon.reset();
+ delete m_dummyTexture;
+ m_visualizer->releaseResources();
+}
+
+void Renderer::releaseCachedResources()
+{
+ m_shaderManager->invalidated();
+
+ destroyGraphicsResources();
+
+ m_samplers.clear();
+ m_dummyTexture = nullptr;
+
+ m_rhi->releaseCachedResources();
+
+ m_vertexUploadPool.shrink(0);
+ m_vertexUploadPool.reset();
+ m_indexUploadPool.shrink(0);
+ m_indexUploadPool.reset();
+
+ for (int i = 0; i < m_vboPool.size(); ++i)
+ delete m_vboPool.at(i);
+ m_vboPool.reset();
+
+ for (int i = 0; i < m_iboPool.size(); ++i)
+ delete m_iboPool.at(i);
+ m_iboPool.reset();
}
void Renderer::invalidateAndRecycleBatch(Batch *b)
{
+ if (b->vbo.buf != nullptr)
+ m_vboPool.add(b->vbo.buf);
+ if (b->ibo.buf != nullptr)
+ m_iboPool.add(b->ibo.buf);
+ b->vbo.buf = nullptr;
+ b->ibo.buf = nullptr;
b->invalidate();
for (int i=0; i<m_batchPool.size(); ++i)
if (b == m_batchPool.at(i))
@@ -875,24 +1006,13 @@ void Renderer::invalidateAndRecycleBatch(Batch *b)
m_batchPool.add(b);
}
-/* The code here does a CPU-side allocation which might seem like a performance issue
- * compared to using glMapBuffer or glMapBufferRange which would give me back
- * potentially GPU allocated memory and saving me one deep-copy, but...
- *
- * Because we do a lot of CPU-side transformations, we need random-access memory
- * and the memory returned from glMapBuffer/glMapBufferRange is typically
- * uncached and thus very slow for our purposes.
- *
- * ref: http://www.opengl.org/wiki/Buffer_Object
- */
-void Renderer::map(Buffer *buffer, int byteSize, bool isIndexBuf)
+void Renderer::map(Buffer *buffer, quint32 byteSize, bool isIndexBuf)
{
- if (!m_context->hasBrokenIndexBufferObjects() && m_visualizeMode == VisualizeNothing) {
+ if (m_visualizer->mode() == Visualizer::VisualizeNothing) {
// Common case, use a shared memory pool for uploading vertex data to avoid
// excessive reevaluation
- QDataBuffer<char> &pool = m_context->separateIndexBuffer() && isIndexBuf
- ? m_indexUploadPool : m_vertexUploadPool;
- if (byteSize > pool.size())
+ QDataBuffer<char> &pool = isIndexBuf ? m_indexUploadPool : m_vertexUploadPool;
+ if (byteSize > quint32(pool.size()))
pool.resize(byteSize);
buffer->data = pool.data();
} else if (buffer->size != byteSize) {
@@ -905,15 +1025,74 @@ void Renderer::map(Buffer *buffer, int byteSize, bool isIndexBuf)
void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
{
- if (buffer->id == 0)
- glGenBuffers(1, &buffer->id);
- GLenum target = isIndexBuf ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER;
- glBindBuffer(target, buffer->id);
- glBufferData(target, buffer->size, buffer->data, m_bufferStrategy);
+ // Batches are pooled and reused which means the QRhiBuffer will be
+ // still valid in a recycled Batch. We only hit the newBuffer() path
+ // when there are no buffers to recycle.
+ QDataBuffer<QRhiBuffer *> *bufferPool = isIndexBuf ? &m_iboPool : &m_vboPool;
+ if (!buffer->buf && bufferPool->isEmpty()) {
+ buffer->buf = m_rhi->newBuffer(QRhiBuffer::Immutable,
+ isIndexBuf ? QRhiBuffer::IndexBuffer : QRhiBuffer::VertexBuffer,
+ buffer->size);
+ if (!buffer->buf->create()) {
+ qWarning("Failed to build vertex/index buffer of size %u", buffer->size);
+ delete buffer->buf;
+ buffer->buf = nullptr;
+ }
+ } else {
+ if (!buffer->buf) {
+ const quint32 expectedSize = buffer->size;
+ qsizetype foundBufferIndex = 0;
+ for (qsizetype i = 0; i < bufferPool->size(); ++i) {
+ QRhiBuffer *testBuffer = bufferPool->at(i);
+ if (!buffer->buf
+ || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
+ || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
+ foundBufferIndex = i;
+ buffer->buf = testBuffer;
+ if (buffer->buf->size() == expectedSize)
+ break;
+ }
+ }
- if (!m_context->hasBrokenIndexBufferObjects() && m_visualizeMode == VisualizeNothing) {
- buffer->data = nullptr;
+ if (foundBufferIndex < bufferPool->size() - 1) {
+ qSwap(bufferPool->data()[foundBufferIndex],
+ bufferPool->data()[bufferPool->size() - 1]);
+ }
+ bufferPool->pop_back();
+ }
+
+ bool needsRebuild = false;
+ if (buffer->buf->size() < buffer->size) {
+ buffer->buf->setSize(buffer->size);
+ needsRebuild = true;
+ }
+ if (buffer->buf->type() != QRhiBuffer::Dynamic
+ && buffer->nonDynamicChangeCount > DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD)
+ {
+ buffer->buf->setType(QRhiBuffer::Dynamic);
+ buffer->nonDynamicChangeCount = 0;
+ needsRebuild = true;
+ }
+ if (needsRebuild) {
+ if (!buffer->buf->create()) {
+ qWarning("Failed to (re)build vertex/index buffer of size %u", buffer->size);
+ delete buffer->buf;
+ buffer->buf = nullptr;
+ }
+ }
+ }
+ if (buffer->buf) {
+ if (buffer->buf->type() != QRhiBuffer::Dynamic) {
+ m_resourceUpdates->uploadStaticBuffer(buffer->buf,
+ 0, buffer->size, buffer->data);
+ buffer->nonDynamicChangeCount += 1;
+ } else {
+ m_resourceUpdates->updateDynamicBuffer(buffer->buf, 0, buffer->size,
+ buffer->data);
+ }
}
+ if (m_visualizer->mode() == Visualizer::VisualizeNothing)
+ buffer->data = nullptr;
}
BatchRootInfo *Renderer::batchRootInfo(Node *node)
@@ -1039,7 +1218,7 @@ void Renderer::nodeWasAdded(QSGNode *node, Node *shadowParent)
Q_ASSERT(!m_renderNodeElements.contains(rn));
m_renderNodeElements.insert(e->renderNode, e);
if (!rn->flags().testFlag(QSGRenderNode::DepthAwareRendering))
- m_useDepthBuffer = false;
+ m_forceNoDepthBuffer = true;
m_rebuild |= FullRebuild;
}
@@ -1075,6 +1254,7 @@ void Renderer::nodeWasRemoved(Node *node)
}
if (e->batch) {
e->batch->needsUpload = true;
+ e->batch->needsPurge = true;
}
}
@@ -1096,11 +1276,16 @@ void Renderer::nodeWasRemoved(Node *node)
if (e) {
e->removed = true;
m_elementsToDelete.add(e);
-
if (m_renderNodeElements.isEmpty()) {
- static bool useDepth = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
- m_useDepthBuffer = useDepth && m_context->openglContext()->format().depthBufferSize() > 0;
+ m_forceNoDepthBuffer = false;
+ // Must have a full rebuild given useDepthBuffer() now returns
+ // a different value than before, meaning there can once again
+ // be an opaque pass.
+ m_rebuild |= FullRebuild;
}
+
+ if (e->batch != nullptr)
+ e->batch->needsPurge = true;
}
}
@@ -1165,6 +1350,11 @@ void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
// to avoid that any of the others are processed twice.
if (state & QSGNode::DirtySubtreeBlocked) {
Node *sn = m_nodes.value(node);
+
+ // Force a batch rebuild if this includes an opacity change
+ if (state & QSGNode::DirtyOpacity)
+ m_rebuild |= FullRebuild;
+
bool blocked = node->isSubtreeBlocked();
if (blocked && sn) {
nodeChanged(node, QSGNode::DirtyNodeRemoved);
@@ -1299,7 +1489,7 @@ void Renderer::buildRenderLists(QSGNode *node)
Q_ASSERT(e);
bool opaque = gn->inheritedOpacity() > OPAQUE_LIMIT && !(gn->activeMaterial()->flags() & QSGMaterial::Blending);
- if (opaque && m_useDepthBuffer)
+ if (opaque && useDepthBuffer())
m_opaqueRenderList << e;
else
m_alphaRenderList << e;
@@ -1471,11 +1661,19 @@ void Renderer::invalidateBatchAndOverlappingRenderOrders(Batch *batch)
Q_ASSERT(batch);
Q_ASSERT(batch->first);
+#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
if (m_renderOrderRebuildLower < 0 || batch->first->order < m_renderOrderRebuildLower)
m_renderOrderRebuildLower = batch->first->order;
if (m_renderOrderRebuildUpper < 0 || batch->lastOrderInBatch > m_renderOrderRebuildUpper)
m_renderOrderRebuildUpper = batch->lastOrderInBatch;
+ int first = m_renderOrderRebuildLower;
+ int last = m_renderOrderRebuildUpper;
+#else
+ int first = batch->first->order;
+ int last = batch->lastOrderInBatch;
+#endif
+
batch->invalidate();
for (int i=0; i<m_alphaBatches.size(); ++i) {
@@ -1483,7 +1681,7 @@ void Renderer::invalidateBatchAndOverlappingRenderOrders(Batch *batch)
if (b->first) {
int bf = b->first->order;
int bl = b->lastOrderInBatch;
- if (bl > m_renderOrderRebuildLower && bf < m_renderOrderRebuildUpper)
+ if (bl > first && bf < last)
b->invalidate();
}
}
@@ -1539,10 +1737,11 @@ void Renderer::prepareOpaqueBatches()
if (gni->clipList() == gnj->clipList()
&& gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
- && (gni->geometry()->drawingMode() != GL_LINES || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth())
+ && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth())
&& gni->geometry()->attributes() == gnj->geometry()->attributes()
&& gni->inheritedOpacity() == gnj->inheritedOpacity()
&& gni->activeMaterial()->type() == gnj->activeMaterial()->type()
+ && gni->activeMaterial()->viewCount() == gnj->activeMaterial()->viewCount()
&& gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
ej->batch = batch;
next->nextInBatch = ej;
@@ -1558,7 +1757,11 @@ bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
{
for (int i=first; i<=last; ++i) {
Element *e = m_alphaRenderList.at(i);
+#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
if (!e || e->batch)
+#else
+ if (!e)
+#endif
continue;
Q_ASSERT(e->boundsComputed);
if (e->bounds.intersects(bounds))
@@ -1629,8 +1832,12 @@ void Renderer::prepareAlphaBatches()
continue;
if (ej->root != ei->root || ej->isRenderNode)
break;
- if (ej->batch)
+ if (ej->batch) {
+#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
+ overlapBounds |= ej->bounds;
+#endif
continue;
+ }
QSGGeometryNode *gnj = ej->node;
if (gnj->geometry()->vertexCount() == 0)
@@ -1638,10 +1845,15 @@ void Renderer::prepareAlphaBatches()
if (gni->clipList() == gnj->clipList()
&& gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
- && (gni->geometry()->drawingMode() != GL_LINES || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth())
+ && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines
+ || (gni->geometry()->lineWidth() == gnj->geometry()->lineWidth()
+ // Must not do overlap checks when the line width is not 1,
+ // we have no knowledge how such lines are rasterized.
+ && gni->geometry()->lineWidth() == 1.0f))
&& gni->geometry()->attributes() == gnj->geometry()->attributes()
&& gni->inheritedOpacity() == gnj->inheritedOpacity()
&& gni->activeMaterial()->type() == gnj->activeMaterial()->type()
+ && gni->activeMaterial()->viewCount() == gnj->activeMaterial()->viewCount()
&& gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
ej->batch = batch;
@@ -1666,19 +1878,20 @@ void Renderer::prepareAlphaBatches()
}
-static inline int qsg_fixIndexCount(int iCount, GLenum drawMode) {
+static inline int qsg_fixIndexCount(int iCount, int drawMode)
+{
switch (drawMode) {
- case GL_TRIANGLE_STRIP:
+ case QSGGeometry::DrawTriangleStrip:
// Merged triangle strips need to contain degenerate triangles at the beginning and end.
// One could save 2 uploaded ushorts here by ditching the padding for the front of the
// first and the end of the last, but for simplicity, we simply don't care.
// Those extra triangles will be skipped while drawing to preserve the strip's parity
// anyhow.
return iCount + 2;
- case GL_LINES:
+ case QSGGeometry::DrawLines:
// For lines we drop the last vertex if the number of vertices is uneven.
return iCount - (iCount % 2);
- case GL_TRIANGLES:
+ case QSGGeometry::DrawTriangles:
// For triangles we drop trailing vertices until the result is divisible by 3.
return iCount - (iCount % 3);
default:
@@ -1686,6 +1899,12 @@ static inline int qsg_fixIndexCount(int iCount, GLenum drawMode) {
}
}
+static inline float calculateElementZOrder(const Element *e, qreal zRange)
+{
+ // Clamp the zOrder to within the min and max depth of the viewport.
+ return std::clamp(1.0f - float(e->order * zRange), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
+}
+
/* These parameters warrant some explanation...
*
* vaOffset: The byte offset into the vertex data to the location of the
@@ -1700,12 +1919,13 @@ static inline int qsg_fixIndexCount(int iCount, GLenum drawMode) {
* iBase: The starting index for this element in the batch
*/
-void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, quint16 *iBase, int *indexCount)
+void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
{
if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
QSGGeometry *g = e->node->geometry();
const QMatrix4x4 &localx = *e->node->matrix();
+ const float *localxdata = localx.constData();
const int vCount = g->vertexCount();
const int vSize = g->sizeOfVertex();
@@ -1713,62 +1933,94 @@ void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData,
// apply vertex transform..
char *vdata = *vertexData + vaOffset;
- if (((const QMatrix4x4_Accessor &) localx).flagBits == 1) {
+ if (localx.flags() == QMatrix4x4::Translation) {
for (int i=0; i<vCount; ++i) {
Pt *p = (Pt *) vdata;
- p->x += ((const QMatrix4x4_Accessor &) localx).m[3][0];
- p->y += ((const QMatrix4x4_Accessor &) localx).m[3][1];
+ p->x += localxdata[12];
+ p->y += localxdata[13];
vdata += vSize;
}
- } else if (((const QMatrix4x4_Accessor &) localx).flagBits > 1) {
+ } else if (localx.flags() > QMatrix4x4::Translation) {
for (int i=0; i<vCount; ++i) {
((Pt *) vdata)->map(localx);
vdata += vSize;
}
}
- if (m_useDepthBuffer) {
+ if (useDepthBuffer()) {
float *vzorder = (float *) *zData;
- float zorder = 1.0f - e->order * m_zRange;
+ float zorder = calculateElementZOrder(e, m_zRange);
for (int i=0; i<vCount; ++i)
vzorder[i] = zorder;
*zData += vCount * sizeof(float);
}
int iCount = g->indexCount();
- quint16 *indices = (quint16 *) *indexData;
+ if (m_uint32IndexForRhi) {
+ // can only happen when using the rhi
+ quint32 *iBase = (quint32 *) iBasePtr;
+ quint32 *indices = (quint32 *) *indexData;
+ if (iCount == 0) {
+ iCount = vCount;
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
+ *indices++ = *iBase;
+ else
+ iCount = qsg_fixIndexCount(iCount, g->drawingMode());
- if (iCount == 0) {
- iCount = vCount;
- if (g->drawingMode() == GL_TRIANGLE_STRIP)
- *indices++ = *iBase;
- else
- iCount = qsg_fixIndexCount(iCount, g->drawingMode());
+ for (int i=0; i<iCount; ++i)
+ indices[i] = *iBase + i;
+ } else {
+ // source index data in QSGGeometry is always ushort (we would not merge otherwise)
+ const quint16 *srcIndices = g->indexDataAsUShort();
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
+ *indices++ = *iBase + srcIndices[0];
+ else
+ iCount = qsg_fixIndexCount(iCount, g->drawingMode());
- for (int i=0; i<iCount; ++i)
- indices[i] = *iBase + i;
+ for (int i=0; i<iCount; ++i)
+ indices[i] = *iBase + srcIndices[i];
+ }
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
+ indices[iCount] = indices[iCount - 1];
+ iCount += 2;
+ }
+ *iBase += vCount;
} else {
- const quint16 *srcIndices = g->indexDataAsUShort();
- if (g->drawingMode() == GL_TRIANGLE_STRIP)
- *indices++ = *iBase + srcIndices[0];
- else
- iCount = qsg_fixIndexCount(iCount, g->drawingMode());
+ // normally batching is only done for ushort index data
+ quint16 *iBase = (quint16 *) iBasePtr;
+ quint16 *indices = (quint16 *) *indexData;
+ if (iCount == 0) {
+ iCount = vCount;
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
+ *indices++ = *iBase;
+ else
+ iCount = qsg_fixIndexCount(iCount, g->drawingMode());
- for (int i=0; i<iCount; ++i)
- indices[i] = *iBase + srcIndices[i];
- }
- if (g->drawingMode() == GL_TRIANGLE_STRIP) {
- indices[iCount] = indices[iCount - 1];
- iCount += 2;
+ for (int i=0; i<iCount; ++i)
+ indices[i] = *iBase + i;
+ } else {
+ const quint16 *srcIndices = g->indexDataAsUShort();
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
+ *indices++ = *iBase + srcIndices[0];
+ else
+ iCount = qsg_fixIndexCount(iCount, g->drawingMode());
+
+ for (int i=0; i<iCount; ++i)
+ indices[i] = *iBase + srcIndices[i];
+ }
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
+ indices[iCount] = indices[iCount - 1];
+ iCount += 2;
+ }
+ *iBase += vCount;
}
*vertexData += vCount * vSize;
- *indexData += iCount * sizeof(quint16);
- *iBase += vCount;
+ *indexData += iCount * mergedIndexElemSize();
*indexCount += iCount;
}
-static QMatrix4x4 qsg_matrixForRoot(Node *node)
+QMatrix4x4 qsg_matrixForRoot(Node *node)
{
if (node->type() == QSGNode::TransformNodeType)
return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
@@ -1779,66 +2031,67 @@ static QMatrix4x4 qsg_matrixForRoot(Node *node)
void Renderer::uploadBatch(Batch *b)
{
- // Early out if nothing has changed in this batch..
- if (!b->needsUpload) {
- if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
- return;
- }
-
- if (!b->first) {
- if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
- return;
- }
-
- if (b->isRenderNode) {
- if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
- return;
- }
+ // Early out if nothing has changed in this batch..
+ if (!b->needsUpload) {
+ if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
+ return;
+ }
- // Figure out if we can merge or not, if not, then just render the batch as is..
- Q_ASSERT(b->first);
- Q_ASSERT(b->first->node);
+ if (!b->first) {
+ if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
+ return;
+ }
- QSGGeometryNode *gn = b->first->node;
- QSGGeometry *g = gn->geometry();
- QSGMaterial::Flags flags = gn->activeMaterial()->flags();
- bool canMerge = (g->drawingMode() == GL_TRIANGLES || g->drawingMode() == GL_TRIANGLE_STRIP ||
- g->drawingMode() == GL_LINES || g->drawingMode() == GL_POINTS)
- && b->positionAttribute >= 0
- && g->indexType() == GL_UNSIGNED_SHORT
- && (flags & (QSGMaterial::CustomCompileStep | QSGMaterial_FullMatrix)) == 0
- && ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot())
- && b->isSafeToBatch();
+ if (b->isRenderNode) {
+ if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
+ return;
+ }
- b->merged = canMerge;
+ // Figure out if we can merge or not, if not, then just render the batch as is..
+ Q_ASSERT(b->first);
+ Q_ASSERT(b->first->node);
- // Figure out how much memory we need...
- b->vertexCount = 0;
- b->indexCount = 0;
- int unmergedIndexSize = 0;
- Element *e = b->first;
+ QSGGeometryNode *gn = b->first->node;
+ QSGGeometry *g = gn->geometry();
+ QSGMaterial::Flags flags = gn->activeMaterial()->flags();
+ bool canMerge = (g->drawingMode() == QSGGeometry::DrawTriangles || g->drawingMode() == QSGGeometry::DrawTriangleStrip ||
+ g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawPoints)
+ && b->positionAttribute >= 0
+ && (g->indexType() == QSGGeometry::UnsignedShortType && g->indexCount() > 0)
+ && (flags & (QSGMaterial::NoBatching | QSGMaterial_FullMatrix)) == 0
+ && ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot())
+ && b->isSafeToBatch();
+
+ b->merged = canMerge;
+
+ // Figure out how much memory we need...
+ b->vertexCount = 0;
+ b->indexCount = 0;
+ int unmergedIndexSize = 0;
+ Element *e = b->first;
- while (e) {
- QSGGeometry *eg = e->node->geometry();
- b->vertexCount += eg->vertexCount();
- int iCount = eg->indexCount();
- if (b->merged) {
- if (iCount == 0)
- iCount = eg->vertexCount();
- iCount = qsg_fixIndexCount(iCount, g->drawingMode());
- } else {
- unmergedIndexSize += iCount * eg->sizeOfIndex();
- }
- b->indexCount += iCount;
- e = e->nextInBatch;
+ while (e) {
+ QSGGeometry *eg = e->node->geometry();
+ b->vertexCount += eg->vertexCount();
+ int iCount = eg->indexCount();
+ if (b->merged) {
+ if (iCount == 0)
+ iCount = eg->vertexCount();
+ iCount = qsg_fixIndexCount(iCount, g->drawingMode());
+ } else {
+ const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
+ unmergedIndexSize += iCount * effectiveIndexSize;
}
+ b->indexCount += iCount;
+ e = e->nextInBatch;
+ }
- // Abort if there are no vertices in this batch.. We abort this late as
- // this is a broken usecase which we do not care to optimize for...
- if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
- return;
+ // Abort if there are no vertices in this batch.. We abort this late as
+ // this is a broken usecase which we do not care to optimize for...
+ if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
+ return;
- /* Allocate memory for this batch. Merged batches are divided into three separate blocks
+ /* Allocate memory for this batch. Merged batches are divided into three separate blocks
1. Vertex data for all elements, as they were in the QSGGeometry object, but
with the tranform relative to this batch's root applied. The vertex data
is otherwise unmodified.
@@ -1850,189 +2103,263 @@ void Renderer::uploadBatch(Batch *b)
primitive. These are unsigned shorts for merged and arbitrary for
non-merged.
*/
- int bufferSize = b->vertexCount * g->sizeOfVertex();
- int ibufferSize = 0;
- if (b->merged) {
- ibufferSize = b->indexCount * sizeof(quint16);
- if (m_useDepthBuffer)
- bufferSize += b->vertexCount * sizeof(float);
- } else {
- ibufferSize = unmergedIndexSize;
- }
+ int bufferSize = b->vertexCount * g->sizeOfVertex();
+ int ibufferSize = 0;
+ if (b->merged) {
+ ibufferSize = b->indexCount * mergedIndexElemSize();
+ if (useDepthBuffer())
+ bufferSize += b->vertexCount * sizeof(float);
+ } else {
+ ibufferSize = unmergedIndexSize;
+ }
- const bool separateIndexBuffer = m_context->separateIndexBuffer();
- if (separateIndexBuffer)
- map(&b->ibo, ibufferSize, true);
- else
- bufferSize += ibufferSize;
- map(&b->vbo, bufferSize);
+ map(&b->ibo, ibufferSize, true);
+ map(&b->vbo, bufferSize);
- if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
- << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
- << " vbo:" << b->vbo.id << ":" << b->vbo.size;
+ if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
+ << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
+ << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
- if (b->merged) {
- char *vertexData = b->vbo.data;
- char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
- char *indexData = separateIndexBuffer
- ? b->ibo.data
- : zData + (int(m_useDepthBuffer) * b->vertexCount * sizeof(float));
-
- quint16 iOffset = 0;
- e = b->first;
- int verticesInSet = 0;
- int indicesInSet = 0;
- b->drawSets.reset();
- int drawSetIndices = separateIndexBuffer ? 0 : indexData - vertexData;
- const auto indexBase = separateIndexBuffer ? b->ibo.data : b->vbo.data;
- b->drawSets << DrawSet(0, zData - vertexData, drawSetIndices);
- while (e) {
- verticesInSet += e->node->geometry()->vertexCount();
- if (verticesInSet > 0xffff) {
- b->drawSets.last().indexCount = indicesInSet;
- if (g->drawingMode() == GL_TRIANGLE_STRIP) {
- b->drawSets.last().indices += 1 * sizeof(quint16);
- b->drawSets.last().indexCount -= 2;
- }
- drawSetIndices = indexData - indexBase;
- b->drawSets << DrawSet(vertexData - b->vbo.data,
- zData - b->vbo.data,
- drawSetIndices);
- iOffset = 0;
- verticesInSet = e->node->geometry()->vertexCount();
- indicesInSet = 0;
+ if (b->merged) {
+ char *vertexData = b->vbo.data;
+ char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
+ char *indexData = b->ibo.data;
+
+ quint16 iOffset16 = 0;
+ quint32 iOffset32 = 0;
+ e = b->first;
+ uint verticesInSet = 0;
+ // Start a new set already after 65534 vertices because 0xFFFF may be
+ // used for an always-on primitive restart with some apis (adapt for
+ // uint32 indices as appropriate).
+ const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
+ int indicesInSet = 0;
+ b->drawSets.reset();
+ int drawSetIndices = 0;
+ const char *indexBase = b->ibo.data;
+ b->drawSets << DrawSet(0, zData - vertexData, drawSetIndices);
+ while (e) {
+ verticesInSet += e->node->geometry()->vertexCount();
+ if (verticesInSet > verticesInSetLimit) {
+ b->drawSets.last().indexCount = indicesInSet;
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
+ b->drawSets.last().indices += 1 * mergedIndexElemSize();
+ b->drawSets.last().indexCount -= 2;
}
- uploadMergedElement(e, b->positionAttribute, &vertexData, &zData, &indexData, &iOffset, &indicesInSet);
- e = e->nextInBatch;
- }
- b->drawSets.last().indexCount = indicesInSet;
- // We skip the very first and very last degenerate triangles since they aren't needed
- // and the first one would reverse the vertex ordering of the merged strips.
- if (g->drawingMode() == GL_TRIANGLE_STRIP) {
- b->drawSets.last().indices += 1 * sizeof(quint16);
- b->drawSets.last().indexCount -= 2;
+ drawSetIndices = indexData - indexBase;
+ b->drawSets << DrawSet(vertexData - b->vbo.data,
+ zData - b->vbo.data,
+ drawSetIndices);
+ iOffset16 = 0;
+ iOffset32 = 0;
+ verticesInSet = e->node->geometry()->vertexCount();
+ indicesInSet = 0;
}
- } else {
- char *vboData = b->vbo.data;
- char *iboData = separateIndexBuffer ? b->ibo.data
- : vboData + b->vertexCount * g->sizeOfVertex();
- Element *e = b->first;
- while (e) {
- QSGGeometry *g = e->node->geometry();
- int vbs = g->vertexCount() * g->sizeOfVertex();
- memcpy(vboData, g->vertexData(), vbs);
- vboData = vboData + vbs;
- if (g->indexCount()) {
- int ibs = g->indexCount() * g->sizeOfIndex();
+ void *iBasePtr = &iOffset16;
+ if (m_uint32IndexForRhi)
+ iBasePtr = &iOffset32;
+ uploadMergedElement(e, b->positionAttribute, &vertexData, &zData, &indexData, iBasePtr, &indicesInSet);
+ e = e->nextInBatch;
+ }
+ b->drawSets.last().indexCount = indicesInSet;
+ // We skip the very first and very last degenerate triangles since they aren't needed
+ // and the first one would reverse the vertex ordering of the merged strips.
+ if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
+ b->drawSets.last().indices += 1 * mergedIndexElemSize();
+ b->drawSets.last().indexCount -= 2;
+ }
+ } else {
+ char *vboData = b->vbo.data;
+ char *iboData = b->ibo.data;
+ Element *e = b->first;
+ while (e) {
+ QSGGeometry *g = e->node->geometry();
+ int vbs = g->vertexCount() * g->sizeOfVertex();
+ memcpy(vboData, g->vertexData(), vbs);
+ vboData = vboData + vbs;
+ const int indexCount = g->indexCount();
+ if (indexCount) {
+ const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
+ const int ibs = indexCount * effectiveIndexSize;
+ if (g->sizeOfIndex() == effectiveIndexSize) {
memcpy(iboData, g->indexData(), ibs);
- iboData += ibs;
+ } else {
+ if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
+ quint16 *src = g->indexDataAsUShort();
+ quint32 *dst = (quint32 *) iboData;
+ for (int i = 0; i < indexCount; ++i)
+ dst[i] = src[i];
+ } else {
+ Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
+ }
}
- e = e->nextInBatch;
+ iboData += ibs;
}
+ e = e->nextInBatch;
}
+ }
#ifndef QT_NO_DEBUG_OUTPUT
- if (Q_UNLIKELY(debug_upload())) {
- const char *vd = b->vbo.data;
- qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
- for (int i=0; i<b->vertexCount; ++i) {
- QDebug dump = qDebug().nospace();
- dump << " --- " << i << ": ";
- int offset = 0;
- for (int a=0; a<g->attributeCount(); ++a) {
- const QSGGeometry::Attribute &attr = g->attributes()[a];
- dump << attr.position << ":(" << attr.tupleSize << ",";
- if (attr.type == GL_FLOAT) {
- dump << "float ";
- if (attr.isVertexCoordinate)
- dump << "* ";
- for (int t=0; t<attr.tupleSize; ++t)
- dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
- } else if (attr.type == GL_UNSIGNED_BYTE) {
- dump << "ubyte ";
- for (int t=0; t<attr.tupleSize; ++t)
- dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
- }
- dump << ") ";
- offset += attr.tupleSize * size_of_type(attr.type);
+ if (Q_UNLIKELY(debug_upload())) {
+ const char *vd = b->vbo.data;
+ qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
+ for (int i=0; i<b->vertexCount; ++i) {
+ QDebug dump = qDebug().nospace();
+ dump << " --- " << i << ": ";
+ int offset = 0;
+ for (int a=0; a<g->attributeCount(); ++a) {
+ const QSGGeometry::Attribute &attr = g->attributes()[a];
+ dump << attr.position << ":(" << attr.tupleSize << ",";
+ if (attr.type == QSGGeometry::FloatType) {
+ dump << "float ";
+ if (attr.isVertexCoordinate)
+ dump << "* ";
+ for (int t=0; t<attr.tupleSize; ++t)
+ dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
+ } else if (attr.type == QSGGeometry::UnsignedByteType) {
+ dump << "ubyte ";
+ for (int t=0; t<attr.tupleSize; ++t)
+ dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
}
- if (b->merged && m_useDepthBuffer) {
- float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
- dump << " Z:(" << zorder << ")";
- }
- vd += g->sizeOfVertex();
+ dump << ") ";
+ offset += attr.tupleSize * size_of_type(attr.type);
+ }
+ if (b->merged && useDepthBuffer()) {
+ float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
+ dump << " Z:(" << zorder << ")";
}
+ vd += g->sizeOfVertex();
+ }
- if (!b->drawSets.isEmpty()) {
- const quint16 *id = (const quint16 *)(separateIndexBuffer
- ? b->ibo.data
- : b->vbo.data + b->drawSets.at(0).indices);
+ if (!b->drawSets.isEmpty()) {
+ if (m_uint32IndexForRhi) {
+ const quint32 *id = (const quint32 *) b->ibo.data;
{
QDebug iDump = qDebug();
iDump << " -- Index Data, count:" << b->indexCount;
for (int i=0; i<b->indexCount; ++i) {
if ((i % 24) == 0)
- iDump << endl << " --- ";
+ iDump << Qt::endl << " --- ";
iDump << id[i];
}
}
-
- for (int i=0; i<b->drawSets.size(); ++i) {
- const DrawSet &s = b->drawSets.at(i);
- qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
+ } else {
+ const quint16 *id = (const quint16 *) b->ibo.data;
+ {
+ QDebug iDump = qDebug();
+ iDump << " -- Index Data, count:" << b->indexCount;
+ for (int i=0; i<b->indexCount; ++i) {
+ if ((i % 24) == 0)
+ iDump << Qt::endl << " --- ";
+ iDump << id[i];
+ }
}
}
+
+ for (int i=0; i<b->drawSets.size(); ++i) {
+ const DrawSet &s = b->drawSets.at(i);
+ qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
+ }
}
+ }
#endif // QT_NO_DEBUG_OUTPUT
- unmap(&b->vbo);
- if (separateIndexBuffer)
- unmap(&b->ibo, true);
+ unmap(&b->vbo);
+ unmap(&b->ibo, true);
- if (Q_UNLIKELY(debug_upload())) qDebug() << " --- vertex/index buffers unmapped, batch upload completed...";
+ if (Q_UNLIKELY(debug_upload())) qDebug() << " --- vertex/index buffers unmapped, batch upload completed...";
- b->needsUpload = false;
+ b->needsUpload = false;
- if (Q_UNLIKELY(debug_render()))
- b->uploadedThisFrame = true;
+ if (Q_UNLIKELY(debug_render()))
+ b->uploadedThisFrame = true;
}
-/*!
- * Convenience function to set up the stencil buffer for clipping based on \a clip.
- *
- * If the clip is a pixel aligned rectangle, this function will use glScissor instead
- * of stencil.
- */
-Renderer::ClipType Renderer::updateStencilClip(const QSGClipNode *clip)
+void Renderer::applyClipStateToGraphicsState()
+{
+ m_gstate.usesScissor = (m_currentClipState.type & ClipState::ScissorClip);
+ m_gstate.stencilTest = (m_currentClipState.type & ClipState::StencilClip);
+}
+
+QRhiGraphicsPipeline *Renderer::buildStencilPipeline(const Batch *batch, bool firstStencilClipInBatch)
{
- if (!clip) {
- glDisable(GL_STENCIL_TEST);
- glDisable(GL_SCISSOR_TEST);
- return NoClip;
+ QRhiGraphicsPipeline *ps = m_rhi->newGraphicsPipeline();
+ ps->setFlags(QRhiGraphicsPipeline::UsesStencilRef);
+ QRhiGraphicsPipeline::TargetBlend blend;
+ blend.colorWrite = {};
+ ps->setTargetBlends({ blend });
+ ps->setSampleCount(renderTarget().rt->sampleCount());
+ ps->setStencilTest(true);
+ QRhiGraphicsPipeline::StencilOpState stencilOp;
+ if (firstStencilClipInBatch) {
+ stencilOp.compareOp = QRhiGraphicsPipeline::Always;
+ stencilOp.failOp = QRhiGraphicsPipeline::Keep;
+ stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
+ stencilOp.passOp = QRhiGraphicsPipeline::Replace;
+ } else {
+ stencilOp.compareOp = QRhiGraphicsPipeline::Equal;
+ stencilOp.failOp = QRhiGraphicsPipeline::Keep;
+ stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
+ stencilOp.passOp = QRhiGraphicsPipeline::IncrementAndClamp;
}
+ ps->setStencilFront(stencilOp);
+ ps->setStencilBack(stencilOp);
+
+ ps->setTopology(m_stencilClipCommon.topology);
+
+ ps->setMultiViewCount(renderTarget().multiViewCount);
- ClipType clipType = NoClip;
- GLuint vbo = 0;
- int vboSize = 0;
+ ps->setShaderStages({ QRhiShaderStage(QRhiShaderStage::Vertex, m_stencilClipCommon.vs),
+ QRhiShaderStage(QRhiShaderStage::Fragment, m_stencilClipCommon.fs) });
+ ps->setVertexInputLayout(m_stencilClipCommon.inputLayout);
+ ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
+ ps->setRenderPassDescriptor(renderTarget().rpDesc);
+
+ if (!ps->create()) {
+ qWarning("Failed to build stencil clip pipeline");
+ delete ps;
+ return nullptr;
+ }
- bool useVBO = false;
- QOpenGLContext *ctx = m_context->openglContext();
- QSurfaceFormat::OpenGLContextProfile profile = ctx->format().profile();
+ return ps;
+}
- if (!ctx->isOpenGLES() && profile == QSurfaceFormat::CoreProfile) {
- // VBO are more expensive, so only use them if we must.
- useVBO = true;
+void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch)
+{
+ // Note: No use of the clip-related speparate m_current* vars is allowed
+ // here. All stored in batch->clipState instead. To collect state during
+ // the prepare steps, m_currentClipState is used. It should not be used in
+ // the render steps afterwards.
+
+ // The stenciling logic is slightly different from Qt 5's direct OpenGL version
+ // as we cannot just randomly clear the stencil buffer. We now put all clip
+ // shapes into the stencil buffer for all batches in the frame. This means
+ // that the number of total clips in a scene is reduced (since the stencil
+ // value cannot exceed 255) but we do not need any clears inbetween.
+
+ Q_ASSERT(m_rhi);
+ batch->stencilClipState.updateStencilBuffer = false;
+ if (clipList == m_currentClipState.clipList || Q_UNLIKELY(debug_noclip())) {
+ applyClipStateToGraphicsState();
+ batch->clipState = m_currentClipState;
+ return;
}
- glDisable(GL_SCISSOR_TEST);
+ ClipState::ClipType clipType = ClipState::NoClip;
+ QRect scissorRect;
+ QVarLengthArray<const QSGClipNode *, 4> stencilClipNodes;
+ const QSGClipNode *clip = clipList;
+
+ batch->stencilClipState.drawCalls.reset();
+ quint32 totalVSize = 0;
+ quint32 totalISize = 0;
+ quint32 totalUSize = 0;
+ const quint32 StencilClipUbufSize = 64;
- m_currentStencilValue = 0;
- m_currentScissorRect = QRect();
while (clip) {
- QMatrix4x4 m = m_current_projection_matrix;
+ QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
if (clip->matrix())
m *= *clip->matrix();
- // TODO: Check for multisampling and pixel grid alignment.
bool isRectangleWithNoPerspective = clip->isRectangular()
&& qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
@@ -2062,181 +2389,736 @@ Renderer::ClipType Renderer::updateStencilClip(const QSGClipNode *clip)
QRect deviceRect = this->deviceRect();
- GLint ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
- GLint iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
- GLint ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
- GLint iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
+ qint32 ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
+ qint32 iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
+ qint32 ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
+ qint32 iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
- if (!(clipType & ScissorClip)) {
- m_currentScissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
- glEnable(GL_SCISSOR_TEST);
- clipType |= ScissorClip;
+ if (!(clipType & ClipState::ScissorClip)) {
+ clipType |= ClipState::ScissorClip;
+ scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
} else {
- m_currentScissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
+ scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
}
- glScissor(m_currentScissorRect.x(), m_currentScissorRect.y(),
- m_currentScissorRect.width(), m_currentScissorRect.height());
} else {
- if (!(clipType & StencilClip)) {
- if (!m_clipProgram.isLinked()) {
- QSGShaderSourceBuilder::initializeProgramFromFiles(
- &m_clipProgram,
- QStringLiteral(":/qt-project.org/scenegraph/shaders/stencilclip.vert"),
- QStringLiteral(":/qt-project.org/scenegraph/shaders/stencilclip.frag"));
- m_clipProgram.bindAttributeLocation("vCoord", 0);
- m_clipProgram.link();
- m_clipMatrixId = m_clipProgram.uniformLocation("matrix");
- }
-
- glClearStencil(0);
- glClear(GL_STENCIL_BUFFER_BIT);
- glEnable(GL_STENCIL_TEST);
- glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
- glDepthMask(GL_FALSE);
+ clipType |= ClipState::StencilClip;
- m_clipProgram.bind();
- m_clipProgram.enableAttributeArray(0);
+ const QSGGeometry *g = clip->geometry();
+ Q_ASSERT(g->attributeCount() > 0);
- clipType |= StencilClip;
+ const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
+ // the 4 byte alignment may not actually be needed here
+ totalVSize = aligned(totalVSize, 4u) + vertexByteSize;
+ if (g->indexCount()) {
+ const int indexByteSize = g->sizeOfIndex() * g->indexCount();
+ // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
+ totalISize = aligned(totalISize, 4u) + indexByteSize;
}
+ // ubuf start offsets must be aligned (typically to 256 bytes)
+ totalUSize = aligned(totalUSize, m_ubufAlignment) + StencilClipUbufSize;
- glStencilFunc(GL_EQUAL, m_currentStencilValue, 0xff); // stencil test, ref, test mask
- glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // stencil fail, z fail, z pass
-
- const QSGGeometry *g = clip->geometry();
- Q_ASSERT(g->attributeCount() > 0);
- const QSGGeometry::Attribute *a = g->attributes();
+ stencilClipNodes.append(clip);
+ }
- const GLvoid *pointer;
- if (!useVBO) {
- pointer = g->vertexData();
- } else {
- if (!vbo)
- glGenBuffers(1, &vbo);
+ clip = clip->clipList();
+ }
- glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ if (clipType & ClipState::StencilClip) {
+ bool rebuildVBuf = false;
+ if (!batch->stencilClipState.vbuf) {
+ batch->stencilClipState.vbuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, totalVSize);
+ rebuildVBuf = true;
+ } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
+ batch->stencilClipState.vbuf->setSize(totalVSize);
+ rebuildVBuf = true;
+ }
+ if (rebuildVBuf) {
+ if (!batch->stencilClipState.vbuf->create()) {
+ qWarning("Failed to build stencil clip vertex buffer");
+ delete batch->stencilClipState.vbuf;
+ batch->stencilClipState.vbuf = nullptr;
+ return;
+ }
+ }
- const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
- if (vboSize < vertexByteSize) {
- vboSize = vertexByteSize;
- glBufferData(GL_ARRAY_BUFFER, vertexByteSize, g->vertexData(), GL_STATIC_DRAW);
- } else {
- glBufferSubData(GL_ARRAY_BUFFER, 0, vertexByteSize, g->vertexData());
+ if (totalISize) {
+ bool rebuildIBuf = false;
+ if (!batch->stencilClipState.ibuf) {
+ batch->stencilClipState.ibuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::IndexBuffer, totalISize);
+ rebuildIBuf = true;
+ } else if (batch->stencilClipState.ibuf->size() < totalISize) {
+ batch->stencilClipState.ibuf->setSize(totalISize);
+ rebuildIBuf = true;
+ }
+ if (rebuildIBuf) {
+ if (!batch->stencilClipState.ibuf->create()) {
+ qWarning("Failed to build stencil clip index buffer");
+ delete batch->stencilClipState.ibuf;
+ batch->stencilClipState.ibuf = nullptr;
+ return;
}
+ }
+ }
- pointer = nullptr;
+ bool rebuildUBuf = false;
+ if (!batch->stencilClipState.ubuf) {
+ batch->stencilClipState.ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalUSize);
+ rebuildUBuf = true;
+ } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
+ batch->stencilClipState.ubuf->setSize(totalUSize);
+ rebuildUBuf = true;
+ }
+ if (rebuildUBuf) {
+ if (!batch->stencilClipState.ubuf->create()) {
+ qWarning("Failed to build stencil clip uniform buffer");
+ delete batch->stencilClipState.ubuf;
+ batch->stencilClipState.ubuf = nullptr;
+ return;
}
+ }
- glVertexAttribPointer(0, a->tupleSize, a->type, GL_FALSE, g->sizeOfVertex(), pointer);
+ if (!batch->stencilClipState.srb) {
+ batch->stencilClipState.srb = m_rhi->newShaderResourceBindings();
+ const QRhiShaderResourceBinding ubufBinding = QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
+ 0, QRhiShaderResourceBinding::VertexStage, batch->stencilClipState.ubuf, StencilClipUbufSize);
+ batch->stencilClipState.srb->setBindings({ ubufBinding });
+ if (!batch->stencilClipState.srb->create()) {
+ qWarning("Failed to build stencil clip srb");
+ delete batch->stencilClipState.srb;
+ batch->stencilClipState.srb = nullptr;
+ return;
+ }
+ }
- m_clipProgram.setUniformValue(m_clipMatrixId, m);
+ quint32 vOffset = 0;
+ quint32 iOffset = 0;
+ quint32 uOffset = 0;
+ for (const QSGClipNode *clip : stencilClipNodes) {
+ const QSGGeometry *g = clip->geometry();
+ const QSGGeometry::Attribute *a = g->attributes();
+ StencilClipState::StencilDrawCall drawCall;
+ const bool firstStencilClipInBatch = batch->stencilClipState.drawCalls.isEmpty();
+
+ if (firstStencilClipInBatch) {
+ m_stencilClipCommon.inputLayout.setBindings({ QRhiVertexInputBinding(g->sizeOfVertex()) });
+ m_stencilClipCommon.inputLayout.setAttributes({ QRhiVertexInputAttribute(0, 0, qsg_vertexInputFormat(*a), 0) });
+ m_stencilClipCommon.topology = qsg_topology(g->drawingMode());
+ }
+#ifndef QT_NO_DEBUG
+ else {
+ if (qsg_topology(g->drawingMode()) != m_stencilClipCommon.topology)
+ qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
+ if (qsg_vertexInputFormat(*a) != m_stencilClipCommon.inputLayout.cbeginAttributes()->format())
+ qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
+ }
+#endif
+
+ drawCall.vbufOffset = aligned(vOffset, 4u);
+ const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
+ vOffset = drawCall.vbufOffset + vertexByteSize;
+
+ int indexByteSize = 0;
if (g->indexCount()) {
- glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
- } else {
- glDrawArrays(g->drawingMode(), 0, g->vertexCount());
+ drawCall.ibufOffset = aligned(iOffset, 4u);
+ indexByteSize = g->sizeOfIndex() * g->indexCount();
+ iOffset = drawCall.ibufOffset + indexByteSize;
}
- if (useVBO)
- glBindBuffer(GL_ARRAY_BUFFER, 0);
+ drawCall.ubufOffset = aligned(uOffset, m_ubufAlignment);
+ uOffset = drawCall.ubufOffset + StencilClipUbufSize;
+
+ QMatrix4x4 matrixYUpNDC = m_current_projection_matrix[0];
+ if (clip->matrix())
+ matrixYUpNDC *= *clip->matrix();
+
+ m_resourceUpdates->updateDynamicBuffer(batch->stencilClipState.ubuf, drawCall.ubufOffset, 64, matrixYUpNDC.constData());
+ m_resourceUpdates->updateDynamicBuffer(batch->stencilClipState.vbuf, drawCall.vbufOffset, vertexByteSize, g->vertexData());
+ if (indexByteSize)
+ m_resourceUpdates->updateDynamicBuffer(batch->stencilClipState.ibuf, drawCall.ibufOffset, indexByteSize, g->indexData());
+
+ // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
+ // then N+1, N+1, N+2, N+3, ... for the next batch,
+ // and so on.
+ // Note the different stencilOp for the first and the subsequent clips.
+ drawCall.stencilRef = firstStencilClipInBatch ? m_currentClipState.stencilRef + 1 : m_currentClipState.stencilRef;
+ m_currentClipState.stencilRef += 1;
+
+ drawCall.vertexCount = g->vertexCount();
+ drawCall.indexCount = g->indexCount();
+ drawCall.indexFormat = qsg_indexFormat(g);
+ batch->stencilClipState.drawCalls.add(drawCall);
+ }
+
+ if (!m_stencilClipCommon.vs.isValid())
+ m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
+
+ if (!m_stencilClipCommon.fs.isValid())
+ m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
+
+ if (!m_stencilClipCommon.replacePs)
+ m_stencilClipCommon.replacePs = buildStencilPipeline(batch, true);
+
+ if (!m_stencilClipCommon.incrPs)
+ m_stencilClipCommon.incrPs = buildStencilPipeline(batch, false);
+
+ batch->stencilClipState.updateStencilBuffer = true;
+ }
+
+ m_currentClipState.clipList = clipList;
+ m_currentClipState.type = clipType;
+ m_currentClipState.scissor = QRhiScissor(scissorRect.x(), scissorRect.y(),
+ scissorRect.width(), scissorRect.height());
+
+ applyClipStateToGraphicsState();
+ batch->clipState = m_currentClipState;
+}
+
+void Renderer::enqueueStencilDraw(const Batch *batch)
+{
+ // cliptype stencil + updateStencilBuffer==false means the batch uses
+ // stenciling but relies on the stencil data generated by a previous batch
+ // (due to the having the same clip node). Do not enqueue draw calls for
+ // stencil in this case as the stencil buffer is already up-to-date.
+ if (!batch->stencilClipState.updateStencilBuffer)
+ return;
- ++m_currentStencilValue;
+ QRhiCommandBuffer *cb = renderTarget().cb;
+ const int count = batch->stencilClipState.drawCalls.size();
+ for (int i = 0; i < count; ++i) {
+ const StencilClipState::StencilDrawCall &drawCall(batch->stencilClipState.drawCalls.at(i));
+ QRhiShaderResourceBindings *srb = batch->stencilClipState.srb;
+ QRhiCommandBuffer::DynamicOffset ubufOffset(0, drawCall.ubufOffset);
+ if (i == 0) {
+ cb->setGraphicsPipeline(m_stencilClipCommon.replacePs);
+ cb->setViewport(m_pstate.viewport);
+ } else if (i == 1) {
+ cb->setGraphicsPipeline(m_stencilClipCommon.incrPs);
+ cb->setViewport(m_pstate.viewport);
+ }
+ // else incrPs is already bound
+ cb->setShaderResources(srb, 1, &ubufOffset);
+ cb->setStencilRef(drawCall.stencilRef);
+ const QRhiCommandBuffer::VertexInput vbufBinding(batch->stencilClipState.vbuf, drawCall.vbufOffset);
+ if (drawCall.indexCount) {
+ cb->setVertexInput(0, 1, &vbufBinding,
+ batch->stencilClipState.ibuf, drawCall.ibufOffset, drawCall.indexFormat);
+ cb->drawIndexed(drawCall.indexCount);
+ } else {
+ cb->setVertexInput(0, 1, &vbufBinding);
+ cb->draw(drawCall.vertexCount);
}
+ }
+}
- clip = clip->clipList();
+void Renderer::setActiveRhiShader(QSGMaterialShader *program, ShaderManager::Shader *shader)
+{
+ Q_ASSERT(m_rhi);
+ m_currentProgram = program;
+ m_currentShader = shader;
+ m_currentMaterial = nullptr;
+}
+
+static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
+{
+ return f == QRhiGraphicsPipeline::ConstantColor
+ || f == QRhiGraphicsPipeline::OneMinusConstantColor
+ || f == QRhiGraphicsPipeline::ConstantAlpha
+ || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
+}
+
+// With QRhi renderBatches() is split to two steps: prepare and render.
+//
+// Prepare goes through the batches and elements, and set up a graphics
+// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
+// material (shaders), and the batches. This step does not touch the command
+// buffer or renderpass-related state (m_pstate).
+//
+// The render step then starts a renderpass, and goes through all
+// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
+// the command buffer. The prepare step's accumulated global state like
+// m_gstate must not be used here. Rather, all data needed for rendering is
+// available from Batch/Element at this stage. Bookkeeping of state in the
+// renderpass is done via m_pstate.
+
+bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms, bool depthPostPass)
+{
+ // Note the key's == and qHash implementations: the renderpass descriptor
+ // and srb are tested for compatibility, not pointer equality.
+ //
+ // We do not store the srb pointer itself because the ownership stays with
+ // the Element and that can go away more often that we would like it
+ // to. (think scrolling a list view, constantly dropping and creating new
+ // nodes) Rather, use an opaque blob of a few uints and store and compare
+ // that. This works because once the pipeline is built, we will always call
+ // setShaderResources with an explicitly specified srb which is fine even if
+ // e->srb we used here to bake the pipeline is already gone by that point.
+ //
+ // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
+ // + texture, 4 fields each) Regardless, using an implicitly shared
+ // container is essential here. (won't detach so no more allocs and copies
+ // are done, unless the Element decides to rebake the srb with a different
+ // layout - but then the detach is exactly what we need)
+ //
+ // Same story for the renderpass descriptor: the object can go away but
+ // that's fine because that has no effect on an already built pipeline, and
+ // for comparison we only rely on the serialized blob in order decide if the
+ // render target is compatible with the pipeline.
+
+ const GraphicsPipelineStateKey k = GraphicsPipelineStateKey::create(m_gstate, sms, renderTarget().rpDesc, e->srb);
+
+ // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
+ // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
+
+ // See if there is an existing, matching pipeline state object.
+ auto it = m_shaderManager->pipelineCache.constFind(k);
+ if (it != m_shaderManager->pipelineCache.constEnd()) {
+ if (depthPostPass)
+ e->depthPostPassPs = *it;
+ else
+ e->ps = *it;
+ return true;
}
- if (vbo)
- glDeleteBuffers(1, &vbo);
+ // Build a new one. This is potentially expensive.
+ QRhiGraphicsPipeline *ps = m_rhi->newGraphicsPipeline();
+ ps->setShaderStages(sms->stages.cbegin(), sms->stages.cend());
+ ps->setVertexInputLayout(sms->inputLayout);
+ ps->setShaderResourceBindings(e->srb);
+ ps->setRenderPassDescriptor(renderTarget().rpDesc);
- if (clipType & StencilClip) {
- m_clipProgram.disableAttributeArray(0);
- glStencilFunc(GL_EQUAL, m_currentStencilValue, 0xff); // stencil test, ref, test mask
- glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // stencil fail, z fail, z pass
- bindable()->reactivate();
- } else {
- glDisable(GL_STENCIL_TEST);
+ QRhiGraphicsPipeline::Flags flags;
+ if (needsBlendConstant(m_gstate.srcColor) || needsBlendConstant(m_gstate.dstColor)
+ || needsBlendConstant(m_gstate.srcAlpha) || needsBlendConstant(m_gstate.dstAlpha))
+ {
+ flags |= QRhiGraphicsPipeline::UsesBlendConstants;
+ }
+ if (m_gstate.usesScissor)
+ flags |= QRhiGraphicsPipeline::UsesScissor;
+ if (m_gstate.stencilTest)
+ flags |= QRhiGraphicsPipeline::UsesStencilRef;
+
+ ps->setFlags(flags);
+ ps->setTopology(qsg_topology(m_gstate.drawMode));
+ ps->setCullMode(m_gstate.cullMode);
+ ps->setPolygonMode(m_gstate.polygonMode);
+ ps->setMultiViewCount(m_gstate.multiViewCount);
+
+ QRhiGraphicsPipeline::TargetBlend blend;
+ blend.colorWrite = m_gstate.colorWrite;
+ blend.enable = m_gstate.blending;
+ blend.srcColor = m_gstate.srcColor;
+ blend.dstColor = m_gstate.dstColor;
+ blend.srcAlpha = m_gstate.srcAlpha;
+ blend.dstAlpha = m_gstate.dstAlpha;
+ blend.opColor = m_gstate.opColor;
+ blend.opAlpha = m_gstate.opAlpha;
+ ps->setTargetBlends({ blend });
+
+ ps->setDepthTest(m_gstate.depthTest);
+ ps->setDepthWrite(m_gstate.depthWrite);
+ ps->setDepthOp(m_gstate.depthFunc);
+
+ if (m_gstate.stencilTest) {
+ ps->setStencilTest(true);
+ QRhiGraphicsPipeline::StencilOpState stencilOp;
+ stencilOp.compareOp = QRhiGraphicsPipeline::Equal;
+ stencilOp.failOp = QRhiGraphicsPipeline::Keep;
+ stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
+ stencilOp.passOp = QRhiGraphicsPipeline::Keep;
+ ps->setStencilFront(stencilOp);
+ ps->setStencilBack(stencilOp);
+ }
+
+ ps->setSampleCount(m_gstate.sampleCount);
+
+ ps->setLineWidth(m_gstate.lineWidth);
+
+ if (!ps->create()) {
+ qWarning("Failed to build graphics pipeline state");
+ delete ps;
+ return false;
+ }
+
+ m_shaderManager->pipelineCache.insert(k, ps);
+ if (depthPostPass)
+ e->depthPostPassPs = ps;
+ else
+ e->ps = ps;
+ return true;
+}
+
+static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
+{
+ QRhiSampler::Filter magFilter;
+ QRhiSampler::Filter minFilter;
+ QRhiSampler::Filter mipmapMode;
+ QRhiSampler::AddressMode u;
+ QRhiSampler::AddressMode v;
+
+ switch (desc.filtering) {
+ case QSGTexture::None:
+ Q_FALLTHROUGH();
+ case QSGTexture::Nearest:
+ magFilter = minFilter = QRhiSampler::Nearest;
+ break;
+ case QSGTexture::Linear:
+ magFilter = minFilter = QRhiSampler::Linear;
+ break;
+ default:
+ Q_UNREACHABLE();
+ magFilter = minFilter = QRhiSampler::Nearest;
+ break;
+ }
+
+ switch (desc.mipmapFiltering) {
+ case QSGTexture::None:
+ mipmapMode = QRhiSampler::None;
+ break;
+ case QSGTexture::Nearest:
+ mipmapMode = QRhiSampler::Nearest;
+ break;
+ case QSGTexture::Linear:
+ mipmapMode = QRhiSampler::Linear;
+ break;
+ default:
+ Q_UNREACHABLE();
+ mipmapMode = QRhiSampler::None;
+ break;
}
- return clipType;
+ switch (desc.horizontalWrap) {
+ case QSGTexture::Repeat:
+ u = QRhiSampler::Repeat;
+ break;
+ case QSGTexture::ClampToEdge:
+ u = QRhiSampler::ClampToEdge;
+ break;
+ case QSGTexture::MirroredRepeat:
+ u = QRhiSampler::Mirror;
+ break;
+ default:
+ Q_UNREACHABLE();
+ u = QRhiSampler::ClampToEdge;
+ break;
+ }
+
+ switch (desc.verticalWrap) {
+ case QSGTexture::Repeat:
+ v = QRhiSampler::Repeat;
+ break;
+ case QSGTexture::ClampToEdge:
+ v = QRhiSampler::ClampToEdge;
+ break;
+ case QSGTexture::MirroredRepeat:
+ v = QRhiSampler::Mirror;
+ break;
+ default:
+ Q_UNREACHABLE();
+ v = QRhiSampler::ClampToEdge;
+ break;
+ }
+
+ return rhi->newSampler(magFilter, minFilter, mipmapMode, u, v);
}
-void Renderer::updateClip(const QSGClipNode *clipList, const Batch *batch)
+QRhiTexture *Renderer::dummyTexture()
{
- if (clipList != m_currentClip && Q_LIKELY(!debug_noclip())) {
- m_currentClip = clipList;
- // updateClip sets another program, so force-reactivate our own
- if (m_currentShader)
- setActiveShader(nullptr, nullptr);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- if (batch->isOpaque)
- glDisable(GL_DEPTH_TEST);
- m_currentClipType = updateStencilClip(m_currentClip);
- if (batch->isOpaque) {
- glEnable(GL_DEPTH_TEST);
- if (m_currentClipType & StencilClip)
- glDepthMask(true);
+ if (!m_dummyTexture) {
+ m_dummyTexture = m_rhi->newTexture(QRhiTexture::RGBA8, QSize(64, 64));
+ if (m_dummyTexture->create()) {
+ if (m_resourceUpdates) {
+ QImage img(m_dummyTexture->pixelSize(), QImage::Format_RGBA8888_Premultiplied);
+ img.fill(0);
+ m_resourceUpdates->uploadTexture(m_dummyTexture, img);
+ }
}
}
+ return m_dummyTexture;
}
-/*!
- * Look at the attribute arrays and potentially the injected z attribute to figure out
- * which vertex attribute arrays need to be enabled and not. Then update the current
- * Shader and current QSGMaterialShader.
- */
-void Renderer::setActiveShader(QSGMaterialShader *program, ShaderManager::Shader *shader)
+static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst,
+ GraphicsState *src)
+{
+ dst->blendEnable = src->blending;
+
+ // the enum values should match, sanity check it
+ Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
+ Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOpMax) == int(QRhiGraphicsPipeline::Max));
+ Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
+ Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
+ Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
+ dst->srcColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcColor);
+ dst->dstColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstColor);
+
+ // For compatibility with any existing code, separateBlendFactors defaults
+ // to _false_ which means that materials that do not touch srcAlpha and
+ // dstAlpha will continue to use srcColor and dstColor as the alpha
+ // blending factors. New code that needs different values for color/alpha,
+ // can explicitly set separateBlendFactors to true and then set srcAlpha
+ // and dstAlpha as well.
+ dst->separateBlendFactors = false;
+
+ dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
+ dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
+
+ dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
+ dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
+
+ dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
+
+ dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
+ dst->polygonMode = QSGMaterialShader::GraphicsPipelineState::PolygonMode(src->polygonMode);
+}
+
+static void materialToRendererGraphicsState(GraphicsState *dst,
+ QSGMaterialShader::GraphicsPipelineState *src)
+{
+ dst->blending = src->blendEnable;
+ dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
+ dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
+ if (src->separateBlendFactors) {
+ dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
+ dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
+ } else {
+ dst->srcAlpha = dst->srcColor;
+ dst->dstAlpha = dst->dstColor;
+ }
+ dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
+ dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
+ dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
+ dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
+ dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
+}
+
+void Renderer::updateMaterialDynamicData(ShaderManager::Shader *sms,
+ QSGMaterialShader::RenderState &renderState,
+ QSGMaterial *material,
+ const Batch *batch,
+ Element *e,
+ int ubufOffset,
+ int ubufRegionSize)
{
- const char * const *c = m_currentProgram ? m_currentProgram->attributeNames() : nullptr;
- const char * const *n = program ? program->attributeNames() : nullptr;
+ m_current_resource_update_batch = m_resourceUpdates;
- int cza = m_currentShader ? m_currentShader->pos_order : -1;
- int nza = shader ? shader->pos_order : -1;
+ QSGMaterialShader *shader = sms->materialShader;
+ QSGMaterialShaderPrivate *pd = QSGMaterialShaderPrivate::get(shader);
+ QVarLengthArray<QRhiShaderResourceBinding, 8> bindings;
- int i = 0;
- while (c || n) {
+ if (pd->ubufBinding >= 0) {
+ m_current_uniform_data = &pd->masterUniformData;
+ const bool changed = shader->updateUniformData(renderState, material, m_currentMaterial);
+ m_current_uniform_data = nullptr;
- bool was = c;
- if (cza == i) {
- was = true;
- c = nullptr;
- } else if (c && !c[i]) { // end of the attribute array names
- c = nullptr;
- was = false;
+ if (changed || !batch->ubufDataValid)
+ m_resourceUpdates->updateDynamicBuffer(batch->ubuf, ubufOffset, ubufRegionSize, pd->masterUniformData.constData());
+
+ bindings.append(QRhiShaderResourceBinding::uniformBuffer(pd->ubufBinding,
+ pd->ubufStages,
+ batch->ubuf,
+ ubufOffset,
+ ubufRegionSize));
+ }
+
+ for (int binding = 0; binding < QSGMaterialShaderPrivate::MAX_SHADER_RESOURCE_BINDINGS; ++binding) {
+ const QRhiShaderResourceBinding::StageFlags stages = pd->combinedImageSamplerBindings[binding];
+ if (!stages)
+ continue;
+
+ QVarLengthArray<QSGTexture *, 4> prevTex = pd->textureBindingTable[binding];
+ QVarLengthArray<QSGTexture *, 4> nextTex = prevTex;
+
+ const int count = pd->combinedImageSamplerCount[binding];
+ nextTex.resize(count);
+
+ shader->updateSampledImage(renderState, binding, nextTex.data(), material,
+ m_currentMaterial);
+
+ if (nextTex.contains(nullptr)) {
+ qWarning("No QSGTexture provided from updateSampledImage(). This is wrong.");
+ continue;
}
- bool is = n;
- if (nza == i) {
- is = true;
- n = nullptr;
- } else if (n && !n[i]) {
- n = nullptr;
- is = false;
+ bool hasDirtySamplerOptions = false;
+ bool isAnisotropic = false;
+ for (QSGTexture *t : nextTex) {
+ QSGTexturePrivate *td = QSGTexturePrivate::get(t);
+ hasDirtySamplerOptions |= td->hasDirtySamplerOptions();
+ isAnisotropic |= t->anisotropyLevel() != QSGTexture::AnisotropyNone;
+ td->resetDirtySamplerOptions();
}
- if (is && !was)
- glEnableVertexAttribArray(i);
- else if (was && !is)
- glDisableVertexAttribArray(i);
+ // prevTex may be invalid at this point, avoid dereferencing it
+ if (nextTex != prevTex || hasDirtySamplerOptions) {
+
+ // The QSGTexture, and so the sampler parameters, may have changed.
+ // The rhiTexture is not relevant here.
+ pd->textureBindingTable[binding] = nextTex; // does not own
+ pd->samplerBindingTable[binding].clear();
+
+ if (isAnisotropic) // ###
+ qWarning("QSGTexture anisotropy levels are not currently supported");
- ++i;
+ QVarLengthArray<QRhiSampler *, 4> samplers;
+
+ for (QSGTexture *t : nextTex) {
+ const QSGSamplerDescription samplerDesc = QSGSamplerDescription::fromTexture(t);
+
+ QRhiSampler *sampler = m_samplers[samplerDesc];
+
+ if (!sampler) {
+ sampler = newSampler(m_rhi, samplerDesc);
+ if (!sampler->create()) {
+ qWarning("Failed to build sampler");
+ delete sampler;
+ continue;
+ }
+ m_samplers[samplerDesc] = sampler;
+ }
+ samplers.append(sampler);
+ }
+
+ pd->samplerBindingTable[binding] = samplers; // does not own
+ }
+
+ if (pd->textureBindingTable[binding].size() == pd->samplerBindingTable[binding].size()) {
+
+ QVarLengthArray<QRhiShaderResourceBinding::TextureAndSampler, 4> textureSamplers;
+
+ for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
+
+ QRhiTexture *texture = pd->textureBindingTable[binding].at(i)->rhiTexture();
+
+ // texture may be null if the update above failed for any reason,
+ // or if the QSGTexture chose to return null intentionally. This is
+ // valid and we still need to provide something to the shader.
+ if (!texture)
+ texture = dummyTexture();
+
+ QRhiSampler *sampler = pd->samplerBindingTable[binding].at(i);
+
+ textureSamplers.append(
+ QRhiShaderResourceBinding::TextureAndSampler { texture, sampler });
+ }
+
+ if (!textureSamplers.isEmpty())
+ bindings.append(QRhiShaderResourceBinding::sampledTextures(
+ binding, stages, count, textureSamplers.constData()));
+ }
}
- if (m_currentProgram)
- m_currentProgram->deactivate();
- m_currentProgram = program;
- m_currentShader = shader;
- m_currentMaterial = nullptr;
- if (m_currentProgram) {
- m_currentProgram->program()->bind();
- m_currentProgram->activate();
+#ifndef QT_NO_DEBUG
+ if (bindings.isEmpty())
+ qWarning("No shader resources for material %p, this is odd.", material);
+#endif
+
+ enum class SrbAction {
+ Unknown,
+ DoNothing,
+ UpdateResources,
+ Rebake
+ } srbAction = SrbAction::Unknown;
+
+ // First, if the Element has no srb created at all, then try to find an existing,
+ // currently unused srb that is layout-compatible with our binding list.
+ if (!e->srb) {
+ // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
+ QVector<quint32> &layoutDesc(m_shaderManager->srbLayoutDescSerializeWorkspace);
+ layoutDesc.clear();
+ QRhiShaderResourceBinding::serializeLayoutDescription(bindings.cbegin(), bindings.cend(), std::back_inserter(layoutDesc));
+ e->srb = m_shaderManager->srbPool.take(layoutDesc);
+ if (e->srb) {
+ // Here we know layout compatibility is satisfied, but do not spend time on full
+ // comparison. The chance of getting an srb that refers to the same resources
+ // (buffer, textures) is low in practice. So reuse, but write new resources.
+ srbAction = SrbAction::UpdateResources;
+ }
+ }
+
+ // If the Element had an existing srb, investigate:
+ // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
+ // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
+ // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
+ if (srbAction == SrbAction::Unknown && e->srb) {
+ if (std::equal(e->srb->cbeginBindings(), e->srb->cendBindings(), bindings.cbegin(), bindings.cend())) {
+ srbAction = SrbAction::DoNothing;
+ } else if (std::equal(e->srb->cbeginBindings(), e->srb->cendBindings(), bindings.cbegin(), bindings.cend(),
+ [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
+ {
+ srbAction = SrbAction::UpdateResources;
+ } else {
+ srbAction = SrbAction::Rebake;
+ }
+ }
+
+ // If the Element had no srb associated at all and could not find a layout-compatible
+ // one from the pool, then create a whole new object.
+ if (!e->srb) {
+ e->srb = m_rhi->newShaderResourceBindings();
+ srbAction = SrbAction::Rebake;
+ }
+
+ Q_ASSERT(srbAction != SrbAction::Unknown && e->srb);
+
+ switch (srbAction) {
+ case SrbAction::DoNothing:
+ break;
+ case SrbAction::UpdateResources:
+ {
+ e->srb->setBindings(bindings.cbegin(), bindings.cend());
+ QRhiShaderResourceBindings::UpdateFlags flags;
+ // Due to the way the binding list is built up above, if we have a uniform buffer
+ // at binding point 0 (or none at all) then the sampledTexture bindings are added
+ // with increasing binding points afterwards, so the list is already sorted based
+ // on the binding points, thus we can save some time by telling the QRhi backend
+ // not to sort again.
+ if (pd->ubufBinding <= 0 || bindings.size() <= 1)
+ flags |= QRhiShaderResourceBindings::BindingsAreSorted;
+
+ e->srb->updateResources(flags);
+ }
+ break;
+ case SrbAction::Rebake:
+ e->srb->setBindings(bindings.cbegin(), bindings.cend());
+ if (!e->srb->create())
+ qWarning("Failed to build srb");
+ break;
+ default:
+ Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
+ }
+}
+
+void Renderer::updateMaterialStaticData(ShaderManager::Shader *sms,
+ QSGMaterialShader::RenderState &renderState,
+ QSGMaterial *material,
+ Batch *batch,
+ bool *gstateChanged)
+{
+ QSGMaterialShader *shader = sms->materialShader;
+ *gstateChanged = false;
+ if (shader->flags().testFlag(QSGMaterialShader::UpdatesGraphicsPipelineState)) {
+ // generate the public mini-state from m_gstate, invoke the material,
+ // write the changes, if any, back to m_gstate, together with a way to
+ // roll those back.
+ QSGMaterialShader::GraphicsPipelineState shaderPs;
+ rendererToMaterialGraphicsState(&shaderPs, &m_gstate);
+ const bool changed = shader->updateGraphicsPipelineState(renderState, &shaderPs, material, m_currentMaterial);
+ if (changed) {
+ m_gstateStack.push(m_gstate);
+ materialToRendererGraphicsState(&m_gstate, &shaderPs);
+ if (needsBlendConstant(m_gstate.srcColor) || needsBlendConstant(m_gstate.dstColor)
+ || needsBlendConstant(m_gstate.srcAlpha) || needsBlendConstant(m_gstate.dstAlpha))
+ {
+ batch->blendConstant = shaderPs.blendConstant;
+ }
+ *gstateChanged = true;
+ }
}
}
-void Renderer::renderMergedBatch(const Batch *batch)
+bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *renderBatch)
{
if (batch->vertexCount == 0 || batch->indexCount == 0)
- return;
+ return false;
Element *e = batch->first;
Q_ASSERT(e);
@@ -2271,31 +3153,30 @@ void Renderer::renderMergedBatch(const Batch *batch)
else
m_current_model_view_matrix.setToIdentity();
m_current_determinant = m_current_model_view_matrix.determinant();
- m_current_projection_matrix = projectionMatrix(); // has potentially been changed by renderUnmergedBatch..
- // updateClip() uses m_current_projection_matrix.
- updateClip(gn->clipList(), batch);
-
- glBindBuffer(GL_ARRAY_BUFFER, batch->vbo.id);
-
- char *indexBase = nullptr;
- const Buffer *indexBuf = m_context->separateIndexBuffer() ? &batch->ibo : &batch->vbo;
- if (m_context->hasBrokenIndexBufferObjects()) {
- indexBase = indexBuf->data;
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- } else {
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf->id);
- }
+ const int viewCount = projectionMatrixCount();
+ m_current_projection_matrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
+ m_current_projection_matrix_native_ndc.resize(projectionMatrixWithNativeNDCCount());
+ for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
+ m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(viewIndex);
QSGMaterial *material = gn->activeMaterial();
- ShaderManager::Shader *sms = m_useDepthBuffer ? m_shaderManager->prepareMaterial(material) : m_shaderManager->prepareMaterialNoRewrite(material);
+ if (m_renderMode != QSGRendererInterface::RenderMode3D)
+ updateClipState(gn->clipList(), batch);
+
+ const QSGGeometry *g = gn->geometry();
+ const int multiViewCount = renderTarget().multiViewCount;
+ ShaderManager::Shader *sms = useDepthBuffer() ? m_shaderManager->prepareMaterial(material, g, m_renderMode, multiViewCount)
+ : m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode, multiViewCount);
if (!sms)
- return;
- QSGMaterialShader *program = sms->program;
+ return false;
+ Q_ASSERT(sms->materialShader);
if (m_currentShader != sms)
- setActiveShader(program, sms);
+ setActiveRhiShader(sms->materialShader, sms);
m_current_opacity = gn->inheritedOpacity();
if (!qFuzzyCompare(sms->lastOpacity, float(m_current_opacity))) {
@@ -2303,7 +3184,36 @@ void Renderer::renderMergedBatch(const Batch *batch)
sms->lastOpacity = m_current_opacity;
}
- program->updateState(state(dirty), material, m_currentMaterial);
+ QSGMaterialShaderPrivate *pd = QSGMaterialShaderPrivate::get(sms->materialShader);
+ const quint32 ubufSize = quint32(pd->masterUniformData.size());
+ if (pd->ubufBinding >= 0) {
+ bool ubufRebuild = false;
+ if (!batch->ubuf) {
+ batch->ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
+ ubufRebuild = true;
+ } else {
+ if (batch->ubuf->size() < ubufSize) {
+ batch->ubuf->setSize(ubufSize);
+ ubufRebuild = true;
+ }
+ }
+ if (ubufRebuild) {
+ batch->ubufDataValid = false;
+ if (!batch->ubuf->create()) {
+ qWarning("Failed to build uniform buffer of size %u bytes", ubufSize);
+ delete batch->ubuf;
+ batch->ubuf = nullptr;
+ return false;
+ }
+ }
+ }
+
+ QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
+
+ bool pendingGStatePop = false;
+ updateMaterialStaticData(sms, renderState, material, batch, &pendingGStatePop);
+
+ updateMaterialDynamicData(sms, renderState, material, batch, e, 0, ubufSize);
#ifndef QT_NO_DEBUG
if (qsg_test_and_clear_material_failure()) {
@@ -2318,33 +3228,93 @@ void Renderer::renderMergedBatch(const Batch *batch)
}
#endif
+ m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
+ m_gstate.lineWidth = g->lineWidth();
+
+ const bool hasPipeline = ensurePipelineState(e, sms);
+
+ if (pendingGStatePop)
+ m_gstate = m_gstateStack.pop();
+
+ if (!hasPipeline)
+ return false;
+
+ if (m_renderMode == QSGRendererInterface::RenderMode3D) {
+ m_gstateStack.push(m_gstate);
+ setStateForDepthPostPass();
+ ensurePipelineState(e, sms, true);
+ m_gstate = m_gstateStack.pop();
+ }
+
+ batch->ubufDataValid = true;
+
m_currentMaterial = material;
- QSGGeometry* g = gn->geometry();
- updateLineWidth(g);
- char const *const *attrNames = program->attributeNames();
- for (int i=0; i<batch->drawSets.size(); ++i) {
- const DrawSet &draw = batch->drawSets.at(i);
- int offset = 0;
- for (int j = 0; attrNames[j]; ++j) {
- if (!*attrNames[j])
- continue;
- const QSGGeometry::Attribute &a = g->attributes()[j];
- GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
- glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (void *) (qintptr) (offset + draw.vertices));
- offset += a.tupleSize * size_of_type(a.type);
+ renderBatch->batch = batch;
+ renderBatch->sms = sms;
+
+ return true;
+}
+
+void Renderer::checkLineWidth(QSGGeometry *g)
+{
+ if (g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawLineLoop
+ || g->drawingMode() == QSGGeometry::DrawLineStrip)
+ {
+ if (g->lineWidth() != 1.0f) {
+ static bool checkedWideLineSupport = false;
+ if (!checkedWideLineSupport) {
+ checkedWideLineSupport = true;
+ if (!m_rhi->isFeatureSupported(QRhi::WideLines))
+ qWarning("Line widths other than 1 are not supported by the graphics API");
+ }
}
- if (m_useDepthBuffer)
- glVertexAttribPointer(sms->pos_order, 1, GL_FLOAT, false, 0, (void *) (qintptr) (draw.zorders));
+ } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
+ if (g->lineWidth() != 1.0f) {
+ static bool warnedPointSize = false;
+ if (!warnedPointSize) {
+ warnedPointSize = true;
+ qWarning("Point size is not controllable by QSGGeometry. "
+ "Set gl_PointSize from the vertex shader instead.");
+ }
+ }
+ }
+}
+
+void Renderer::renderMergedBatch(PreparedRenderBatch *renderBatch, bool depthPostPass)
+{
+ const Batch *batch = renderBatch->batch;
+ if (!batch->vbo.buf || !batch->ibo.buf)
+ return;
- glDrawElements(g->drawingMode(), draw.indexCount, GL_UNSIGNED_SHORT, (void *) (qintptr) (indexBase + draw.indices));
+ Element *e = batch->first;
+ QSGGeometryNode *gn = e->node;
+ QSGGeometry *g = gn->geometry();
+ checkLineWidth(g);
+
+ if (batch->clipState.type & ClipState::StencilClip)
+ enqueueStencilDraw(batch);
+
+ QRhiCommandBuffer *cb = renderTarget().cb;
+ setGraphicsPipeline(cb, batch, e, depthPostPass);
+
+ for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
+ const DrawSet &draw = batch->drawSets.at(i);
+ const QRhiCommandBuffer::VertexInput vbufBindings[] = {
+ { batch->vbo.buf, quint32(draw.vertices) },
+ { batch->vbo.buf, quint32(draw.zorders) }
+ };
+ cb->setVertexInput(VERTEX_BUFFER_BINDING, useDepthBuffer() ? 2 : 1, vbufBindings,
+ batch->ibo.buf, draw.indices,
+ m_uint32IndexForRhi ? QRhiCommandBuffer::IndexUInt32 : QRhiCommandBuffer::IndexUInt16);
+ cb->drawIndexed(draw.indexCount);
}
}
-void Renderer::renderUnmergedBatch(const Batch *batch)
+bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *renderBatch)
{
if (batch->vertexCount == 0)
- return;
+ return false;
Element *e = batch->first;
Q_ASSERT(e);
@@ -2364,35 +3334,33 @@ void Renderer::renderUnmergedBatch(const Batch *batch)
batch->uploadedThisFrame = false;
}
- QSGGeometryNode *gn = e->node;
+ const int viewCount = projectionMatrixCount();
+ m_current_projection_matrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
- m_current_projection_matrix = projectionMatrix();
- updateClip(gn->clipList(), batch);
-
- glBindBuffer(GL_ARRAY_BUFFER, batch->vbo.id);
- char *indexBase = nullptr;
- const auto separateIndexBuffer = m_context->separateIndexBuffer();
- const Buffer *indexBuf = separateIndexBuffer ? &batch->ibo : &batch->vbo;
- if (batch->indexCount) {
- if (m_context->hasBrokenIndexBufferObjects()) {
- indexBase = indexBuf->data;
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- } else {
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf->id);
- }
- }
+ m_current_projection_matrix_native_ndc.resize(projectionMatrixWithNativeNDCCount());
+ for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
+ m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(viewIndex);
+
+ QSGGeometryNode *gn = e->node;
+ if (m_renderMode != QSGRendererInterface::RenderMode3D)
+ updateClipState(gn->clipList(), batch);
// We always have dirty matrix as all batches are at a unique z range.
QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
+ // The vertex attributes are assumed to be the same for all elements in the
+ // unmerged batch since the material (and so the shaders) is the same.
+ QSGGeometry *g = gn->geometry();
QSGMaterial *material = gn->activeMaterial();
- ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material);
+ ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode, renderTarget().multiViewCount);
if (!sms)
- return;
- QSGMaterialShader *program = sms->program;
+ return false;
- if (sms != m_currentShader)
- setActiveShader(program, sms);
+ Q_ASSERT(sms->materialShader);
+ if (m_currentShader != sms)
+ setActiveRhiShader(sms->materialShader, sms);
m_current_opacity = gn->inheritedOpacity();
if (sms->lastOpacity != m_current_opacity) {
@@ -2400,164 +3368,225 @@ void Renderer::renderUnmergedBatch(const Batch *batch)
sms->lastOpacity = m_current_opacity;
}
- int vOffset = 0;
- char *iOffset = indexBase;
- if (!separateIndexBuffer)
- iOffset += batch->vertexCount * gn->geometry()->sizeOfVertex();
-
QMatrix4x4 rootMatrix = batch->root ? qsg_matrixForRoot(batch->root) : QMatrix4x4();
+ QSGMaterialShaderPrivate *pd = QSGMaterialShaderPrivate::get(sms->materialShader);
+ const quint32 ubufSize = quint32(pd->masterUniformData.size());
+ if (pd->ubufBinding >= 0) {
+ quint32 totalUBufSize = 0;
+ while (e) {
+ totalUBufSize += aligned(ubufSize, m_ubufAlignment);
+ e = e->nextInBatch;
+ }
+ bool ubufRebuild = false;
+ if (!batch->ubuf) {
+ batch->ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalUBufSize);
+ ubufRebuild = true;
+ } else {
+ if (batch->ubuf->size() < totalUBufSize) {
+ batch->ubuf->setSize(totalUBufSize);
+ ubufRebuild = true;
+ }
+ }
+ if (ubufRebuild) {
+ batch->ubufDataValid = false;
+ if (!batch->ubuf->create()) {
+ qWarning("Failed to build uniform buffer of size %u bytes", totalUBufSize);
+ delete batch->ubuf;
+ batch->ubuf = nullptr;
+ return false;
+ }
+ }
+ }
+
+ QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
+ bool pendingGStatePop = false;
+ updateMaterialStaticData(sms, renderState,
+ material, batch, &pendingGStatePop);
+
+ int ubufOffset = 0;
+ QRhiGraphicsPipeline *ps = nullptr;
+ QRhiGraphicsPipeline *depthPostPassPs = nullptr;
+ e = batch->first;
while (e) {
gn = e->node;
m_current_model_view_matrix = rootMatrix * *gn->matrix();
m_current_determinant = m_current_model_view_matrix.determinant();
- m_current_projection_matrix = projectionMatrix();
- if (m_useDepthBuffer) {
- m_current_projection_matrix(2, 2) = m_zRange;
- m_current_projection_matrix(2, 3) = 1.0f - e->order * m_zRange;
+ const int viewCount = projectionMatrixCount();
+ m_current_projection_matrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
+
+ m_current_projection_matrix_native_ndc.resize(projectionMatrixWithNativeNDCCount());
+ for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
+ m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(viewIndex);
+
+ if (useDepthBuffer()) {
+ // this cannot be multiview
+ m_current_projection_matrix[0](2, 2) = m_zRange;
+ m_current_projection_matrix[0](2, 3) = calculateElementZOrder(e, m_zRange);
}
- program->updateState(state(dirty), material, m_currentMaterial);
+ QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
+ updateMaterialDynamicData(sms, renderState, material, batch, e, ubufOffset, ubufSize);
#ifndef QT_NO_DEBUG
- if (qsg_test_and_clear_material_failure()) {
- qDebug("QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:");
- qDebug() << " - offending node is" << e->node;
- QSGNodeDumper::dump(rootNode());
- qFatal("Aborting: scene graph is invalid...");
- return;
- }
+ if (qsg_test_and_clear_material_failure()) {
+ qDebug("QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:");
+ qDebug() << " - offending node is" << e->node;
+ QSGNodeDumper::dump(rootNode());
+ qFatal("Aborting: scene graph is invalid...");
+ return false;
+ }
#endif
+ ubufOffset += aligned(ubufSize, m_ubufAlignment);
+
+ const QSGGeometry::DrawingMode prevDrawMode = m_gstate.drawMode;
+ const float prevLineWidth = m_gstate.lineWidth;
+ m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
+ m_gstate.lineWidth = g->lineWidth();
+
+ // Do not bother even looking up the ps if the topology has not changed
+ // since everything else is the same for all elements in the batch.
+ // (except if the material modified blend state)
+ if (!ps || m_gstate.drawMode != prevDrawMode || m_gstate.lineWidth != prevLineWidth || pendingGStatePop) {
+ if (!ensurePipelineState(e, sms)) {
+ if (pendingGStatePop)
+ m_gstate = m_gstateStack.pop();
+ return false;
+ }
+ ps = e->ps;
+ if (m_renderMode == QSGRendererInterface::RenderMode3D) {
+ m_gstateStack.push(m_gstate);
+ setStateForDepthPostPass();
+ ensurePipelineState(e, sms, true);
+ m_gstate = m_gstateStack.pop();
+ depthPostPassPs = e->depthPostPassPs;
+ }
+ } else {
+ e->ps = ps;
+ if (m_renderMode == QSGRendererInterface::RenderMode3D)
+ e->depthPostPassPs = depthPostPassPs;
+ }
+
// We don't need to bother with asking each node for its material as they
// are all identical (compare==0) since they are in the same batch.
m_currentMaterial = material;
- QSGGeometry* g = gn->geometry();
- char const *const *attrNames = program->attributeNames();
- int offset = 0;
- for (int j = 0; attrNames[j]; ++j) {
- if (!*attrNames[j])
- continue;
- const QSGGeometry::Attribute &a = g->attributes()[j];
- GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
- glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (void *) (qintptr) (offset + vOffset));
- offset += a.tupleSize * size_of_type(a.type);
- }
-
- updateLineWidth(g);
- if (g->indexCount())
- glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), iOffset);
- else
- glDrawArrays(g->drawingMode(), 0, g->vertexCount());
-
- vOffset += g->sizeOfVertex() * g->vertexCount();
- iOffset += g->indexCount() * g->sizeOfIndex();
-
// We only need to push this on the very first iteration...
dirty &= ~QSGMaterialShader::RenderState::DirtyOpacity;
e = e->nextInBatch;
}
-}
-void Renderer::updateLineWidth(QSGGeometry *g)
-{
- if (g->drawingMode() == GL_LINE_STRIP || g->drawingMode() == GL_LINE_LOOP || g->drawingMode() == GL_LINES)
- glLineWidth(g->lineWidth());
-#if !defined(QT_OPENGL_ES_2)
- else {
- QOpenGLContext *ctx = m_context->openglContext();
- if (!ctx->isOpenGLES() && g->drawingMode() == GL_POINTS) {
- QOpenGLFunctions_1_0 *gl1funcs = nullptr;
- QOpenGLFunctions_3_2_Core *gl3funcs = nullptr;
- if (ctx->format().profile() == QSurfaceFormat::CoreProfile)
- gl3funcs = ctx->versionFunctions<QOpenGLFunctions_3_2_Core>();
- else
- gl1funcs = ctx->versionFunctions<QOpenGLFunctions_1_0>();
- Q_ASSERT(gl1funcs || gl3funcs);
- if (gl1funcs)
- gl1funcs->glPointSize(g->lineWidth());
- else
- gl3funcs->glPointSize(g->lineWidth());
- }
- }
-#endif
+ if (pendingGStatePop)
+ m_gstate = m_gstateStack.pop();
+
+ batch->ubufDataValid = true;
+
+ renderBatch->batch = batch;
+ renderBatch->sms = sms;
+
+ return true;
}
-void Renderer::renderBatches()
+void Renderer::renderUnmergedBatch(PreparedRenderBatch *renderBatch, bool depthPostPass)
{
- if (Q_UNLIKELY(debug_render())) {
- qDebug().nospace() << "Rendering:" << endl
- << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << endl
- << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
- }
+ const Batch *batch = renderBatch->batch;
+ if (!batch->vbo.buf)
+ return;
- QRect r = viewportRect();
- glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height());
- glClearColor(clearColor().redF(), clearColor().greenF(), clearColor().blueF(), clearColor().alphaF());
+ Element *e = batch->first;
- if (m_useDepthBuffer) {
- glClearDepthf(1); // calls glClearDepth() under the hood for desktop OpenGL
- glEnable(GL_DEPTH_TEST);
- glDepthFunc(GL_LESS);
- glDepthMask(true);
- glDisable(GL_BLEND);
- } else {
- glDisable(GL_DEPTH_TEST);
- glDepthMask(false);
- }
- glDisable(GL_CULL_FACE);
- glColorMask(true, true, true, true);
- glDisable(GL_SCISSOR_TEST);
- glDisable(GL_STENCIL_TEST);
+ if (batch->clipState.type & ClipState::StencilClip)
+ enqueueStencilDraw(batch);
- bindable()->clear(clearMode());
+ quint32 vOffset = 0;
+ quint32 iOffset = 0;
+ QRhiCommandBuffer *cb = renderTarget().cb;
- m_current_opacity = 1;
- m_currentMaterial = nullptr;
- m_currentShader = nullptr;
- m_currentProgram = nullptr;
- m_currentClip = nullptr;
+ while (e) {
+ QSGGeometry *g = e->node->geometry();
+ checkLineWidth(g);
+ const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
+
+ setGraphicsPipeline(cb, batch, e, depthPostPass);
+
+ const QRhiCommandBuffer::VertexInput vbufBinding(batch->vbo.buf, vOffset);
+ if (g->indexCount()) {
+ if (batch->ibo.buf) {
+ cb->setVertexInput(VERTEX_BUFFER_BINDING, 1, &vbufBinding,
+ batch->ibo.buf, iOffset,
+ effectiveIndexSize == sizeof(quint32) ? QRhiCommandBuffer::IndexUInt32
+ : QRhiCommandBuffer::IndexUInt16);
+ cb->drawIndexed(g->indexCount());
+ }
+ } else {
+ cb->setVertexInput(VERTEX_BUFFER_BINDING, 1, &vbufBinding);
+ cb->draw(g->vertexCount());
+ }
- bool renderOpaque = !debug_noopaque();
- bool renderAlpha = !debug_noalpha();
+ vOffset += g->sizeOfVertex() * g->vertexCount();
+ iOffset += g->indexCount() * effectiveIndexSize;
- if (Q_LIKELY(renderOpaque)) {
- for (int i=0; i<m_opaqueBatches.size(); ++i) {
- Batch *b = m_opaqueBatches.at(i);
- if (b->merged)
- renderMergedBatch(b);
- else
- renderUnmergedBatch(b);
- }
+ e = e->nextInBatch;
}
+}
- glEnable(GL_BLEND);
- if (m_useDepthBuffer)
- glDepthMask(false);
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+void Renderer::setGraphicsPipeline(QRhiCommandBuffer *cb, const Batch *batch, Element *e, bool depthPostPass)
+{
+ cb->setGraphicsPipeline(depthPostPass ? e->depthPostPassPs : e->ps);
- if (Q_LIKELY(renderAlpha)) {
- for (int i=0; i<m_alphaBatches.size(); ++i) {
- Batch *b = m_alphaBatches.at(i);
- if (b->merged)
- renderMergedBatch(b);
- else if (b->isRenderNode)
- renderRenderNode(b);
- else
- renderUnmergedBatch(b);
+ if (!m_pstate.viewportSet) {
+ m_pstate.viewportSet = true;
+ cb->setViewport(m_pstate.viewport);
+ }
+ if (batch->clipState.type & ClipState::ScissorClip) {
+ Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
+ m_pstate.scissorSet = true;
+ cb->setScissor(batch->clipState.scissor);
+ } else {
+ Q_ASSERT(!e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
+ // Regardless of the ps not using scissor, the scissor may need to be
+ // reset, depending on the backend. So set the viewport again, which in
+ // turn also sets the scissor on backends where a scissor rect is
+ // always-on (Vulkan).
+ if (m_pstate.scissorSet) {
+ m_pstate.scissorSet = false;
+ cb->setViewport(m_pstate.viewport);
}
}
+ if (batch->clipState.type & ClipState::StencilClip) {
+ Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesStencilRef));
+ cb->setStencilRef(batch->clipState.stencilRef);
+ }
+ if (!depthPostPass && e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesBlendConstants))
+ cb->setBlendConstants(batch->blendConstant);
- if (m_currentShader)
- setActiveShader(nullptr, nullptr);
- updateStencilClip(nullptr);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- glDepthMask(true);
+ cb->setShaderResources(e->srb);
+}
+
+void Renderer::releaseElement(Element *e, bool inDestructor)
+{
+ if (e->isRenderNode) {
+ delete static_cast<RenderNodeElement *>(e);
+ } else {
+ if (e->srb) {
+ if (!inDestructor) {
+ if (m_shaderManager->srbPool.size() < m_srbPoolThreshold)
+ m_shaderManager->srbPool.insert(e->srb->serializedLayoutDescription(), e->srb);
+ else
+ delete e->srb;
+ } else {
+ delete e->srb;
+ }
+ e->srb = nullptr;
+ }
+ m_elementAllocator.release(e);
+ }
}
void Renderer::deleteRemovedElements()
@@ -2576,32 +3605,59 @@ void Renderer::deleteRemovedElements()
*e = nullptr;
}
- for (int i=0; i<m_elementsToDelete.size(); ++i) {
- Element *e = m_elementsToDelete.at(i);
- if (e->isRenderNode)
- delete static_cast<RenderNodeElement *>(e);
- else
- m_elementAllocator.release(e);
- }
+ for (int i=0; i<m_elementsToDelete.size(); ++i)
+ releaseElement(m_elementsToDelete.at(i));
+
m_elementsToDelete.reset();
}
void Renderer::render()
{
- Q_ASSERT(m_context->openglContext() == QOpenGLContext::currentContext());
+ // Gracefully handle the lack of a render target - some autotests may rely
+ // on this in odd cases.
+ if (!renderTarget().rt)
+ return;
+
+ prepareRenderPass(&m_mainRenderPassContext);
+ beginRenderPass(&m_mainRenderPassContext);
+ recordRenderPass(&m_mainRenderPassContext);
+ endRenderPass(&m_mainRenderPassContext);
+}
+
+// An alternative to render() is to call prepareInline() and renderInline() at
+// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
+// respectively) These allow rendering within a render pass that is started by
+// another component. In contrast, render() records a full render pass on its
+// own.
+
+void Renderer::prepareInline()
+{
+ prepareRenderPass(&m_mainRenderPassContext);
+}
+
+void Renderer::renderInline()
+{
+ recordRenderPass(&m_mainRenderPassContext);
+}
+
+void Renderer::prepareRenderPass(RenderPassContext *ctx)
+{
+ if (ctx->valid)
+ qWarning("prepareRenderPass() called with an already prepared render pass context");
+
+ ctx->valid = true;
if (Q_UNLIKELY(debug_dump())) {
qDebug("\n");
QSGNodeDumper::dump(rootNode());
}
- QElapsedTimer timer;
- quint64 timeRenderLists = 0;
- quint64 timePrepareOpaque = 0;
- quint64 timePrepareAlpha = 0;
- quint64 timeSorting = 0;
- quint64 timeUploadOpaque = 0;
- quint64 timeUploadAlpha = 0;
+ ctx->timeRenderLists = 0;
+ ctx->timePrepareOpaque = 0;
+ ctx->timePrepareAlpha = 0;
+ ctx->timeSorting = 0;
+ ctx->timeUploadOpaque = 0;
+ ctx->timeUploadAlpha = 0;
if (Q_UNLIKELY(debug_render() || debug_build())) {
QByteArray type("rebuild:");
@@ -2619,11 +3675,10 @@ void Renderer::render()
}
qDebug() << "Renderer::render()" << this << type;
- timer.start();
+ ctx->timer.start();
}
- if (m_vao)
- m_vao->bind();
+ m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
if (m_rebuild & (BuildRenderLists | BuildRenderListsForTaggedRoots)) {
bool complete = (m_rebuild & BuildRenderLists) != 0;
@@ -2646,7 +3701,7 @@ void Renderer::render()
}
}
}
- if (Q_UNLIKELY(debug_render())) timeRenderLists = timer.restart();
+ if (Q_UNLIKELY(debug_render())) ctx->timeRenderLists = ctx->timer.restart();
for (int i=0; i<m_opaqueBatches.size(); ++i)
m_opaqueBatches.at(i)->cleanupRemovedElements();
@@ -2659,9 +3714,9 @@ void Renderer::render()
if (m_rebuild & BuildBatches) {
prepareOpaqueBatches();
- if (Q_UNLIKELY(debug_render())) timePrepareOpaque = timer.restart();
+ if (Q_UNLIKELY(debug_render())) ctx->timePrepareOpaque = ctx->timer.restart();
prepareAlphaBatches();
- if (Q_UNLIKELY(debug_render())) timePrepareAlpha = timer.restart();
+ if (Q_UNLIKELY(debug_render())) ctx->timePrepareAlpha = ctx->timer.restart();
if (Q_UNLIKELY(debug_build())) {
qDebug("Opaque Batches:");
@@ -2682,7 +3737,7 @@ void Renderer::render()
}
}
} else {
- if (Q_UNLIKELY(debug_render())) timePrepareOpaque = timePrepareAlpha = timer.restart();
+ if (Q_UNLIKELY(debug_render())) ctx->timePrepareOpaque = ctx->timePrepareAlpha = ctx->timer.restart();
}
@@ -2703,55 +3758,232 @@ void Renderer::render()
: 0;
}
- if (Q_UNLIKELY(debug_render())) timeSorting = timer.restart();
+ if (Q_UNLIKELY(debug_render())) ctx->timeSorting = ctx->timer.restart();
- int largestVBO = 0;
- int largestIBO = 0;
+ // Set size to 0, nothing is deallocated, they will "grow" again
+ // as part of uploadBatch.
+ m_vertexUploadPool.reset();
+ m_indexUploadPool.reset();
if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
for (int i=0; i<m_opaqueBatches.size(); ++i) {
Batch *b = m_opaqueBatches.at(i);
- largestVBO = qMax(b->vbo.size, largestVBO);
- largestIBO = qMax(b->ibo.size, largestIBO);
uploadBatch(b);
}
- if (Q_UNLIKELY(debug_render())) timeUploadOpaque = timer.restart();
-
+ if (Q_UNLIKELY(debug_render())) ctx->timeUploadOpaque = ctx->timer.restart();
if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Alpha Batches:");
for (int i=0; i<m_alphaBatches.size(); ++i) {
Batch *b = m_alphaBatches.at(i);
uploadBatch(b);
- largestVBO = qMax(b->vbo.size, largestVBO);
- largestIBO = qMax(b->ibo.size, largestIBO);
}
- if (Q_UNLIKELY(debug_render())) timeUploadAlpha = timer.restart();
+ if (Q_UNLIKELY(debug_render())) ctx->timeUploadAlpha = ctx->timer.restart();
+
+ if (Q_UNLIKELY(debug_render())) {
+ qDebug().nospace() << "Rendering:" << Qt::endl
+ << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
+ << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
+ }
+
+ m_current_opacity = 1;
+ m_currentMaterial = nullptr;
+ m_currentShader = nullptr;
+ m_currentProgram = nullptr;
+ m_currentClipState.reset();
- if (largestVBO * 2 < m_vertexUploadPool.size())
- m_vertexUploadPool.resize(largestVBO * 2);
- if (m_context->separateIndexBuffer() && largestIBO * 2 < m_indexUploadPool.size())
- m_indexUploadPool.resize(largestIBO * 2);
+ const QRect viewport = viewportRect();
- renderBatches();
+ bool renderOpaque = !debug_noopaque();
+ bool renderAlpha = !debug_noalpha();
- if (Q_UNLIKELY(debug_render())) {
- qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, render: %d",
- (int) timeRenderLists,
- (int) timePrepareOpaque, (int) timePrepareAlpha,
- (int) timeSorting,
- (int) timeUploadOpaque, (int) timeUploadAlpha,
- (int) timer.elapsed());
+ m_pstate.viewport =
+ QRhiViewport(viewport.x(), deviceRect().bottom() - viewport.bottom(), viewport.width(),
+ viewport.height(), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
+ m_pstate.clearColor = clearColor();
+ m_pstate.dsClear = QRhiDepthStencilClearValue(1.0f, 0);
+ m_pstate.viewportSet = false;
+ m_pstate.scissorSet = false;
+
+ m_gstate.depthTest = useDepthBuffer();
+ m_gstate.depthWrite = useDepthBuffer();
+ m_gstate.depthFunc = QRhiGraphicsPipeline::Less;
+ m_gstate.blending = false;
+
+ m_gstate.cullMode = QRhiGraphicsPipeline::None;
+ m_gstate.polygonMode = QRhiGraphicsPipeline::Fill;
+ m_gstate.colorWrite = QRhiGraphicsPipeline::R
+ | QRhiGraphicsPipeline::G
+ | QRhiGraphicsPipeline::B
+ | QRhiGraphicsPipeline::A;
+ m_gstate.usesScissor = false;
+ m_gstate.stencilTest = false;
+
+ m_gstate.sampleCount = renderTarget().rt->sampleCount();
+ m_gstate.multiViewCount = renderTarget().multiViewCount;
+
+ ctx->opaqueRenderBatches.clear();
+ if (Q_LIKELY(renderOpaque)) {
+ for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
+ Batch *b = m_opaqueBatches.at(i);
+ PreparedRenderBatch renderBatch;
+ bool ok;
+ if (b->merged)
+ ok = prepareRenderMergedBatch(b, &renderBatch);
+ else
+ ok = prepareRenderUnmergedBatch(b, &renderBatch);
+ if (ok)
+ ctx->opaqueRenderBatches.append(renderBatch);
+ }
+ }
+
+ m_gstate.blending = true;
+ // factors never change, always set for premultiplied alpha based blending
+
+ // depth test stays enabled (if useDepthBuffer(), that is) but no need
+ // to write out depth from the transparent (back-to-front) pass
+ m_gstate.depthWrite = false;
+
+ // special case: the 3D plane mode tests against the depth buffer, but does
+ // not write (and all batches are alpha because this render mode evaluates
+ // to useDepthBuffer()==false)
+ if (m_renderMode == QSGRendererInterface::RenderMode3D) {
+ Q_ASSERT(m_opaqueBatches.isEmpty());
+ m_gstate.depthTest = true;
+ }
+
+ ctx->alphaRenderBatches.clear();
+ if (Q_LIKELY(renderAlpha)) {
+ for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
+ Batch *b = m_alphaBatches.at(i);
+ PreparedRenderBatch renderBatch;
+ bool ok;
+ if (b->merged)
+ ok = prepareRenderMergedBatch(b, &renderBatch);
+ else if (b->isRenderNode)
+ ok = prepareRhiRenderNode(b, &renderBatch);
+ else
+ ok = prepareRenderUnmergedBatch(b, &renderBatch);
+ if (ok)
+ ctx->alphaRenderBatches.append(renderBatch);
+ }
}
m_rebuild = 0;
+
+#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
m_renderOrderRebuildLower = -1;
m_renderOrderRebuildUpper = -1;
+#endif
- if (m_visualizeMode != VisualizeNothing)
- visualize();
+ if (m_visualizer->mode() != Visualizer::VisualizeNothing)
+ m_visualizer->prepareVisualize();
- if (m_vao)
- m_vao->release();
+ renderTarget().cb->resourceUpdate(m_resourceUpdates);
+ m_resourceUpdates = nullptr;
+}
+
+void Renderer::beginRenderPass(RenderPassContext *)
+{
+ const QSGRenderTarget &rt(renderTarget());
+ rt.cb->beginPass(rt.rt, m_pstate.clearColor, m_pstate.dsClear, nullptr,
+ // we cannot tell if the application will have
+ // native rendering thrown in to this pass
+ // (QQuickWindow::beginExternalCommands()), so
+ // we have no choice but to set the flag always
+ // (thus triggering using secondary command
+ // buffers with Vulkan)
+ QRhiCommandBuffer::ExternalContent);
+
+ if (m_renderPassRecordingCallbacks.start)
+ m_renderPassRecordingCallbacks.start(m_renderPassRecordingCallbacks.userData);
+}
+
+void Renderer::recordRenderPass(RenderPassContext *ctx)
+{
+ // prepareRenderPass and recordRenderPass must always be called together.
+ // They are separate because beginRenderPass and endRenderPass are optional.
+ //
+ // The valid call sequence are therefore:
+ // prepare, begin, record, end
+ // or
+ // prepare, record
+
+ if (!ctx->valid)
+ qWarning("recordRenderPass() called without a prepared render pass context");
+
+ ctx->valid = false;
+
+ QRhiCommandBuffer *cb = renderTarget().cb;
+ cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
+
+ for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
+ if (i == 0)
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
+ PreparedRenderBatch *renderBatch = &ctx->opaqueRenderBatches[i];
+ if (renderBatch->batch->merged)
+ renderMergedBatch(renderBatch);
+ else
+ renderUnmergedBatch(renderBatch);
+ }
+
+ for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
+ if (i == 0) {
+ if (m_renderMode == QSGRendererInterface::RenderMode3D)
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
+ else
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
+ }
+ PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
+ if (renderBatch->batch->merged)
+ renderMergedBatch(renderBatch);
+ else if (renderBatch->batch->isRenderNode)
+ renderRhiRenderNode(renderBatch->batch);
+ else
+ renderUnmergedBatch(renderBatch);
+ }
+
+ if (m_renderMode == QSGRendererInterface::RenderMode3D) {
+ // Depth post-pass to fill up the depth buffer in a way that it
+ // corresponds to what got rendered to the color buffer in the previous
+ // (alpha) pass. The previous pass cannot enable depth write due to Z
+ // fighting. Rather, do it separately in a dedicated color-write-off,
+ // depth-write-on pass. This enables the 3D content drawn afterwards to
+ // depth test against the 2D items' rendering.
+ for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
+ if (i == 0)
+ cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
+ PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
+ if (renderBatch->batch->merged)
+ renderMergedBatch(renderBatch, true);
+ else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
+ renderUnmergedBatch(renderBatch, true);
+ }
+ }
+
+ if (m_currentShader)
+ setActiveRhiShader(nullptr, nullptr);
+
+ cb->debugMarkEnd();
+
+ if (Q_UNLIKELY(debug_render())) {
+ qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
+ (int) ctx->timeRenderLists,
+ (int) ctx->timePrepareOpaque, (int) ctx->timePrepareAlpha,
+ (int) ctx->timeSorting,
+ (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
+ (int) ctx->timer.elapsed());
+ }
+}
+
+void Renderer::endRenderPass(RenderPassContext *)
+{
+ if (m_renderPassRecordingCallbacks.end)
+ m_renderPassRecordingCallbacks.end(m_renderPassRecordingCallbacks.userData);
+
+ if (m_visualizer->mode() != Visualizer::VisualizeNothing)
+ m_visualizer->visualize();
+
+ renderTarget().cb->endPass();
}
struct RenderNodeState : public QSGRenderNode::RenderState
@@ -2770,42 +4002,30 @@ struct RenderNodeState : public QSGRenderNode::RenderState
bool m_stencilEnabled;
};
-void Renderer::renderRenderNode(Batch *batch)
+bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBatch)
{
if (Q_UNLIKELY(debug_render()))
qDebug() << " -" << batch << "rendernode";
Q_ASSERT(batch->first->isRenderNode);
- RenderNodeElement *e = (RenderNodeElement *) batch->first;
+ RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
- setActiveShader(nullptr, nullptr);
+ setActiveRhiShader(nullptr, nullptr);
- QSGNode *clip = e->renderNode->parent();
QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(e->renderNode);
rd->m_clip_list = nullptr;
- while (clip != rootNode()) {
- if (clip->type() == QSGNode::ClipNodeType) {
- rd->m_clip_list = static_cast<QSGClipNode *>(clip);
- break;
+ if (m_renderMode != QSGRendererInterface::RenderMode3D) {
+ QSGNode *clip = e->renderNode->parent();
+ while (clip != rootNode()) {
+ if (clip->type() == QSGNode::ClipNodeType) {
+ rd->m_clip_list = static_cast<QSGClipNode *>(clip);
+ break;
+ }
+ clip = clip->parent();
}
- clip = clip->parent();
+ updateClipState(rd->m_clip_list, batch);
}
- updateClip(rd->m_clip_list, batch);
-
- QMatrix4x4 pm = projectionMatrix();
- if (m_useDepthBuffer) {
- pm(2, 2) = m_zRange;
- pm(2, 3) = 1.0f - e->order * m_zRange;
- }
-
- RenderNodeState state;
- state.m_projectionMatrix = &pm;
- state.m_scissorEnabled = m_currentClipType & ScissorClip;
- state.m_stencilEnabled = m_currentClipType & StencilClip;
- state.m_scissorRect = m_currentScissorRect;
- state.m_stencilValue = m_currentStencilValue;
-
QSGNode *xform = e->renderNode->parent();
QMatrix4x4 matrix;
QSGNode *root = rootNode();
@@ -2820,7 +4040,8 @@ void Renderer::renderRenderNode(Batch *batch)
}
xform = xform->parent();
}
- rd->m_matrix = &matrix;
+ rd->m_localMatrix = matrix;
+ rd->m_matrix = &rd->m_localMatrix;
QSGNode *opacity = e->renderNode->parent();
rd->m_opacity = 1.0;
@@ -2832,158 +4053,182 @@ void Renderer::renderRenderNode(Batch *batch)
opacity = opacity->parent();
}
- glDisable(GL_STENCIL_TEST);
- glDisable(GL_SCISSOR_TEST);
- glDisable(GL_DEPTH_TEST);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-
- QSGRenderNode::StateFlags changes = e->renderNode->changedStates();
+ rd->m_rt = renderTarget();
- GLuint prevFbo = 0;
- if (changes & QSGRenderNode::RenderTargetState)
- glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &prevFbo);
+ const int viewCount = projectionMatrixCount();
+ rd->m_projectionMatrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ rd->m_projectionMatrix[viewIndex] = projectionMatrix(viewIndex);
- e->renderNode->render(&state);
+ if (useDepthBuffer()) {
+ // this cannot be multiview
+ rd->m_projectionMatrix[0](2, 2) = m_zRange;
+ rd->m_projectionMatrix[0](2, 3) = calculateElementZOrder(e, m_zRange);
+ }
- rd->m_matrix = nullptr;
- rd->m_clip_list = nullptr;
+ e->renderNode->prepare();
- if (changes & QSGRenderNode::ViewportState) {
- QRect r = viewportRect();
- glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height());
- }
+ renderBatch->batch = batch;
+ renderBatch->sms = nullptr;
- if (changes & QSGRenderNode::StencilState) {
- glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
- glStencilMask(0xff);
- glDisable(GL_STENCIL_TEST);
- }
+ return true;
+}
- if (changes & (QSGRenderNode::StencilState | QSGRenderNode::ScissorState)) {
- glDisable(GL_SCISSOR_TEST);
- m_currentClip = nullptr;
- m_currentClipType = NoClip;
- }
+void Renderer::renderRhiRenderNode(const Batch *batch)
+{
+ if (batch->clipState.type & ClipState::StencilClip)
+ enqueueStencilDraw(batch);
- if (changes & QSGRenderNode::DepthState)
- glDisable(GL_DEPTH_TEST);
+ RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
+ QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(e->renderNode);
- if (changes & QSGRenderNode::ColorState)
- bindable()->reactivate();
+ RenderNodeState state;
+ // Expose only the first matrix through the state object, the rest are
+ // queriable through the QSGRenderNode getters anyway.
+ state.m_projectionMatrix = &rd->m_projectionMatrix[0];
+ const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
+ state.m_scissorRect = QRect(scissor[0], scissor[1], scissor[2], scissor[3]);
+ state.m_stencilValue = batch->clipState.stencilRef;
+ state.m_scissorEnabled = batch->clipState.type & ClipState::ScissorClip;
+ state.m_stencilEnabled = batch->clipState.type & ClipState::StencilClip;
+
+ const QSGRenderNode::StateFlags changes = e->renderNode->changedStates();
+
+ QRhiCommandBuffer *cb = renderTarget().cb;
+ const bool needsExternal = !e->renderNode->flags().testFlag(QSGRenderNode::NoExternalRendering);
+ if (needsExternal)
+ cb->beginExternal();
+ e->renderNode->render(&state);
+ if (needsExternal)
+ cb->endExternal();
- if (changes & QSGRenderNode::BlendState) {
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- }
+ rd->m_matrix = nullptr;
+ rd->m_clip_list = nullptr;
- if (changes & QSGRenderNode::CullState) {
- glFrontFace(isMirrored() ? GL_CW : GL_CCW);
- glDisable(GL_CULL_FACE);
+ if ((changes & QSGRenderNode::ViewportState)
+ || (changes & QSGRenderNode::ScissorState))
+ {
+ // Reset both flags if either is reported as changed, since with the rhi
+ // it could be setViewport() that will record the resetting of the scissor.
+ m_pstate.viewportSet = false;
+ m_pstate.scissorSet = false;
}
- if (changes & QSGRenderNode::RenderTargetState)
- glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
+ // Do not bother with RenderTargetState. Where applicable, endExternal()
+ // ensures the correct target is rebound. For others (like Vulkan) it makes
+ // no sense since render() could not possibly do that on our command buffer
+ // which is in renderpass recording state.
}
-void Renderer::releaseCachedResources()
+void Renderer::setVisualizationMode(const QByteArray &mode)
{
- m_shaderManager->invalidated();
+ if (mode.isEmpty())
+ m_visualizer->setMode(Visualizer::VisualizeNothing);
+ else if (mode == "clip")
+ m_visualizer->setMode(Visualizer::VisualizeClipping);
+ else if (mode == "overdraw")
+ m_visualizer->setMode(Visualizer::VisualizeOverdraw);
+ else if (mode == "batches")
+ m_visualizer->setMode(Visualizer::VisualizeBatches);
+ else if (mode == "changes")
+ m_visualizer->setMode(Visualizer::VisualizeChanges);
}
-class VisualizeShader : public QOpenGLShaderProgram
+bool Renderer::hasVisualizationModeWithContinuousUpdate() const
{
-public:
- int color;
- int matrix;
- int rotation;
- int pattern;
- int projection;
-};
+ return m_visualizer->mode() == Visualizer::VisualizeOverdraw;
+}
-void Renderer::visualizeDrawGeometry(const QSGGeometry *g)
+bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
{
- if (g->attributeCount() < 1)
- return;
- const QSGGeometry::Attribute *a = g->attributes();
- glVertexAttribPointer(0, a->tupleSize, a->type, false, g->sizeOfVertex(), g->vertexData());
- if (g->indexCount())
- glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
- else
- glDrawArrays(g->drawingMode(), 0, g->vertexCount());
-
+ return a.depthTest == b.depthTest
+ && a.depthWrite == b.depthWrite
+ && a.depthFunc == b.depthFunc
+ && a.blending == b.blending
+ && a.srcColor == b.srcColor
+ && a.dstColor == b.dstColor
+ && a.srcAlpha == b.srcAlpha
+ && a.dstAlpha == b.dstAlpha
+ && a.opColor == b.opColor
+ && a.opAlpha == b.opAlpha
+ && a.colorWrite == b.colorWrite
+ && a.cullMode == b.cullMode
+ && a.usesScissor == b.usesScissor
+ && a.stencilTest == b.stencilTest
+ && a.sampleCount == b.sampleCount
+ && a.drawMode == b.drawMode
+ && a.lineWidth == b.lineWidth
+ && a.polygonMode == b.polygonMode
+ && a.multiViewCount == b.multiViewCount;
}
-void Renderer::visualizeBatch(Batch *b)
+bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
{
- VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
-
- if (b->positionAttribute != 0)
- return;
-
- QSGGeometryNode *gn = b->first->node;
- QSGGeometry *g = gn->geometry();
- const QSGGeometry::Attribute &a = g->attributes()[b->positionAttribute];
-
- glBindBuffer(GL_ARRAY_BUFFER, b->vbo.id);
+ return !(a == b);
+}
- QMatrix4x4 matrix(m_current_projection_matrix);
- if (b->root)
- matrix = matrix * qsg_matrixForRoot(b->root);
+size_t qHash(const GraphicsState &s, size_t seed) noexcept
+{
+ // do not bother with all fields
+ return seed
+ + s.depthTest * 1000
+ + s.depthWrite * 100
+ + s.depthFunc
+ + s.blending * 10
+ + s.srcColor
+ + s.cullMode
+ + s.usesScissor
+ + s.stencilTest
+ + s.sampleCount
+ + s.multiViewCount;
+}
- shader->setUniformValue(shader->pattern, float(b->merged ? 0 : 1));
+bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
+{
+ return a.state == b.state
+ && a.sms->materialShader == b.sms->materialShader
+ && a.renderTargetDescription == b.renderTargetDescription
+ && a.srbLayoutDescription == b.srbLayoutDescription;
+}
- QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 1.0, 1.0);
- float cr = color.redF();
- float cg = color.greenF();
- float cb = color.blueF();
- shader->setUniformValue(shader->color, cr, cg, cb, 1.0);
+bool operator!=(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
+{
+ return !(a == b);
+}
- if (b->merged) {
- shader->setUniformValue(shader->matrix, matrix);
- const auto &dataStart = m_context->separateIndexBuffer() ? b->ibo.data : b->vbo.data;
- for (int ds=0; ds<b->drawSets.size(); ++ds) {
- const DrawSet &set = b->drawSets.at(ds);
- glVertexAttribPointer(a.position, 2, a.type, false, g->sizeOfVertex(), (void *) (qintptr) (set.vertices));
- glDrawElements(g->drawingMode(), set.indexCount, GL_UNSIGNED_SHORT,
- (void *)(qintptr)(dataStart + set.indices));
- }
- } else {
- Element *e = b->first;
- int offset = 0;
- while (e) {
- gn = e->node;
- g = gn->geometry();
- shader->setUniformValue(shader->matrix, matrix * *gn->matrix());
- glVertexAttribPointer(a.position, a.tupleSize, a.type, false, g->sizeOfVertex(), (void *) (qintptr) offset);
- if (g->indexCount())
- glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
- else
- glDrawArrays(g->drawingMode(), 0, g->vertexCount());
- offset += g->sizeOfVertex() * g->vertexCount();
- e = e->nextInBatch;
- }
- }
+size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
+{
+ return qHash(k.state, seed)
+ ^ qHash(k.sms->materialShader)
+ ^ k.extra.renderTargetDescriptionHash
+ ^ k.extra.srbLayoutDescriptionHash;
}
+bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
+{
+ return a.type == b.type
+ && a.renderMode == b.renderMode
+ && a.multiViewCount == b.multiViewCount;
+}
+bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
+{
+ return !(a == b);
+}
+size_t qHash(const ShaderKey &k, size_t seed) noexcept
+{
+ return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
+}
-void Renderer::visualizeClipping(QSGNode *node)
+Visualizer::Visualizer(Renderer *renderer)
+ : m_renderer(renderer),
+ m_visualizeMode(VisualizeNothing)
{
- if (node->type() == QSGNode::ClipNodeType) {
- VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
- QSGClipNode *clipNode = static_cast<QSGClipNode *>(node);
- QMatrix4x4 matrix = m_current_projection_matrix;
- if (clipNode->matrix())
- matrix = matrix * *clipNode->matrix();
- shader->setUniformValue(shader->matrix, matrix);
- visualizeDrawGeometry(clipNode->geometry());
- }
+}
- QSGNODE_TRAVERSE(node) {
- visualizeClipping(child);
- }
+Visualizer::~Visualizer()
+{
}
#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \
@@ -2991,215 +4236,19 @@ void Renderer::visualizeClipping(QSGNode *node)
| QSGNode::DirtyMatrix \
| QSGNode::DirtyNodeRemoved)
-void Renderer::visualizeChangesPrepare(Node *n, uint parentChanges)
+void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
{
uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
uint selfDirty = n->dirtyState | parentChanges;
if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
- m_visualizeChanceSet.insert(n, selfDirty);
+ m_visualizeChangeSet.insert(n, selfDirty);
SHADOWNODE_TRAVERSE(n) {
visualizeChangesPrepare(child, childDirty);
}
}
-void Renderer::visualizeChanges(Node *n)
-{
-
- if (n->type() == QSGNode::GeometryNodeType && n->element()->batch && m_visualizeChanceSet.contains(n)) {
- uint dirty = m_visualizeChanceSet.value(n);
- bool tinted = (dirty & QSGNODE_DIRTY_PARENT) != 0;
-
- VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
- QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 0.3, 1.0);
- float ca = 0.5;
- float cr = color.redF() * ca;
- float cg = color.greenF() * ca;
- float cb = color.blueF() * ca;
- shader->setUniformValue(shader->color, cr, cg, cb, ca);
- shader->setUniformValue(shader->pattern, float(tinted ? 0.5 : 0));
-
- QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
-
- QMatrix4x4 matrix = m_current_projection_matrix;
- if (n->element()->batch->root)
- matrix = matrix * qsg_matrixForRoot(n->element()->batch->root);
- matrix = matrix * *gn->matrix();
- shader->setUniformValue(shader->matrix, matrix);
- visualizeDrawGeometry(gn->geometry());
-
- // This is because many changes don't propegate their dirty state to the
- // parent so the node updater will not unset these states. They are
- // not used for anything so, unsetting it should have no side effects.
- n->dirtyState = nullptr;
- }
-
- SHADOWNODE_TRAVERSE(n) {
- visualizeChanges(child);
- }
-}
-
-void Renderer::visualizeOverdraw_helper(Node *node)
-{
- if (node->type() == QSGNode::GeometryNodeType && node->element()->batch) {
- VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
- QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
-
- QMatrix4x4 matrix = m_current_projection_matrix;
- matrix(2, 2) = m_zRange;
- matrix(2, 3) = 1.0f - node->element()->order * m_zRange;
-
- if (node->element()->batch->root)
- matrix = matrix * qsg_matrixForRoot(node->element()->batch->root);
- matrix = matrix * *gn->matrix();
- shader->setUniformValue(shader->matrix, matrix);
-
- QColor color = node->element()->batch->isOpaque ? QColor::fromRgbF(0.3, 1.0, 0.3) : QColor::fromRgbF(1.0, 0.3, 0.3);
- float ca = 0.33f;
- shader->setUniformValue(shader->color, color.redF() * ca, color.greenF() * ca, color.blueF() * ca, ca);
-
- visualizeDrawGeometry(gn->geometry());
- }
-
- SHADOWNODE_TRAVERSE(node) {
- visualizeOverdraw_helper(child);
- }
-}
-
-void Renderer::visualizeOverdraw()
-{
- VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
- shader->setUniformValue(shader->color, 0.5f, 0.5f, 1.0f, 1.0f);
- shader->setUniformValue(shader->projection, 1);
-
- glBlendFunc(GL_ONE, GL_ONE);
-
- static float step = 0;
- step += static_cast<float>(M_PI * 2 / 1000.);
- if (step > M_PI * 2)
- step = 0;
- float angle = 80.0 * std::sin(step);
-
- QMatrix4x4 xrot; xrot.rotate(20, 1, 0, 0);
- QMatrix4x4 zrot; zrot.rotate(angle, 0, 0, 1);
- QMatrix4x4 tx; tx.translate(0, 0, 1);
-
- QMatrix4x4 m;
-
-// m.rotate(180, 0, 1, 0);
-
- m.translate(0, 0.5, 4);
- m.scale(2, 2, 1);
-
- m.rotate(-30, 1, 0, 0);
- m.rotate(angle, 0, 1, 0);
- m.translate(0, 0, -1);
-
- shader->setUniformValue(shader->rotation, m);
-
- float box[] = {
- // lower
- -1, 1, 0, 1, 1, 0,
- -1, 1, 0, -1, -1, 0,
- 1, 1, 0, 1, -1, 0,
- -1, -1, 0, 1, -1, 0,
-
- // upper
- -1, 1, 1, 1, 1, 1,
- -1, 1, 1, -1, -1, 1,
- 1, 1, 1, 1, -1, 1,
- -1, -1, 1, 1, -1, 1,
-
- // sides
- -1, -1, 0, -1, -1, 1,
- 1, -1, 0, 1, -1, 1,
- -1, 1, 0, -1, 1, 1,
- 1, 1, 0, 1, 1, 1
- };
- glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, box);
- glLineWidth(2);
- glDrawArrays(GL_LINES, 0, 24);
-
- visualizeOverdraw_helper(m_nodes.value(rootNode()));
-
- // Animate the view...
- QSurface *surface = m_context->openglContext()->surface();
- if (surface->surfaceClass() == QSurface::Window)
- if (QQuickWindow *window = qobject_cast<QQuickWindow *>(static_cast<QWindow *>(surface)))
- window->update();
-}
-
-void Renderer::setCustomRenderMode(const QByteArray &mode)
-{
- if (mode.isEmpty()) m_visualizeMode = VisualizeNothing;
- else if (mode == "clip") m_visualizeMode = VisualizeClipping;
- else if (mode == "overdraw") m_visualizeMode = VisualizeOverdraw;
- else if (mode == "batches") m_visualizeMode = VisualizeBatches;
- else if (mode == "changes") m_visualizeMode = VisualizeChanges;
-}
-
-void Renderer::visualize()
-{
- if (!m_shaderManager->visualizeProgram) {
- VisualizeShader *prog = new VisualizeShader();
- QSGShaderSourceBuilder::initializeProgramFromFiles(
- prog,
- QStringLiteral(":/qt-project.org/scenegraph/shaders/visualization.vert"),
- QStringLiteral(":/qt-project.org/scenegraph/shaders/visualization.frag"));
- prog->bindAttributeLocation("v", 0);
- prog->link();
- prog->bind();
- prog->color = prog->uniformLocation("color");
- prog->pattern = prog->uniformLocation("pattern");
- prog->projection = prog->uniformLocation("projection");
- prog->matrix = prog->uniformLocation("matrix");
- prog->rotation = prog->uniformLocation("rotation");
- m_shaderManager->visualizeProgram = prog;
- } else {
- m_shaderManager->visualizeProgram->bind();
- }
- VisualizeShader *shader = static_cast<VisualizeShader *>(m_shaderManager->visualizeProgram);
-
- glDisable(GL_DEPTH_TEST);
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- glEnableVertexAttribArray(0);
-
- // Blacken out the actual rendered content...
- float bgOpacity = 0.8f;
- if (m_visualizeMode == VisualizeBatches)
- bgOpacity = 1.0;
- float v[] = { -1, 1, 1, 1, -1, -1, 1, -1 };
- shader->setUniformValue(shader->color, 0.0f, 0.0f, 0.0f, bgOpacity);
- shader->setUniformValue(shader->matrix, QMatrix4x4());
- shader->setUniformValue(shader->rotation, QMatrix4x4());
- shader->setUniformValue(shader->pattern, 0.0f);
- shader->setUniformValue(shader->projection, false);
- glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, v);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
- if (m_visualizeMode == VisualizeBatches) {
- srand(0); // To force random colors to be roughly the same every time..
- for (int i=0; i<m_opaqueBatches.size(); ++i) visualizeBatch(m_opaqueBatches.at(i));
- for (int i=0; i<m_alphaBatches.size(); ++i) visualizeBatch(m_alphaBatches.at(i));
- } else if (m_visualizeMode == VisualizeClipping) {
- shader->setUniformValue(shader->pattern, 0.5f);
- shader->setUniformValue(shader->color, 0.2f, 0.0f, 0.0f, 0.2f);
- visualizeClipping(rootNode());
- } else if (m_visualizeMode == VisualizeChanges) {
- visualizeChanges(m_nodes.value(rootNode()));
- m_visualizeChanceSet.clear();
- } else if (m_visualizeMode == VisualizeOverdraw) {
- visualizeOverdraw();
- }
-
- // Reset state back to defaults..
- glDisable(GL_BLEND);
- glDisableVertexAttribArray(0);
- shader->release();
-}
+} // namespace QSGBatchRenderer
QT_END_NAMESPACE
-}
-
#include "moc_qsgbatchrenderer_p.cpp"