aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph')
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp8
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp17
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp4
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp148
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h13
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp3
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp4
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h2
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp57
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h4
-rw-r--r--src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp2
-rw-r--r--src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h6
-rw-r--r--src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp148
-rw-r--r--src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h28
-rw-r--r--src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h7
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp236
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h34
-rw-r--r--src/quick/scenegraph/coreapi/qsggeometry.cpp25
-rw-r--r--src/quick/scenegraph/coreapi/qsggeometry.h4
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterial.cpp200
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterial.h6
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterialshader.cpp85
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterialshader.h17
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterialshader_p.h6
-rw-r--r--src/quick/scenegraph/coreapi/qsgnode.cpp11
-rw-r--r--src/quick/scenegraph/coreapi/qsgnode.h4
-rw-r--r--src/quick/scenegraph/coreapi/qsgnodeupdater_p.h2
-rw-r--r--src/quick/scenegraph/coreapi/qsgrenderer.cpp11
-rw-r--r--src/quick/scenegraph/coreapi/qsgrenderer_p.h22
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendererinterface.cpp89
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendererinterface.h5
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode.cpp214
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode.h5
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode_p.h5
-rw-r--r--src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp8
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture.cpp106
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture_mac.mm2
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture_p.h28
-rw-r--r--src/quick/scenegraph/coreapi/qsgtexture_platform.h27
-rw-r--r--src/quick/scenegraph/qsgadaptationlayer.cpp17
-rw-r--r--src/quick/scenegraph/qsgadaptationlayer_p.h40
-rw-r--r--src/quick/scenegraph/qsgbasicglyphnode_p.h2
-rw-r--r--src/quick/scenegraph/qsgbasicinternalimagenode.cpp18
-rw-r--r--src/quick/scenegraph/qsgbasicinternalimagenode_p.h2
-rw-r--r--src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp580
-rw-r--r--src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h14
-rw-r--r--src/quick/scenegraph/qsgcontext.cpp173
-rw-r--r--src/quick/scenegraph/qsgcontext_p.h54
-rw-r--r--src/quick/scenegraph/qsgcontextplugin.cpp2
-rw-r--r--src/quick/scenegraph/qsgcontextplugin_p.h4
-rw-r--r--src/quick/scenegraph/qsgcurveabstractnode_p.h32
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode.cpp61
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.cpp396
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.h273
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p_p.h57
-rw-r--r--src/quick/scenegraph/qsgcurveglyphatlas.cpp142
-rw-r--r--src/quick/scenegraph/qsgcurveglyphatlas_p.h69
-rw-r--r--src/quick/scenegraph/qsgcurveglyphnode.cpp164
-rw-r--r--src/quick/scenegraph/qsgcurveglyphnode_p.h68
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor.cpp1887
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor_p.h53
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode.cpp112
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p.cpp90
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p.h116
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p_p.h75
-rw-r--r--src/quick/scenegraph/qsgdefaultcontext.cpp19
-rw-r--r--src/quick/scenegraph/qsgdefaultcontext_p.h5
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode.cpp7
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode_p.cpp187
-rw-r--r--src/quick/scenegraph/qsgdefaultglyphnode_p_p.h1
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalimagenode.cpp17
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalimagenode_p.h4
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp30
-rw-r--r--src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h4
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext.cpp98
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext_p.h13
-rw-r--r--src/quick/scenegraph/qsgdefaultspritenode.cpp26
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode.cpp10
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp189
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h12
-rw-r--r--src/quick/scenegraph/qsgrenderloop.cpp71
-rw-r--r--src/quick/scenegraph/qsgrenderloop_p.h30
-rw-r--r--src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp23
-rw-r--r--src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h4
-rw-r--r--src/quick/scenegraph/qsgrhiinternaltextnode.cpp41
-rw-r--r--src/quick/scenegraph/qsgrhiinternaltextnode_p.h31
-rw-r--r--src/quick/scenegraph/qsgrhilayer.cpp9
-rw-r--r--src/quick/scenegraph/qsgrhilayer_p.h4
-rw-r--r--src/quick/scenegraph/qsgrhishadereffectnode.cpp139
-rw-r--r--src/quick/scenegraph/qsgrhishadereffectnode_p.h1
-rw-r--r--src/quick/scenegraph/qsgrhisupport.cpp209
-rw-r--r--src/quick/scenegraph/qsgrhisupport_p.h37
-rw-r--r--src/quick/scenegraph/qsgrhitextureglyphcache.cpp72
-rw-r--r--src/quick/scenegraph/qsgrhitextureglyphcache_p.h3
-rw-r--r--src/quick/scenegraph/qsgthreadedrenderloop.cpp192
-rw-r--r--src/quick/scenegraph/qsgthreadedrenderloop_p.h2
-rw-r--r--src/quick/scenegraph/shaders_ng/24bittextmask.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/32bitcolortext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/8bittextmask.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/8bittextmask_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert16
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert18
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext.vert16
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/flatcolor.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/flatcolor.vert14
-rw-r--r--src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag15
-rw-r--r--src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/opaquetexture.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/opaquetexture.vert14
-rw-r--r--src/quick/scenegraph/shaders_ng/outlinedtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/outlinedtext.vert28
-rw-r--r--src/quick/scenegraph/shaders_ng/outlinedtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/shadereffect.frag7
-rw-r--r--src/quick/scenegraph/shaders_ng/shadereffect.vert10
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.frag152
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.vert92
-rw-r--r--src/quick/scenegraph/shaders_ng/shapestroke.frag134
-rw-r--r--src/quick/scenegraph/shaders_ng/shapestroke.vert82
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothcolor.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothcolor.vert28
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothtexture.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/smoothtexture.vert28
-rw-r--r--src/quick/scenegraph/shaders_ng/sprite.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/sprite.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/stencilclip.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/styledtext.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/styledtext.vert22
-rw-r--r--src/quick/scenegraph/shaders_ng/styledtext_a.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/textmask.frag13
-rw-r--r--src/quick/scenegraph/shaders_ng/textmask.vert20
-rw-r--r--src/quick/scenegraph/shaders_ng/texture.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/texture.vert14
-rw-r--r--src/quick/scenegraph/shaders_ng/vertexcolor.frag3
-rw-r--r--src/quick/scenegraph/shaders_ng/vertexcolor.vert16
-rw-r--r--src/quick/scenegraph/shaders_ng/visualization.frag3
-rw-r--r--src/quick/scenegraph/util/qquadpath.cpp950
-rw-r--r--src/quick/scenegraph/util/qquadpath_p.h341
-rw-r--r--src/quick/scenegraph/util/qsgareaallocator.cpp13
-rw-r--r--src/quick/scenegraph/util/qsgareaallocator_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgdefaultimagenode_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgdefaultpainternode.cpp5
-rw-r--r--src/quick/scenegraph/util/qsgdefaultpainternode_p.h4
-rw-r--r--src/quick/scenegraph/util/qsgflatcolormaterial.cpp26
-rw-r--r--src/quick/scenegraph/util/qsggradientcache.cpp121
-rw-r--r--src/quick/scenegraph/util/qsggradientcache_p.h72
-rw-r--r--src/quick/scenegraph/util/qsgplaintexture.cpp7
-rw-r--r--src/quick/scenegraph/util/qsgplaintexture_p.h4
-rw-r--r--src/quick/scenegraph/util/qsgrhiatlastexture_p.h2
-rw-r--r--src/quick/scenegraph/util/qsgsimpletexturenode.cpp2
-rw-r--r--src/quick/scenegraph/util/qsgtextnode.cpp320
-rw-r--r--src/quick/scenegraph/util/qsgtextnode.h101
-rw-r--r--src/quick/scenegraph/util/qsgtexturematerial.cpp35
-rw-r--r--src/quick/scenegraph/util/qsgtexturematerial_p.h6
-rw-r--r--src/quick/scenegraph/util/qsgtransform.cpp10
-rw-r--r--src/quick/scenegraph/util/qsgtransform_p.h105
-rw-r--r--src/quick/scenegraph/util/qsgvertexcolormaterial.cpp32
176 files changed, 9514 insertions, 1403 deletions
diff --git a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp
index d33919f344..c9385630d9 100644
--- a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp
@@ -178,8 +178,12 @@ QRegion QSGAbstractSoftwareRenderer::optimizeRenderList()
for (auto j = m_renderableNodes.begin(); j != m_renderableNodes.end(); ++j) {
auto node = *j;
- if (!node->isOpaque() && !m_dirtyRegion.isEmpty()) {
- // Only blended nodes need to be updated
+ if ((!node->isOpaque() || node->boundingRectMax() != node->boundingRectMin()) && !m_dirtyRegion.isEmpty()) {
+ // Blended nodes need to be updated
+ // QTBUG-113745: Also nodes with floating point boundary rectangles need to
+ // be updated. The reason is that m_obscuredRegion contains only the rounded
+ // down bounding rectangle (node->boundingRectMin()) and thus not the whole
+ // node. As a result up to 1 pixel would be overpainted when it should not.
node->addDirtyRegion(m_dirtyRegion, true);
}
diff --git a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h
index 5d27708342..b0426a0b37 100644
--- a/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer_p.h
@@ -26,7 +26,7 @@ class QSGSimpleRectNode;
class QSGSoftwareRenderableNode;
class QSGSoftwareRenderableNodeUpdater;
-class Q_QUICK_PRIVATE_EXPORT QSGAbstractSoftwareRenderer : public QSGRenderer
+class Q_QUICK_EXPORT QSGAbstractSoftwareRenderer : public QSGRenderer
{
public:
QSGAbstractSoftwareRenderer(QSGRenderContext *context);
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
index 4da188bbfe..4e07508ea0 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
@@ -75,10 +75,10 @@ QSGPainterNode *QSGSoftwareContext::createPainterNode(QQuickPaintedItem *item)
return new QSGSoftwarePainterNode(item);
}
-QSGGlyphNode *QSGSoftwareContext::createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality)
+QSGGlyphNode *QSGSoftwareContext::createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality)
{
Q_UNUSED(rc);
- Q_UNUSED(preferNativeGlyphNode);
+ Q_UNUSED(renderType);
Q_UNUSED(renderTypeQuality);
return new QSGSoftwareGlyphNode();
}
@@ -107,6 +107,17 @@ void QSGSoftwareRenderContext::initializeIfNeeded()
void QSGSoftwareRenderContext::invalidate()
{
+ qDeleteAll(m_texturesToDelete);
+ m_texturesToDelete.clear();
+
+ qDeleteAll(m_textures);
+ m_textures.clear();
+
+ Q_ASSERT(m_fontEnginesToClean.isEmpty());
+
+ qDeleteAll(m_glyphCaches);
+ m_glyphCaches.clear();
+
m_sg->renderContextInvalidated(this);
emit invalidated();
}
@@ -190,7 +201,7 @@ void *QSGSoftwareContext::getResource(QQuickWindow *window, Resource resource) c
if (resource == PainterResource)
return window->isSceneGraphInitialized() ? static_cast<QSGSoftwareRenderContext *>(cd->context)->m_activePainter : nullptr;
else if (resource == RedirectPaintDevice)
- return cd->redirect.rt.paintDevice;
+ return cd->redirect.rt.sw.paintDevice;
return nullptr;
}
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
index 6b95ebb1ad..68f18b490a 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
@@ -55,7 +55,7 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode() override;
QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) override;
QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override;
- QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) override;
+ QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) override;
QSGLayer *createLayer(QSGRenderContext *renderContext) override;
QSurfaceFormat defaultSurfaceFormat() const override;
QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext) override;
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp
index 15e8096d8d..83eef8b54f 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareglyphnode.cpp
@@ -59,6 +59,10 @@ void QSGSoftwareGlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &g
{
m_position = position;
m_glyphRun = glyphs;
+ // Decorations handled by text node
+ m_glyphRun.setOverline(false);
+ m_glyphRun.setStrikeOut(false);
+ m_glyphRun.setUnderline(false);
m_bounding_rect = calculateBoundingRect(position, glyphs);
}
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp
index 488f622dce..411c189b3d 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp
@@ -11,6 +11,10 @@ QT_BEGIN_NAMESPACE
QSGSoftwareInternalRectangleNode::QSGSoftwareInternalRectangleNode()
: m_penWidth(0)
, m_radius(0)
+ , m_topLeftRadius(-1)
+ , m_topRightRadius(-1)
+ , m_bottomLeftRadius(-1)
+ , m_bottomRightRadius(-1)
, m_vertical(true)
, m_cornerPixmapIsDirty(true)
, m_devicePixelRatio(1)
@@ -171,6 +175,42 @@ void QSGSoftwareInternalRectangleNode::setRadius(qreal radius)
}
}
+void QSGSoftwareInternalRectangleNode::setTopLeftRadius(qreal radius)
+{
+ if (m_topLeftRadius != radius) {
+ m_topLeftRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGSoftwareInternalRectangleNode::setTopRightRadius(qreal radius)
+{
+ if (m_topRightRadius != radius) {
+ m_topRightRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGSoftwareInternalRectangleNode::setBottomLeftRadius(qreal radius)
+{
+ if (m_bottomLeftRadius != radius) {
+ m_bottomLeftRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
+void QSGSoftwareInternalRectangleNode::setBottomRightRadius(qreal radius)
+{
+ if (m_bottomRightRadius != radius) {
+ m_bottomRightRadius = radius;
+ m_cornerPixmapIsDirty = true;
+ markDirty(DirtyMaterial);
+ }
+}
+
void QSGSoftwareInternalRectangleNode::setAligned(bool /*aligned*/)
{
}
@@ -185,9 +225,8 @@ void QSGSoftwareInternalRectangleNode::update()
}
if (!m_stops.isEmpty()) {
- QLinearGradient gradient(QPoint(0,0), QPoint(m_vertical ? 0 : 1, m_vertical ? 1 : 0));
+ QLinearGradient gradient(QPoint(0,0), QPoint(m_vertical ? 0 : m_rect.width(), m_vertical ? m_rect.height() : 0));
gradient.setStops(m_stops);
- gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
m_brush = QBrush(gradient);
} else {
m_brush = QBrush(m_color);
@@ -212,13 +251,21 @@ void QSGSoftwareInternalRectangleNode::paint(QPainter *painter)
//Rotated rectangles lose the benefits of direct rendering, and have poor rendering
//quality when using only blits and fills.
- if (m_radius == 0 && m_penWidth == 0) {
+ if (m_radius == 0
+ && m_penWidth == 0
+ && m_topLeftRadius <= 0
+ && m_topRightRadius <= 0
+ && m_bottomLeftRadius <= 0
+ && m_bottomRightRadius <= 0) {
//Non-Rounded Rects without borders (fall back to drawRect)
//Most common case
painter->setPen(Qt::NoPen);
painter->setBrush(m_brush);
painter->drawRect(m_rect);
- } else {
+ } else if (m_topLeftRadius < 0
+ && m_topRightRadius < 0
+ && m_bottomLeftRadius < 0
+ && m_bottomRightRadius < 0) {
//Rounded Rects and Rects with Borders
//Avoids broken behaviors of QPainter::drawRect/roundedRect
QPixmap pixmap = QPixmap(qRound(m_rect.width() * m_devicePixelRatio), qRound(m_rect.height() * m_devicePixelRatio));
@@ -231,12 +278,34 @@ void QSGSoftwareInternalRectangleNode::paint(QPainter *painter)
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
painter->drawPixmap(m_rect, pixmap);
painter->setRenderHints(previousRenderHints);
+ } else {
+ // Corners with different radii. Split implementation to avoid
+ // performance regression of the majority of cases
+ QPixmap pixmap = QPixmap(qRound(m_rect.width() * m_devicePixelRatio), qRound(m_rect.height() * m_devicePixelRatio));
+ pixmap.fill(Qt::transparent);
+ pixmap.setDevicePixelRatio(m_devicePixelRatio);
+ QPainter pixmapPainter(&pixmap);
+ // Slow function relying on paths
+ paintRectangleIndividualCorners(&pixmapPainter, QRect(0, 0, m_rect.width(), m_rect.height()));
+
+ QPainter::RenderHints previousRenderHints = painter->renderHints();
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
+ painter->drawPixmap(m_rect, pixmap);
+ painter->setRenderHints(previousRenderHints);
+
}
} else {
//Paint directly
- paintRectangle(painter, m_rect);
+ if (m_topLeftRadius < 0
+ && m_topRightRadius < 0
+ && m_bottomLeftRadius < 0
+ && m_bottomRightRadius < 0) {
+ paintRectangle(painter, m_rect);
+ } else {
+ paintRectangleIndividualCorners(painter, m_rect);
+ }
}
}
@@ -387,6 +456,75 @@ void QSGSoftwareInternalRectangleNode::paintRectangle(QPainter *painter, const Q
painter->setRenderHints(previousRenderHints);
}
+void QSGSoftwareInternalRectangleNode::paintRectangleIndividualCorners(QPainter *painter, const QRect &rect)
+{
+ QPainterPath path;
+
+ const float w = m_penWidth;
+
+ // Radius should never exceeds half of the width or half of the height
+ const float radiusTL = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_topLeftRadius < 0. ? m_radius : m_topLeftRadius));
+ const float radiusTR = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_topRightRadius < 0. ? m_radius : m_topRightRadius));
+ const float radiusBL = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_bottomLeftRadius < 0. ? m_radius : m_bottomLeftRadius));
+ const float radiusBR = qMin(qMin(rect.width(), rect.height()) * 0.5f, float(m_bottomRightRadius < 0 ? m_radius : m_bottomRightRadius));
+
+ const float innerRadiusTL = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusTL - w);
+ const float innerRadiusTR = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusTR - w);
+ const float innerRadiusBL = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusBL - w);
+ const float innerRadiusBR = qMin(qMin(rect.width(), rect.height()) * 0.5f, radiusBR - w);
+
+ QRect rect2 = rect.adjusted(0, 0, 1, 1);
+
+ path.moveTo(rect2.topRight() - QPointF(radiusTR, -w));
+ if (innerRadiusTR > 0.)
+ path.arcTo(QRectF(rect2.topRight() - QPointF(radiusTR + innerRadiusTR, -w), 2. * QSizeF(innerRadiusTR, innerRadiusTR)), 90, -90);
+ else
+ path.lineTo(rect2.topRight() - QPointF(w, -w));
+
+ if (innerRadiusBR > 0.)
+ path.arcTo(QRectF(rect2.bottomRight() - QPointF(radiusBR + innerRadiusBR, radiusBR + innerRadiusBR), 2. * QSizeF(innerRadiusBR, innerRadiusBR)), 0, -90);
+ else
+ path.lineTo(rect2.bottomRight() - QPointF(w, w));
+
+ if (innerRadiusBL > 0.)
+ path.arcTo(QRectF(rect2.bottomLeft() - QPointF(-w, radiusBL + innerRadiusBL), 2. * QSizeF(innerRadiusBL, innerRadiusBL)), -90, -90);
+ else
+ path.lineTo(rect2.bottomLeft() - QPointF(-w, w));
+ if (innerRadiusTL > 0.)
+ path.arcTo(QRectF(rect2.topLeft() + QPointF(w, w), 2. * QSizeF(innerRadiusTL, innerRadiusTL)), -180, -90);
+ else
+ path.lineTo(rect2.topLeft() + QPointF(w, w));
+ path.closeSubpath();
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(m_brush);
+ painter->drawPath(path);
+
+ if (w > 0) {
+ path.moveTo(rect2.topRight() - QPointF(radiusTR, 0.));
+ if (radiusTR > 0.)
+ path.arcTo(QRectF(rect2.topRight() - 2. * QPointF(radiusTR, 0.), 2. * QSizeF(radiusTR, radiusTR)), 90, -90);
+ else
+ path.lineTo(rect2.topRight());
+ if (radiusBR > 0.)
+ path.arcTo(QRectF(rect2.bottomRight() - 2. * QPointF(radiusBR, radiusBR), 2. * QSizeF(radiusBR, radiusBR)), 0, -90);
+ else
+ path.lineTo(rect2.bottomRight());
+ if (radiusBL > 0.)
+ path.arcTo(QRectF(rect2.bottomLeft() - 2. * QPointF(0., radiusBL), 2. * QSizeF(radiusBL, radiusBL)), -90, -90);
+ else
+ path.lineTo(rect2.bottomLeft());
+ if (radiusTL > 0.)
+ path.arcTo(QRectF(rect2.topLeft() - 2. * QPointF(0., 0.), 2. * QSizeF(radiusTL, radiusTL)), -180, -90);
+ else
+ path.lineTo(rect2.topLeft());
+ path.closeSubpath();
+
+ painter->setBrush(m_penColor);
+ painter->drawPath(path);
+ }
+}
+
void QSGSoftwareInternalRectangleNode::generateCornerPixmap()
{
//Generate new corner Pixmap
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h
index bf815f42df..ac58e7b254 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode_p.h
@@ -35,6 +35,10 @@ public:
void setGradientStops(const QGradientStops &stops) override;
void setGradientVertical(bool vertical) override;
void setRadius(qreal radius) override;
+ void setTopLeftRadius(qreal radius) override;
+ void setTopRightRadius(qreal radius) override;
+ void setBottomLeftRadius(qreal radius) override;
+ void setBottomRightRadius(qreal radius) override;
void setAntialiasing(bool antialiasing) override { Q_UNUSED(antialiasing); }
void setAligned(bool aligned) override;
@@ -46,14 +50,19 @@ public:
QRectF rect() const;
private:
void paintRectangle(QPainter *painter, const QRect &rect);
+ void paintRectangleIndividualCorners(QPainter *painter, const QRect &rect);
void generateCornerPixmap();
QRect m_rect;
QColor m_color;
QColor m_penColor;
- double m_penWidth;
+ qreal m_penWidth;
QGradientStops m_stops;
- double m_radius;
+ qreal m_radius;
+ qreal m_topLeftRadius;
+ qreal m_topRightRadius;
+ qreal m_bottomLeftRadius;
+ qreal m_bottomRightRadius;
QPen m_pen;
QBrush m_brush;
bool m_vertical;
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp
index 75b106f464..a200b84af8 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarepainternode.cpp
@@ -137,7 +137,10 @@ void QSGSoftwarePainterNode::update()
void QSGSoftwarePainterNode::paint(QPainter *painter)
{
+ bool before = painter->testRenderHint(QPainter::SmoothPixmapTransform);
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, m_linear_filtering);
painter->drawPixmap(0, 0, m_size.width(), m_size.height(), m_pixmap);
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, before);
}
void QSGSoftwarePainterNode::paint()
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp
index e59f894d7f..79fa4a78ad 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode.cpp
@@ -225,8 +225,8 @@ QRegion QSGSoftwareRenderableNode::renderNode(QPainter *painter, bool forceOpaqu
return QRegion();
} else {
QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(m_handle.renderNode);
- QMatrix4x4 m = m_transform;
- rd->m_matrix = &m;
+ rd->m_localMatrix = m_transform;
+ rd->m_matrix = &rd->m_localMatrix;
rd->m_opacity = m_opacity;
// all the clip region below is in world coordinates, taking m_transform into account already
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h
index 5e695bd615..281539b3cb 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderablenode_p.h
@@ -36,7 +36,7 @@ class QSGSoftwareNinePatchNode;
class QSGSoftwareSpriteNode;
class QSGRenderNode;
-class Q_QUICK_PRIVATE_EXPORT QSGSoftwareRenderableNode
+class Q_QUICK_EXPORT QSGSoftwareRenderableNode
{
public:
enum NodeType {
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h
index 9dff08cf42..e87f79f8f3 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarerenderer_p.h
@@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE
class QPaintDevice;
class QBackingStore;
-class Q_QUICK_PRIVATE_EXPORT QSGSoftwareRenderer : public QSGAbstractSoftwareRenderer
+class Q_QUICK_EXPORT QSGSoftwareRenderer : public QSGAbstractSoftwareRenderer
{
public:
QSGSoftwareRenderer(QSGRenderContext *context);
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp
index 54bcdf9b49..39aa89472f 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp
@@ -29,23 +29,6 @@
QT_BEGIN_NAMESPACE
-// Passed from the RL to the RT when a window is removed obscured and should be
-// removed from the render loop.
-const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
-
-// Passed from the RL to RT when GUI has been locked, waiting for sync.
-const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
-
-// Passed by the RL to the RT to maybe release resource if no windows are
-// rendering.
-const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
-
-// Passed by the RL to the RT when a QQuickWindow::grabWindow() is called.
-const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
-
-// Passed by the window when there is a render job to run.
-const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
-
class QSGSoftwareWindowEvent : public QEvent
{
public:
@@ -57,7 +40,7 @@ class QSGSoftwareTryReleaseEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareTryReleaseEvent(QQuickWindow *win, bool destroy)
- : QSGSoftwareWindowEvent(win, WM_TryRelease), destroying(destroy) { }
+ : QSGSoftwareWindowEvent(win, QEvent::Type(WM_TryRelease)), destroying(destroy) { }
bool destroying;
};
@@ -65,7 +48,7 @@ class QSGSoftwareSyncEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareSyncEvent(QQuickWindow *c, bool inExpose, bool force)
- : QSGSoftwareWindowEvent(c, WM_RequestSync)
+ : QSGSoftwareWindowEvent(c, QEvent::Type(WM_RequestSync))
, size(c->size())
, dpr(c->effectiveDevicePixelRatio())
, syncInExpose(inExpose)
@@ -80,7 +63,7 @@ class QSGSoftwareGrabEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareGrabEvent(QQuickWindow *c, QImage *result)
- : QSGSoftwareWindowEvent(c, WM_Grab), image(result) { }
+ : QSGSoftwareWindowEvent(c, QEvent::Type(WM_Grab)), image(result) { }
QImage *image;
};
@@ -88,7 +71,7 @@ class QSGSoftwareJobEvent : public QSGSoftwareWindowEvent
{
public:
QSGSoftwareJobEvent(QQuickWindow *c, QRunnable *postedJob)
- : QSGSoftwareWindowEvent(c, WM_PostJob), job(postedJob) { }
+ : QSGSoftwareWindowEvent(c, QEvent::Type(WM_PostJob)), job(postedJob) { }
~QSGSoftwareJobEvent() { delete job; }
QRunnable *job;
};
@@ -516,11 +499,11 @@ void QSGSoftwareRenderThread::syncAndRender()
QQuickProfiler::SceneGraphRenderLoopSwap);
}
-template<class T> T *windowFor(const QVector<T> &list, QQuickWindow *window)
+QSGSoftwareThreadedRenderLoop::WindowData *QSGSoftwareThreadedRenderLoop::windowFor(QQuickWindow *window)
{
- for (const T &t : list) {
+ for (const auto &t : std::as_const(m_windows)) {
if (t.window == window)
- return const_cast<T *>(&t);
+ return const_cast<WindowData *>(&t);
}
return nullptr;
}
@@ -552,7 +535,7 @@ void QSGSoftwareThreadedRenderLoop::hide(QQuickWindow *window)
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "hide" << window;
if (window->isExposed())
- handleObscurity(windowFor(m_windows, window));
+ handleObscurity(windowFor(window));
releaseResources(window);
}
@@ -569,7 +552,7 @@ void QSGSoftwareThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "window destroyed" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (!w)
return;
@@ -603,7 +586,7 @@ void QSGSoftwareThreadedRenderLoop::exposureChanged(QQuickWindow *window)
if (window->isExposed()) {
handleExposure(window);
} else {
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
handleObscurity(w);
}
@@ -613,13 +596,13 @@ QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "grab" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
// Have to support invisible (but created()'ed) windows as well.
// Unlike with GL, leaving that case for QQuickWindow to handle is not feasible.
const bool tempExpose = !w;
if (tempExpose) {
handleExposure(window);
- w = windowFor(m_windows, window);
+ w = windowFor(window);
Q_ASSERT(w);
}
@@ -650,7 +633,7 @@ QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window)
void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window)
{
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (!w)
return;
@@ -667,7 +650,7 @@ void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window)
void QSGSoftwareThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
{
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
scheduleUpdate(w);
}
@@ -676,7 +659,7 @@ void QSGSoftwareThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleUpdateRequest" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
polishAndSync(w, false);
}
@@ -700,14 +683,14 @@ void QSGSoftwareThreadedRenderLoop::releaseResources(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "releaseResources" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w)
handleResourceRelease(w, false);
}
void QSGSoftwareThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
{
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (w && w->thread && w->thread->exposedWindow)
w->thread->postEvent(new QSGSoftwareJobEvent(window, job));
else
@@ -791,7 +774,7 @@ void QSGSoftwareThreadedRenderLoop::handleExposure(QQuickWindow *window)
{
qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleExposure" << window;
- WindowData *w = windowFor(m_windows, window);
+ WindowData *w = windowFor(window);
if (!w) {
qCDebug(QSG_RASTER_LOG_RENDERLOOP, "adding window to list");
WindowData win;
@@ -851,7 +834,7 @@ void QSGSoftwareThreadedRenderLoop::handleObscurity(QSGSoftwareThreadedRenderLoo
if (w->thread->isRunning()) {
w->thread->mutex.lock();
- w->thread->postEvent(new QSGSoftwareWindowEvent(w->window, WM_Obscure));
+ w->thread->postEvent(new QSGSoftwareWindowEvent(w->window, QEvent::Type(WM_Obscure)));
w->thread->waitCondition.wait(&w->thread->mutex);
w->thread->mutex.unlock();
}
@@ -921,7 +904,7 @@ void QSGSoftwareThreadedRenderLoop::polishAndSync(QSGSoftwareThreadedRenderLoop:
// Flush pending touch events.
QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
// The delivery of the event might have caused the window to stop rendering
- w = windowFor(m_windows, window);
+ w = windowFor(window);
if (!w || !w->thread || !w->thread->exposedWindow) {
qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - removed after touch event flushing, abort");
return;
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h
index adb3c401db..02adb28ab3 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop_p.h
@@ -61,6 +61,8 @@ private:
uint forceRenderPass : 1;
};
+ WindowData *windowFor(QQuickWindow *window);
+
void startOrStopAnimationTimer();
void handleExposure(QQuickWindow *window);
void handleObscurity(WindowData *w);
@@ -72,7 +74,7 @@ private:
QAnimationDriver *m_anim;
int animationTimer = 0;
bool lockedForSync = false;
- QVector<WindowData> m_windows;
+ QList<WindowData> m_windows;
friend class QSGSoftwareRenderThread;
};
diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp
index 0e33312a9e..629c6427b8 100644
--- a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp
+++ b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp
@@ -6,7 +6,7 @@
#include <QDebug>
#include <QtQuick/private/qquickwindow_p.h>
#include <QtQuick/private/qquickitem_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h
index a9ff902e6e..eef95a3fee 100644
--- a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h
+++ b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h
@@ -18,7 +18,7 @@
#include <private/qtexturefiledata_p.h>
#include <private/qsgcontext_p.h>
#include <private/qsgtexture_p.h>
-#include <private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <QQuickTextureFactory>
#include <QOpenGLFunctions>
@@ -26,7 +26,7 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TEXTUREIO);
-class Q_QUICK_PRIVATE_EXPORT QSGCompressedTexture : public QSGTexture
+class Q_QUICK_EXPORT QSGCompressedTexture : public QSGTexture
{
Q_OBJECT
public:
@@ -63,7 +63,7 @@ namespace QSGOpenGLAtlasTexture {
class Manager;
}
-class Q_QUICK_PRIVATE_EXPORT QSGCompressedTextureFactory : public QQuickTextureFactory
+class Q_QUICK_EXPORT QSGCompressedTextureFactory : public QQuickTextureFactory
{
public:
QSGCompressedTextureFactory(const QTextureFileData& texData);
diff --git a/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp b/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp
index a51521eeaa..ffa5599231 100644
--- a/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgabstractrenderer.cpp
@@ -14,19 +14,6 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \enum QSGAbstractRenderer::ClearModeBit
-
- Used with setClearMode() to indicate which buffer should
- be cleared before the scene render.
-
- \value ClearColorBuffer Clear the color buffer using clearColor().
- \value ClearDepthBuffer Clear the depth buffer.
- \value ClearStencilBuffer Clear the stencil buffer.
-
- \sa setClearMode(), setClearColor()
- */
-
-/*!
\enum QSGAbstractRenderer::MatrixTransformFlag
Used with setProjectionMatrixToRect() to indicate the expectations towards
@@ -61,8 +48,9 @@ QT_BEGIN_NAMESPACE
QSGAbstractRendererPrivate::QSGAbstractRendererPrivate()
: m_root_node(nullptr)
, m_clear_color(Qt::transparent)
- , m_clear_mode(QSGAbstractRenderer::ClearColorBuffer | QSGAbstractRenderer::ClearDepthBuffer)
{
+ m_projection_matrix.resize(1);
+ m_projection_matrix_native_ndc.resize(1);
}
/*!
@@ -193,15 +181,7 @@ QRect QSGAbstractRenderer::viewportRect() const
*/
void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect)
{
- QMatrix4x4 matrix;
- matrix.ortho(rect.x(),
- rect.x() + rect.width(),
- rect.y() + rect.height(),
- rect.y(),
- 1,
- -1);
- setProjectionMatrix(matrix);
- setProjectionMatrixWithNativeNDC(matrix);
+ setProjectionMatrixToRect(rect, {}, false);
}
/*!
@@ -217,46 +197,77 @@ void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect)
*/
void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags)
{
+ setProjectionMatrixToRect(rect, flags, flags.testFlag(MatrixTransformFlipY));
+}
+
+/*!
+ Convenience method that calls setProjectionMatrix() with an
+ orthographic matrix generated from \a rect.
+
+ Set MatrixTransformFlipY in \a flags when the graphics API uses Y down in
+ its normalized device coordinate system (for example, Vulkan).
+
+ Convenience method that calls setProjectionMatrixWithNativeNDC() with an
+ orthographic matrix generated from \a rect.
+
+ Set true to \a nativeNDCFlipY to flip the Y axis relative to
+ projection matrix in its normalized device coordinate system.
+
+ \sa setProjectionMatrix(), projectionMatrix()
+ \sa setProjectionMatrixWithNativeNDC(), projectionMatrixWithNativeNDC()
+
+ \since 6.7
+ */
+void QSGAbstractRenderer::setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags,
+ bool nativeNDCFlipY)
+{
const bool flipY = flags.testFlag(MatrixTransformFlipY);
+
+ const float left = rect.x();
+ const float right = rect.x() + rect.width();
+ float bottom = rect.y() + rect.height();
+ float top = rect.y();
+
+ if (flipY)
+ std::swap(top, bottom);
+
QMatrix4x4 matrix;
- matrix.ortho(rect.x(),
- rect.x() + rect.width(),
- flipY ? rect.y() : rect.y() + rect.height(),
- flipY ? rect.y() + rect.height() : rect.y(),
- 1,
- -1);
- setProjectionMatrix(matrix);
-
- if (flipY) {
+ matrix.ortho(left, right, bottom, top, 1, -1);
+ setProjectionMatrix(matrix, 0);
+
+ if (nativeNDCFlipY) {
+ std::swap(top, bottom);
+
matrix.setToIdentity();
- matrix.ortho(rect.x(),
- rect.x() + rect.width(),
- rect.y() + rect.height(),
- rect.y(),
- 1,
- -1);
+ matrix.ortho(left, right, bottom, top, 1, -1);
}
- setProjectionMatrixWithNativeNDC(matrix);
+ setProjectionMatrixWithNativeNDC(matrix, 0);
}
/*!
Use \a matrix to project the QSGNode coordinates onto surface pixels.
+ \a index specifies the view index when multiview rendering is in use.
+
\sa projectionMatrix(), setProjectionMatrixToRect()
*/
-void QSGAbstractRenderer::setProjectionMatrix(const QMatrix4x4 &matrix)
+void QSGAbstractRenderer::setProjectionMatrix(const QMatrix4x4 &matrix, int index)
{
Q_D(QSGAbstractRenderer);
- d->m_projection_matrix = matrix;
+ if (d->m_projection_matrix.count() <= index)
+ d->m_projection_matrix.resize(index + 1);
+ d->m_projection_matrix[index] = matrix;
}
/*!
\internal
*/
-void QSGAbstractRenderer::setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix)
+void QSGAbstractRenderer::setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix, int index)
{
Q_D(QSGAbstractRenderer);
- d->m_projection_matrix_native_ndc = matrix;
+ if (d->m_projection_matrix_native_ndc.count() <= index)
+ d->m_projection_matrix_native_ndc.resize(index + 1);
+ d->m_projection_matrix_native_ndc[index] = matrix;
}
/*!
@@ -264,67 +275,54 @@ void QSGAbstractRenderer::setProjectionMatrixWithNativeNDC(const QMatrix4x4 &mat
\sa setProjectionMatrix(), setProjectionMatrixToRect()
*/
-QMatrix4x4 QSGAbstractRenderer::projectionMatrix() const
+QMatrix4x4 QSGAbstractRenderer::projectionMatrix(int index) const
{
Q_D(const QSGAbstractRenderer);
- return d->m_projection_matrix;
+ return d->m_projection_matrix[index];
}
-/*!
- \internal
- */
-QMatrix4x4 QSGAbstractRenderer::projectionMatrixWithNativeNDC() const
+int QSGAbstractRenderer::projectionMatrixCount() const
{
Q_D(const QSGAbstractRenderer);
- return d->m_projection_matrix_native_ndc;
+ return d->m_projection_matrix.count();
}
-/*!
- Use \a color to clear the framebuffer when clearMode() is
- set to QSGAbstractRenderer::ClearColorBuffer.
-
- \sa clearColor(), setClearMode()
- */
-void QSGAbstractRenderer::setClearColor(const QColor &color)
+int QSGAbstractRenderer::projectionMatrixWithNativeNDCCount() const
{
- Q_D(QSGAbstractRenderer);
- d->m_clear_color = color;
+ Q_D(const QSGAbstractRenderer);
+ return d->m_projection_matrix_native_ndc.count();
}
/*!
- Returns the color that clears the framebuffer at the beginning
- of the rendering.
-
- \sa setClearColor(), clearMode()
+ \internal
*/
-QColor QSGAbstractRenderer::clearColor() const
+QMatrix4x4 QSGAbstractRenderer::projectionMatrixWithNativeNDC(int index) const
{
Q_D(const QSGAbstractRenderer);
- return d->m_clear_color;
+ return d->m_projection_matrix_native_ndc[index];
}
/*!
- Defines which attachment of the framebuffer should be cleared
- before each scene render with the \a mode flag.
+ Sets the \a color to clear the framebuffer.
- \sa clearMode(), setClearColor()
+ \sa clearColor()
*/
-void QSGAbstractRenderer::setClearMode(ClearMode mode)
+void QSGAbstractRenderer::setClearColor(const QColor &color)
{
Q_D(QSGAbstractRenderer);
- d->m_clear_mode = mode;
+ d->m_clear_color = color;
}
/*!
- Flags defining which attachment of the framebuffer will be cleared
- before each scene render.
+ Returns the color that clears the framebuffer at the beginning
+ of the rendering.
- \sa setClearMode(), clearColor()
+ \sa setClearColor()
*/
-QSGAbstractRenderer::ClearMode QSGAbstractRenderer::clearMode() const
+QColor QSGAbstractRenderer::clearColor() const
{
Q_D(const QSGAbstractRenderer);
- return d->m_clear_mode;
+ return d->m_clear_color;
}
/*!
diff --git a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h
index ec2cb66662..5d7a3b13b6 100644
--- a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h
+++ b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p.h
@@ -27,19 +27,10 @@ QT_BEGIN_NAMESPACE
class QSGAbstractRendererPrivate;
-class Q_QUICK_PRIVATE_EXPORT QSGAbstractRenderer : public QObject
+class Q_QUICK_EXPORT QSGAbstractRenderer : public QObject
{
Q_OBJECT
public:
- enum ClearModeBit
- {
- ClearColorBuffer = 0x0001,
- ClearDepthBuffer = 0x0002,
- ClearStencilBuffer = 0x0004
- };
- Q_DECLARE_FLAGS(ClearMode, ClearModeBit)
- Q_FLAG(ClearMode)
-
enum MatrixTransformFlag
{
MatrixTransformFlipY = 0x01
@@ -61,17 +52,18 @@ public:
void setProjectionMatrixToRect(const QRectF &rect);
void setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags);
- void setProjectionMatrix(const QMatrix4x4 &matrix);
- void setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix);
- QMatrix4x4 projectionMatrix() const;
- QMatrix4x4 projectionMatrixWithNativeNDC() const;
+ void setProjectionMatrixToRect(const QRectF &rect, MatrixTransformFlags flags,
+ bool nativeNDCFlipY);
+ void setProjectionMatrix(const QMatrix4x4 &matrix, int index = 0);
+ void setProjectionMatrixWithNativeNDC(const QMatrix4x4 &matrix, int index = 0);
+ QMatrix4x4 projectionMatrix(int index) const;
+ QMatrix4x4 projectionMatrixWithNativeNDC(int index) const;
+ int projectionMatrixCount() const;
+ int projectionMatrixWithNativeNDCCount() const;
void setClearColor(const QColor &color);
QColor clearColor() const;
- void setClearMode(ClearMode mode);
- ClearMode clearMode() const;
-
virtual void renderScene() = 0;
virtual void prepareSceneInline();
@@ -89,8 +81,6 @@ private:
friend class QSGRootNode;
};
-Q_DECLARE_OPERATORS_FOR_FLAGS(QSGAbstractRenderer::ClearMode)
-
QT_END_NAMESPACE
#endif
diff --git a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h
index afac489a6a..3bc04247db 100644
--- a/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h
+++ b/src/quick/scenegraph/coreapi/qsgabstractrenderer_p_p.h
@@ -25,7 +25,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGAbstractRendererPrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QSGAbstractRendererPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QSGAbstractRenderer)
public:
@@ -36,13 +36,12 @@ public:
QSGRootNode *m_root_node;
QColor m_clear_color;
- QSGAbstractRenderer::ClearMode m_clear_mode;
QRect m_device_rect;
QRect m_viewport_rect;
- QMatrix4x4 m_projection_matrix;
- QMatrix4x4 m_projection_matrix_native_ndc;
+ QVarLengthArray<QMatrix4x4, 1> m_projection_matrix;
+ QVarLengthArray<QMatrix4x4, 1> m_projection_matrix_native_ndc;
uint m_mirrored : 1;
};
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
index cbf459555f..6c084ec441 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
#ifndef QT_NO_DEBUG
-Q_QUICK_PRIVATE_EXPORT bool qsg_test_and_clear_material_failure();
+Q_QUICK_EXPORT bool qsg_test_and_clear_material_failure();
#endif
int qt_sg_envInt(const char *name, int defaultValue);
@@ -201,13 +201,22 @@ QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode)
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)
+ QSGRendererInterface::RenderMode renderMode,
+ int multiViewCount)
{
- QSGMaterialType *type = material->type();
+ qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
- ShaderKey key = qMakePair(type, renderMode);
+ QSGMaterialType *type = material->type();
+ ShaderKey key = { type, renderMode, multiViewCount };
Shader *shader = rewrittenShaders.value(key, nullptr);
if (shader)
return shader;
@@ -219,8 +228,8 @@ ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
shader->stages = {
- { QRhiGraphicsShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
- { QRhiGraphicsShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
+ { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
+ { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
};
shader->lastOpacity = 0;
@@ -231,11 +240,13 @@ ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
const QSGGeometry *geometry,
- QSGRendererInterface::RenderMode renderMode)
+ QSGRendererInterface::RenderMode renderMode,
+ int multiViewCount)
{
- QSGMaterialType *type = material->type();
+ qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
- ShaderKey key = qMakePair(type, renderMode);
+ QSGMaterialType *type = material->type();
+ ShaderKey key = { type, renderMode, multiViewCount };
Shader *shader = stockShaders.value(key, nullptr);
if (shader)
return shader;
@@ -247,8 +258,8 @@ ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *mate
shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
shader->stages = {
- { QRhiGraphicsShaderStage::Vertex, sD->shader(QShader::VertexStage) },
- { QRhiGraphicsShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
+ { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
+ { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
};
shader->lastOpacity = 0;
@@ -696,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;
}
@@ -839,6 +850,8 @@ Renderer::Renderer(QSGDefaultRenderContext *ctx, QSGRendererInterface::RenderMod
, 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)
@@ -915,6 +928,10 @@ Renderer::~Renderer()
qsg_wipeBatch(m_alphaBatches.at(i));
for (int i = 0; i < m_batchPool.size(); ++i)
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 : std::as_const(m_nodes)) {
@@ -960,12 +977,28 @@ void Renderer::releaseCachedResources()
m_rhi->releaseCachedResources();
- m_vertexUploadPool.resize(0);
- m_indexUploadPool.resize(0);
+ 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))
@@ -994,8 +1027,9 @@ void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
{
// Batches are pooled and reused which means the QRhiBuffer will be
// still valid in a recycled Batch. We only hit the newBuffer() path
- // for brand new Batches.
- if (!buffer->buf) {
+ // 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);
@@ -1005,6 +1039,28 @@ void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
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 (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);
@@ -1220,8 +1276,13 @@ void Renderer::nodeWasRemoved(Node *node)
if (e) {
e->removed = true;
m_elementsToDelete.add(e);
- if (m_renderNodeElements.isEmpty())
+ if (m_renderNodeElements.isEmpty()) {
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;
@@ -1680,6 +1741,7 @@ void Renderer::prepareOpaqueBatches()
&& 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;
@@ -1791,6 +1853,7 @@ void Renderer::prepareAlphaBatches()
&& 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;
@@ -1994,7 +2057,7 @@ void Renderer::uploadBatch(Batch *b)
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->indexType() == QSGGeometry::UnsignedShortType && g->indexCount() > 0)
&& (flags & (QSGMaterial::NoBatching | QSGMaterial_FullMatrix)) == 0
&& ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot())
&& b->isSafeToBatch();
@@ -2243,8 +2306,10 @@ QRhiGraphicsPipeline *Renderer::buildStencilPipeline(const Batch *batch, bool fi
ps->setTopology(m_stencilClipCommon.topology);
- ps->setShaderStages({ QRhiGraphicsShaderStage(QRhiGraphicsShaderStage::Vertex, m_stencilClipCommon.vs),
- QRhiGraphicsShaderStage(QRhiGraphicsShaderStage::Fragment, m_stencilClipCommon.fs) });
+ ps->setMultiViewCount(renderTarget().multiViewCount);
+
+ 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);
@@ -2291,7 +2356,7 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch)
const quint32 StencilClipUbufSize = 64;
while (clip) {
- QMatrix4x4 m = m_current_projection_matrix_native_ndc;
+ QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
if (clip->matrix())
m *= *clip->matrix();
@@ -2462,7 +2527,7 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch)
drawCall.ubufOffset = aligned(uOffset, m_ubufAlignment);
uOffset = drawCall.ubufOffset + StencilClipUbufSize;
- QMatrix4x4 matrixYUpNDC = m_current_projection_matrix;
+ QMatrix4x4 matrixYUpNDC = m_current_projection_matrix[0];
if (clip->matrix())
matrixYUpNDC *= *clip->matrix();
@@ -2636,6 +2701,7 @@ bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms,
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;
@@ -2644,6 +2710,8 @@ bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms,
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);
@@ -2775,6 +2843,7 @@ static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineS
// 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));
@@ -2792,6 +2861,9 @@ static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineS
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);
@@ -2811,6 +2883,8 @@ static void materialToRendererGraphicsState(GraphicsState *dst,
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);
@@ -3079,16 +3153,24 @@ bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *rende
else
m_current_model_view_matrix.setToIdentity();
m_current_determinant = m_current_model_view_matrix.determinant();
- m_current_projection_matrix = projectionMatrix();
- m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
+
+ 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();
if (m_renderMode != QSGRendererInterface::RenderMode3D)
updateClipState(gn->clipList(), batch);
const QSGGeometry *g = gn->geometry();
- ShaderManager::Shader *sms = useDepthBuffer() ? m_shaderManager->prepareMaterial(material, g, m_renderMode)
- : m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode);
+ 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 false;
@@ -3252,8 +3334,14 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren
batch->uploadedThisFrame = false;
}
- m_current_projection_matrix = projectionMatrix();
- m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
+ 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);
QSGGeometryNode *gn = e->node;
if (m_renderMode != QSGRendererInterface::RenderMode3D)
@@ -3266,7 +3354,7 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren
// 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, g);
+ ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode, renderTarget().multiViewCount);
if (!sms)
return false;
@@ -3326,11 +3414,19 @@ bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *ren
m_current_model_view_matrix = rootMatrix * *gn->matrix();
m_current_determinant = m_current_model_view_matrix.determinant();
- m_current_projection_matrix = projectionMatrix();
- m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
+ 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()) {
- m_current_projection_matrix(2, 2) = m_zRange;
- m_current_projection_matrix(2, 3) = calculateElementZOrder(e, m_zRange);
+ // this cannot be multiview
+ m_current_projection_matrix[0](2, 2) = m_zRange;
+ m_current_projection_matrix[0](2, 3) = calculateElementZOrder(e, m_zRange);
}
QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
@@ -3664,14 +3760,14 @@ void Renderer::prepareRenderPass(RenderPassContext *ctx)
if (Q_UNLIKELY(debug_render())) ctx->timeSorting = ctx->timer.restart();
- quint32 largestVBO = 0;
- quint32 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())) ctx->timeUploadOpaque = ctx->timer.restart();
@@ -3680,14 +3776,9 @@ void Renderer::prepareRenderPass(RenderPassContext *ctx)
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())) ctx->timeUploadAlpha = ctx->timer.restart();
- m_vertexUploadPool.resize(largestVBO);
- m_indexUploadPool.resize(largestIBO);
-
if (Q_UNLIKELY(debug_render())) {
qDebug().nospace() << "Rendering:" << Qt::endl
<< " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
@@ -3728,6 +3819,7 @@ void Renderer::prepareRenderPass(RenderPassContext *ctx)
m_gstate.stencilTest = false;
m_gstate.sampleCount = renderTarget().rt->sampleCount();
+ m_gstate.multiViewCount = renderTarget().multiViewCount;
ctx->opaqueRenderBatches.clear();
if (Q_LIKELY(renderOpaque)) {
@@ -3825,6 +3917,8 @@ void Renderer::recordRenderPass(RenderPassContext *ctx)
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);
@@ -3833,6 +3927,12 @@ void Renderer::recordRenderPass(RenderPassContext *ctx)
}
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);
@@ -3843,8 +3943,15 @@ void Renderer::recordRenderPass(RenderPassContext *ctx)
}
if (m_renderMode == QSGRendererInterface::RenderMode3D) {
- // depth post-pass
+ // 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);
@@ -3933,7 +4040,8 @@ bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBat
}
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;
@@ -3947,10 +4055,15 @@ bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBat
rd->m_rt = renderTarget();
- rd->m_projectionMatrix = projectionMatrix();
+ const int viewCount = projectionMatrixCount();
+ rd->m_projectionMatrix.resize(viewCount);
+ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
+ rd->m_projectionMatrix[viewIndex] = projectionMatrix(viewIndex);
+
if (useDepthBuffer()) {
- rd->m_projectionMatrix(2, 2) = m_zRange;
- rd->m_projectionMatrix(2, 3) = calculateElementZOrder(e, m_zRange);
+ // this cannot be multiview
+ rd->m_projectionMatrix[0](2, 2) = m_zRange;
+ rd->m_projectionMatrix[0](2, 3) = calculateElementZOrder(e, m_zRange);
}
e->renderNode->prepare();
@@ -3970,7 +4083,9 @@ void Renderer::renderRhiRenderNode(const Batch *batch)
QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(e->renderNode);
RenderNodeState state;
- state.m_projectionMatrix = &rd->m_projectionMatrix;
+ // 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;
@@ -4034,6 +4149,8 @@ bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
&& 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
@@ -4041,7 +4158,8 @@ bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
&& a.sampleCount == b.sampleCount
&& a.drawMode == b.drawMode
&& a.lineWidth == b.lineWidth
- && a.polygonMode == b.polygonMode;
+ && a.polygonMode == b.polygonMode
+ && a.multiViewCount == b.multiViewCount;
}
bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
@@ -4061,7 +4179,8 @@ size_t qHash(const GraphicsState &s, size_t seed) noexcept
+ s.cullMode
+ s.usesScissor
+ s.stencilTest
- + s.sampleCount;
+ + s.sampleCount
+ + s.multiViewCount;
}
bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
@@ -4085,6 +4204,23 @@ size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
^ 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;
+}
+
Visualizer::Visualizer(Renderer *renderer)
: m_renderer(renderer),
m_visualizeMode(VisualizeNothing)
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
index 9a024804a7..11cec6b99b 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
@@ -27,7 +27,7 @@
#include <QtCore/QBitArray>
#include <QtCore/QStack>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -599,6 +599,8 @@ struct GraphicsState
QRhiGraphicsPipeline::BlendFactor dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
QRhiGraphicsPipeline::BlendFactor srcAlpha = QRhiGraphicsPipeline::One;
QRhiGraphicsPipeline::BlendFactor dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
+ QRhiGraphicsPipeline::BlendOp opColor = QRhiGraphicsPipeline::Add;
+ QRhiGraphicsPipeline::BlendOp opAlpha = QRhiGraphicsPipeline::Add;
QRhiGraphicsPipeline::ColorMask colorWrite = QRhiGraphicsPipeline::ColorMask(0xF);
QRhiGraphicsPipeline::CullMode cullMode = QRhiGraphicsPipeline::None;
bool usesScissor = false;
@@ -607,6 +609,7 @@ struct GraphicsState
QSGGeometry::DrawingMode drawMode = QSGGeometry::DrawTriangles;
float lineWidth = 1.0f;
QRhiGraphicsPipeline::PolygonMode polygonMode = QRhiGraphicsPipeline::Fill;
+ int multiViewCount = 0;
};
bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept;
@@ -640,6 +643,17 @@ bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKe
bool operator!=(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept;
size_t qHash(const GraphicsPipelineStateKey &k, size_t seed = 0) noexcept;
+struct ShaderKey
+{
+ QSGMaterialType *type;
+ QSGRendererInterface::RenderMode renderMode;
+ int multiViewCount;
+};
+
+bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept;
+bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept;
+size_t qHash(const ShaderKey &k, size_t seed = 0) noexcept;
+
struct ShaderManagerShader
{
~ShaderManagerShader() {
@@ -647,7 +661,7 @@ struct ShaderManagerShader
}
QSGMaterialShader *materialShader = nullptr;
QRhiVertexInputLayout inputLayout;
- QVarLengthArray<QRhiGraphicsShaderStage, 2> stages;
+ QVarLengthArray<QRhiShaderStage, 2> stages;
float lastOpacity;
};
@@ -674,11 +688,16 @@ public Q_SLOTS:
void invalidated();
public:
- Shader *prepareMaterial(QSGMaterial *material, const QSGGeometry *geometry = nullptr, QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D);
- Shader *prepareMaterialNoRewrite(QSGMaterial *material, const QSGGeometry *geometry = nullptr, QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D);
+ Shader *prepareMaterial(QSGMaterial *material,
+ const QSGGeometry *geometry = nullptr,
+ QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D,
+ int multiViewCount = 0);
+ Shader *prepareMaterialNoRewrite(QSGMaterial *material,
+ const QSGGeometry *geometry = nullptr,
+ QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D,
+ int multiViewCount = 0);
private:
- typedef QPair<QSGMaterialType *, QSGRendererInterface::RenderMode> ShaderKey;
QHash<ShaderKey, Shader *> rewrittenShaders;
QHash<ShaderKey, Shader *> stockShaders;
@@ -723,7 +742,7 @@ protected:
QHash<Node *, uint> m_visualizeChangeSet;
};
-class Q_QUICK_PRIVATE_EXPORT Renderer : public QSGRenderer
+class Q_QUICK_EXPORT Renderer : public QSGRenderer
{
public:
Renderer(QSGDefaultRenderContext *ctx, QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D);
@@ -858,6 +877,9 @@ private:
QDataBuffer<Element *> m_tmpAlphaElements;
QDataBuffer<Element *> m_tmpOpaqueElements;
+ QDataBuffer<QRhiBuffer *> m_vboPool;
+ QDataBuffer<QRhiBuffer *> m_iboPool;
+
uint m_rebuild;
qreal m_zRange;
#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
diff --git a/src/quick/scenegraph/coreapi/qsggeometry.cpp b/src/quick/scenegraph/coreapi/qsggeometry.cpp
index 32ba34587a..9541107b78 100644
--- a/src/quick/scenegraph/coreapi/qsggeometry.cpp
+++ b/src/quick/scenegraph/coreapi/qsggeometry.cpp
@@ -496,13 +496,18 @@ const void *QSGGeometry::indexData() const
Specifies the drawing mode, also called primitive topology.
+ \note Starting with Qt 6 the scene graph only exposes topologies that are
+ supported across all the supported 3D graphics APIs. As a result, the
+ values \c DrawLineLoop and \c DrawTriangleFan are no longer supported at
+ run time in Qt 6, even though the enum values themselves are still present.
+
\value DrawPoints
\value DrawLines
- \value DrawLineLoop
+ \omitvalue DrawLineLoop
\value DrawLineStrip
\value DrawTriangles
\value DrawTriangleStrip
- \value DrawTriangleFan
+ \omitvalue DrawTriangleFan
*/
/*!
@@ -536,10 +541,10 @@ void QSGGeometry::setDrawingMode(unsigned int mode)
}
/*!
- Gets the current line or point width or to be used for this
- geometry. This property only applies to line width when the drawingMode
- is DrawLines, DarwLineStrip, or DrawLineLoop. When supported, it also
- applies to point size when the drawingMode is DrawPoints.
+ Gets the current line or point width or to be used for this geometry. This
+ property only applies to line width when the drawingMode is DrawLines or
+ DrawLineStrip. When supported, it also applies to point size when the
+ drawingMode is DrawPoints.
The default value is \c 1.0
@@ -558,10 +563,10 @@ float QSGGeometry::lineWidth() const
}
/*!
- Sets the line or point width to be used for this geometry to \a
- width. This property only applies to line width when the drawingMode is
- DrawLines, DrawLineStrip, or DrawLineLoop. When supported, it also
- applies to point size when the drawingMode is DrawPoints.
+ Sets the line or point width to be used for this geometry to \a width. This
+ property only applies to line width when the drawingMode is DrawLines or
+ DrawLineStrip. When supported, it also applies to point size when the
+ drawingMode is DrawPoints.
\note Support for point and line drawing may be limited at run time,
depending on the platform and graphics API. For example, some APIs do
diff --git a/src/quick/scenegraph/coreapi/qsggeometry.h b/src/quick/scenegraph/coreapi/qsggeometry.h
index 0b4a7cdce1..7fff62a915 100644
--- a/src/quick/scenegraph/coreapi/qsggeometry.h
+++ b/src/quick/scenegraph/coreapi/qsggeometry.h
@@ -30,8 +30,6 @@ public:
StaticPattern = 3
};
- // Equivalents to GL_* drawing modes.
- // Keep in sync with GL headers.
enum DrawingMode {
DrawPoints = 0x0000,
DrawLines = 0x0001,
@@ -42,8 +40,6 @@ public:
DrawTriangleFan = 0x0006
};
- // Equivalents to GL_BYTE and similar type constants.
- // Keep in sync with GL headers.
enum Type {
ByteType = 0x1400,
UnsignedByteType = 0x1401,
diff --git a/src/quick/scenegraph/coreapi/qsgmaterial.cpp b/src/quick/scenegraph/coreapi/qsgmaterial.cpp
index 9c7cbe4d8d..e2b240479e 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterial.cpp
+++ b/src/quick/scenegraph/coreapi/qsgmaterial.cpp
@@ -6,6 +6,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQsgLeak)
+
#ifndef QT_NO_DEBUG
bool qsg_material_failure = false;
bool qsg_test_and_clear_material_failure()
@@ -21,10 +23,6 @@ void qsg_set_material_failure()
}
#endif
-#ifndef QT_NO_DEBUG
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
-#endif
-
/*!
\group qtquick-scenegraph-materials
\title Qt Quick Scene Graph Material Classes
@@ -39,7 +37,7 @@ static int qt_material_count = 0;
static void qt_print_material_count()
{
- qDebug("Number of leaked materials: %i", qt_material_count);
+ qCDebug(lcQsgLeak, "Number of leaked materials: %i", qt_material_count);
qt_material_count = -1;
}
#endif
@@ -109,7 +107,7 @@ QSGMaterial::QSGMaterial()
{
Q_UNUSED(m_reserved);
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
++qt_material_count;
static bool atexit_registered = false;
if (!atexit_registered) {
@@ -128,10 +126,10 @@ QSGMaterial::QSGMaterial()
QSGMaterial::~QSGMaterial()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
--qt_material_count;
if (qt_material_count < 0)
- qDebug("Material destroyed after qt_print_material_count() was called.");
+ qCDebug(lcQsgLeak, "Material destroyed after qt_print_material_count() was called.");
}
#endif
}
@@ -165,6 +163,10 @@ QSGMaterial::~QSGMaterial()
\value CustomCompileStep In Qt 6 this flag is identical to NoBatching. Prefer using
NoBatching instead.
+
+ \omitvalue MultiView2
+ \omitvalue MultiView3
+ \omitvalue MultiView4
*/
/*!
@@ -249,4 +251,186 @@ int QSGMaterial::compare(const QSGMaterial *other) const
RenderMode3D is in use.
*/
+/*!
+ \return The number of views in case of the material is used in multiview
+ rendering.
+
+ \note The return value is valid only when called from createShader(), and
+ afterwards. The value is not necessarily up-to-date before createShader()
+ is invokved by the scene graph.
+
+ Normally the return value is \c 1. A view count greater than 2 implies a
+ \e{multiview render pass}. Materials that support multiview are expected to
+ query viewCount() in createShader(), or in their QSGMaterialShader
+ constructor, and ensure the appropriate shaders are picked. The vertex
+ shader is then expected to use
+ \c{gl_ViewIndex} to index the modelview-projection matrix array as there
+ are multiple matrices in multiview mode. (one for each view)
+
+ As an example, take the following simple vertex shader:
+
+ \badcode
+ #version 440
+
+ layout(location = 0) in vec4 vertexCoord;
+ layout(location = 1) in vec4 vertexColor;
+
+ layout(location = 0) out vec4 color;
+
+ layout(std140, binding = 0) uniform buf {
+ mat4 matrix[2];
+ float opacity;
+ };
+
+ void main()
+ {
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+ color = vertexColor * opacity;
+ }
+ \endcode
+
+ This shader is prepared to handle 2 views, and 2 views only. It is not
+ compatible with other view counts. When conditioning the shader, the \c qsb
+ tool has to be invoked with \c{--view-count 2} or, if using the CMake
+ integration,
+ \c{VIEW_COUNT 2} must be specified in the \c{qt_add_shaders()} command.
+
+ \note A line with \c{#extension GL_EXT_multiview : require} is injected
+ automatically by \c qsb whenever a view count of 2 or greater is set.
+
+ Developers are encouraged to use the automatically injected preprocessor
+ variable \c{QSHADER_VIEW_COUNT} to simplify the handling of the different
+ number of views. For example, if there is a need to support both
+ non-multiview and multiview with a view count of 2 in the same source file,
+ the following could be done:
+
+ \badcode
+ #version 440
+
+ layout(location = 0) in vec4 vertexCoord;
+ layout(location = 1) in vec4 vertexColor;
+
+ layout(location = 0) out vec4 color;
+
+ layout(std140, binding = 0) uniform buf {
+ #if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+ #else
+ mat4 matrix;
+ #endif
+ float opacity;
+ };
+
+ void main()
+ {
+ #if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+ #else
+ gl_Position = matrix * vertexCoord;
+ #endif
+ color = vertexColor * opacity;
+ }
+ \endcode
+
+ The same source file can now be run through \c qsb or \c{qt_add_shaders()}
+ twice, once without specify the view count, and once with the view count
+ set to 2. The material can then pick the appropriate .qsb file based on
+ viewCount() at run time.
+
+ With CMake, this could looks similar to the following. With this example
+ the corresponding QSGMaterialShader is expected to choose between
+ \c{:/shaders/example.vert.qsb} and \c{:/shaders/multiview/example.vert.qsb}
+ based on the value of viewCount(). (same goes for the fragment shader)
+
+ \badcode
+ qt_add_shaders(application "application_shaders"
+ PREFIX
+ /
+ FILES
+ shaders/example.vert
+ shaders/example.frag
+ )
+
+ qt_add_shaders(application "application_multiview_shaders"
+ GLSL
+ 330,300es
+ HLSL
+ 61
+ MSL
+ 12
+ VIEW_COUNT
+ 2
+ PREFIX
+ /
+ FILES
+ shaders/example.vert
+ shaders/example.frag
+ OUTPUTS
+ shaders/multiview/example.vert
+ shaders/multiview/example.frag
+ )
+ \endcode
+
+ \note The fragment shader should be treated the same way the vertex shader
+ is, even though the fragment shader code cannot have any dependency on the
+ view count (\c{gl_ViewIndex}), for maximum portability. There are two
+ reasons for including fragment shaders too in the multiview set. One is that
+ mixing different shader versions within the same graphics pipeline can be
+ problematic, depending on the underlying graphics API: with D3D12 for
+ example, mixing HLSL shaders for shader model 5.0 and 6.1 would generate an
+ error. The other is that having \c QSHADER_VIEW_COUNT defined in fragment
+ shaders can be very useful, for example when sharing a uniform buffer layout
+ between the vertex and fragment stages.
+
+ \note For OpenGL the minimum GLSL version for vertex shaders relying on
+ \c{gl_ViewIndex} is \c 330. Lower versions may be accepted at build time,
+ but may lead to an error at run time, depending on the OpenGL implementation.
+
+ As a convenience, there is also a \c MULTIVIEW option for qt_add_shaders().
+ This first runs the \c qsb tool normally, then overrides \c VIEW_COUNT to
+ \c 2, sets \c GLSL, \c HLSL, \c MSL to some suitable defaults, and runs \c
+ qsb again, this time outputting .qsb files with a suffix added. The material
+ implementation can then use the \l QSGMaterialShader::setShaderFileName()
+ overload taking a \c viewCount argument, that automatically picks the
+ correct .qsb file.
+
+ The following is therefore mostly equivalent to the example call shown
+ above, except that no manually managed output files need to be specified.
+ Note that there can be cases when the automatically chosen shading language
+ versions are not sufficient, in which case applications should continue
+ specify everything explicitly.
+
+ \badcode
+ qt_add_shaders(application "application_multiview_shaders"
+ MULTIVIEW
+ PREFIX
+ /
+ FILES
+ shaders/example.vert
+ shaders/example.frag
+ )
+ \endcode
+
+ See \l QRhi::MultiView, \l QRhiColorAttachment::setMultiViewCount(), and
+ \l QRhiGraphicsPipeline::setMultiViewCount() for further, lower-level details
+ on multiview support in Qt. The Qt Quick scene graph renderer is prepared to
+ recognize multiview render targets, when specified via \l
+ QQuickRenderTarget::fromRhiRenderTarget() or the 3D API specific functions,
+ such as \l{QQuickRenderTarget::}{fromVulkanImage()} with an \c arraySize
+ argument greater than 1. The renderer will then propagate the view count to
+ graphics pipelines and the materials.
+
+ \since 6.8
+ */
+int QSGMaterial::viewCount() const
+{
+ if (m_flags.testFlag(MultiView4))
+ return 4;
+ if (m_flags.testFlag(MultiView3))
+ return 3;
+ if (m_flags.testFlag(MultiView2))
+ return 2;
+ return 1;
+}
+
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgmaterial.h b/src/quick/scenegraph/coreapi/qsgmaterial.h
index 29de92d53e..ca3f134a9d 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterial.h
+++ b/src/quick/scenegraph/coreapi/qsgmaterial.h
@@ -21,6 +21,10 @@ public:
RequiresFullMatrix = 0x0008 | RequiresFullMatrixExceptTranslate,
NoBatching = 0x0010,
+ MultiView2 = 0x10000,
+ MultiView3 = 0x20000,
+ MultiView4 = 0x40000,
+
#if QT_DEPRECATED_SINCE(6, 3)
CustomCompileStep Q_DECL_ENUMERATOR_DEPRECATED_X(
"Qt 6 does not have custom shader compilation support. If the intention is to just disable batching, use NoBatching instead."
@@ -40,6 +44,8 @@ public:
QSGMaterial::Flags flags() const { return m_flags; }
void setFlag(Flags flags, bool on = true);
+ int viewCount() const;
+
private:
Flags m_flags;
void *m_reserved;
diff --git a/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp b/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp
index b4090eb9cd..57090f73b3 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp
+++ b/src/quick/scenegraph/coreapi/qsgmaterialshader.cpp
@@ -208,8 +208,8 @@ void QSGMaterialShaderPrivate::prepare(QShader::Variant vertexShaderVariant)
clearCachedRendererData();
for (QShader::Stage stage : { QShader::VertexStage, QShader::FragmentStage }) {
- auto it = shaderFileNames.find(stage);
- if (it != shaderFileNames.end()) {
+ auto it = shaderFileNames.constFind(stage);
+ if (it != shaderFileNames.cend()) {
QString fn = *it;
const QShader s = loadShader(*it);
if (!s.isValid())
@@ -361,6 +361,33 @@ void QSGMaterialShader::setShaderFileName(Stage stage, const QString &filename)
}
/*!
+ Sets the \a filename for the shader for the specified \a stage.
+
+ The file is expected to contain a serialized QShader.
+
+ This overload is used when enabling \l{QSGMaterial::viewCount()}{multiview}
+ rendering, in particular when the \l{Qt Shader Tools Build System
+ Integration}{build system's MULTIVIEW convenience option} is used.
+
+ \a viewCount should be 2, 3, or 4. The \a filename is adjusted automatically
+ based on this.
+
+ \since 6.8
+ */
+void QSGMaterialShader::setShaderFileName(Stage stage, const QString &filename, int viewCount)
+{
+ Q_D(QSGMaterialShader);
+ if (viewCount == 2)
+ d->shaderFileNames[toShaderStage(stage)] = filename + QStringLiteral(".mv2qsb");
+ else if (viewCount == 3)
+ d->shaderFileNames[toShaderStage(stage)] = filename + QStringLiteral(".mv3qsb");
+ else if (viewCount == 4)
+ d->shaderFileNames[toShaderStage(stage)] = filename + QStringLiteral(".mv4qsb");
+ else
+ d->shaderFileNames[toShaderStage(stage)] = filename;
+}
+
+/*!
\return the currently set flags for this material shader.
*/
QSGMaterialShader::Flags QSGMaterialShader::flags() const
@@ -634,6 +661,17 @@ bool QSGMaterialShader::updateGraphicsPipelineState(RenderState &state, Graphics
*/
/*!
+ \enum QSGMaterialShader::GraphicsPipelineState::BlendOp
+ \since 6.8
+
+ \value BlendOpAdd
+ \value BlendOpSubtract
+ \value BlendOpReverseSubtract
+ \value BlendOpMin
+ \value BlendOpMax
+ */
+
+/*!
\enum QSGMaterialShader::GraphicsPipelineState::ColorMaskComponent
\since 5.14
@@ -745,6 +783,18 @@ bool QSGMaterialShader::updateGraphicsPipelineState(RenderState &state, Graphics
*/
/*!
+ \variable QSGMaterialShader::GraphicsPipelineState::opColor
+ \since 6.8
+ \brief RGB blending operation.
+ */
+
+/*!
+ \variable QSGMaterialShader::GraphicsPipelineState::opAlpha
+ \since 6.8
+ \brief Alpha blending operation.
+ */
+
+/*!
Returns the accumulated opacity to be used for rendering.
*/
float QSGMaterialShader::RenderState::opacity() const
@@ -768,7 +818,16 @@ float QSGMaterialShader::RenderState::determinant() const
QMatrix4x4 QSGMaterialShader::RenderState::combinedMatrix() const
{
Q_ASSERT(m_data);
- return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix();
+ return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix(0);
+}
+
+/*!
+ \internal
+ */
+QMatrix4x4 QSGMaterialShader::RenderState::combinedMatrix(int index) const
+{
+ Q_ASSERT(m_data);
+ return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix(index);
}
/*!
@@ -807,7 +866,25 @@ QMatrix4x4 QSGMaterialShader::RenderState::modelViewMatrix() const
QMatrix4x4 QSGMaterialShader::RenderState::projectionMatrix() const
{
Q_ASSERT(m_data);
- return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix();
+ return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix(0);
+}
+
+/*!
+ \internal
+ */
+QMatrix4x4 QSGMaterialShader::RenderState::projectionMatrix(int index) const
+{
+ Q_ASSERT(m_data);
+ return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix(index);
+}
+
+/*!
+ \internal
+ */
+int QSGMaterialShader::RenderState::projectionMatrixCount() const
+{
+ Q_ASSERT(m_data);
+ return static_cast<const QSGRenderer *>(m_data)->projectionMatrixCount();
}
/*!
diff --git a/src/quick/scenegraph/coreapi/qsgmaterialshader.h b/src/quick/scenegraph/coreapi/qsgmaterialshader.h
index caf44942cf..85ebf6eb8a 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterialshader.h
+++ b/src/quick/scenegraph/coreapi/qsgmaterialshader.h
@@ -40,8 +40,11 @@ public:
float opacity() const;
QMatrix4x4 combinedMatrix() const;
+ QMatrix4x4 combinedMatrix(int index) const;
QMatrix4x4 modelViewMatrix() const;
QMatrix4x4 projectionMatrix() const;
+ QMatrix4x4 projectionMatrix(int index) const;
+ int projectionMatrixCount() const;
QRect viewportRect() const;
QRect deviceRect() const;
float determinant() const;
@@ -57,7 +60,7 @@ public:
const void *m_data;
};
- struct Q_QUICK_EXPORT GraphicsPipelineState {
+ struct GraphicsPipelineState {
enum BlendFactor {
Zero,
One,
@@ -80,6 +83,14 @@ public:
OneMinusSrc1Alpha
};
+ enum BlendOp {
+ BlendOpAdd,
+ BlendOpSubtract,
+ BlendOpReverseSubtract,
+ BlendOpMin,
+ BlendOpMax
+ };
+
enum ColorMaskComponent {
R = 1 << 0,
G = 1 << 1,
@@ -109,6 +120,9 @@ public:
bool separateBlendFactors;
BlendFactor srcAlpha;
BlendFactor dstAlpha;
+ BlendOp opColor;
+ BlendOp opAlpha;
+
// This struct is extensible while keeping BC since apps only ever get
// a ptr to the struct, it is not created by them.
};
@@ -147,6 +161,7 @@ protected:
// filename is for a file containing a serialized QShader.
void setShaderFileName(Stage stage, const QString &filename);
+ void setShaderFileName(Stage stage, const QString &filename, int viewCount);
void setShader(Stage stage, const QShader &shader);
diff --git a/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h b/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h
index de0161e431..8b4a4c0b2f 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h
+++ b/src/quick/scenegraph/coreapi/qsgmaterialshader_p.h
@@ -18,14 +18,14 @@
#include <private/qtquickglobal_p.h>
#include "qsgmaterialshader.h"
#include "qsgmaterial.h"
-#include <QtGui/private/qrhi_p.h>
-#include <QtGui/private/qshader_p.h>
+#include <rhi/qrhi.h>
+#include <rhi/qshader.h>
QT_BEGIN_NAMESPACE
class QRhiSampler;
-class Q_QUICK_PRIVATE_EXPORT QSGMaterialShaderPrivate
+class Q_QUICK_EXPORT QSGMaterialShaderPrivate
{
public:
Q_DECLARE_PUBLIC(QSGMaterialShader)
diff --git a/src/quick/scenegraph/coreapi/qsgnode.cpp b/src/quick/scenegraph/coreapi/qsgnode.cpp
index 62979cb9fc..b77f33d6a3 100644
--- a/src/quick/scenegraph/coreapi/qsgnode.cpp
+++ b/src/quick/scenegraph/coreapi/qsgnode.cpp
@@ -11,13 +11,14 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQsgLeak)
+
#ifndef QT_NO_DEBUG
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
static int qt_node_count = 0;
static void qt_print_node_count()
{
- qDebug("Number of leaked nodes: %i", qt_node_count);
+ qCDebug(lcQsgLeak, "Number of leaked nodes: %i", qt_node_count);
qt_node_count = -1;
}
#endif
@@ -256,7 +257,7 @@ QSGNode::QSGNode(QSGNodePrivate &dd, NodeType type)
void QSGNode::init()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
++qt_node_count;
static bool atexit_registered = false;
if (!atexit_registered) {
@@ -281,10 +282,10 @@ void QSGNode::init()
QSGNode::~QSGNode()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check) {
+ if (lcQsgLeak().isDebugEnabled()) {
--qt_node_count;
if (qt_node_count < 0)
- qDebug("Node destroyed after qt_print_node_count() was called.");
+ qCDebug(lcQsgLeak, "Node destroyed after qt_print_node_count() was called.");
}
#endif
destroy();
diff --git a/src/quick/scenegraph/coreapi/qsgnode.h b/src/quick/scenegraph/coreapi/qsgnode.h
index 659d085922..50dc6021ea 100644
--- a/src/quick/scenegraph/coreapi/qsgnode.h
+++ b/src/quick/scenegraph/coreapi/qsgnode.h
@@ -175,8 +175,8 @@ private:
QSGGeometry *m_geometry;
- int m_reserved_start_index;
- int m_reserved_end_index;
+ Q_DECL_UNUSED_MEMBER int m_reserved_start_index;
+ Q_DECL_UNUSED_MEMBER int m_reserved_end_index;
const QMatrix4x4 *m_matrix;
const QSGClipNode *m_clip_list;
diff --git a/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h b/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h
index 6babf76395..a2bf37b538 100644
--- a/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h
+++ b/src/quick/scenegraph/coreapi/qsgnodeupdater_p.h
@@ -28,7 +28,7 @@ class QSGGeometryNode;
class QMatrix4x4;
class QSGRenderNode;
-class Q_QUICK_PRIVATE_EXPORT QSGNodeUpdater
+class Q_QUICK_EXPORT QSGNodeUpdater
{
public:
QSGNodeUpdater();
diff --git a/src/quick/scenegraph/coreapi/qsgrenderer.cpp b/src/quick/scenegraph/coreapi/qsgrenderer.cpp
index 5d67b5dd74..ffa3047850 100644
--- a/src/quick/scenegraph/coreapi/qsgrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrenderer.cpp
@@ -14,6 +14,13 @@ static QElapsedTimer frameTimer;
static qint64 preprocessTime;
static qint64 updatePassTime;
+Q_TRACE_POINT(qtquick, QSG_preprocess_entry)
+Q_TRACE_POINT(qtquick, QSG_preprocess_exit)
+Q_TRACE_POINT(qtquick, QSG_update_entry)
+Q_TRACE_POINT(qtquick, QSG_update_exit)
+Q_TRACE_POINT(qtquick, QSG_renderScene_entry)
+Q_TRACE_POINT(qtquick, QSG_renderScene_exit)
+
int qt_sg_envInt(const char *name, int defaultValue)
{
if (Q_LIKELY(!qEnvironmentVariableIsSet(name)))
@@ -64,6 +71,8 @@ QSGRenderer::QSGRenderer(QSGRenderContext *context)
, m_is_rendering(false)
, m_is_preprocessing(false)
{
+ m_current_projection_matrix.resize(1);
+ m_current_projection_matrix_native_ndc.resize(1);
}
@@ -103,7 +112,7 @@ void QSGRenderer::setNodeUpdater(QSGNodeUpdater *updater)
bool QSGRenderer::isMirrored() const
{
- QMatrix4x4 matrix = projectionMatrix();
+ QMatrix4x4 matrix = projectionMatrix(0);
// Mirrored relative to the usual Qt coordinate system with origin in the top left corner.
return matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0) > 0;
}
diff --git a/src/quick/scenegraph/coreapi/qsgrenderer_p.h b/src/quick/scenegraph/coreapi/qsgrenderer_p.h
index 1485e903f4..28654521c9 100644
--- a/src/quick/scenegraph/coreapi/qsgrenderer_p.h
+++ b/src/quick/scenegraph/coreapi/qsgrenderer_p.h
@@ -29,10 +29,10 @@ class QRhiCommandBuffer;
class QRhiRenderPassDescriptor;
class QRhiResourceUpdateBatch;
-Q_QUICK_PRIVATE_EXPORT bool qsg_test_and_clear_fatal_render_error();
-Q_QUICK_PRIVATE_EXPORT void qsg_set_fatal_renderer_error();
+Q_QUICK_EXPORT bool qsg_test_and_clear_fatal_render_error();
+Q_QUICK_EXPORT void qsg_set_fatal_renderer_error();
-class Q_QUICK_PRIVATE_EXPORT QSGRenderTarget
+class Q_QUICK_EXPORT QSGRenderTarget
{
public:
QSGRenderTarget() { }
@@ -53,18 +53,20 @@ public:
QRhiCommandBuffer *cb = nullptr;
QPaintDevice *paintDevice = nullptr;
+
+ int multiViewCount = 0;
};
-class Q_QUICK_PRIVATE_EXPORT QSGRenderer : public QSGAbstractRenderer
+class Q_QUICK_EXPORT QSGRenderer : public QSGAbstractRenderer
{
public:
QSGRenderer(QSGRenderContext *context);
virtual ~QSGRenderer();
// Accessed by QSGMaterial[Rhi]Shader::RenderState.
- QMatrix4x4 currentProjectionMatrix() const { return m_current_projection_matrix; }
+ QMatrix4x4 currentProjectionMatrix(int index) const { return m_current_projection_matrix[index]; }
QMatrix4x4 currentModelViewMatrix() const { return m_current_model_view_matrix; }
- QMatrix4x4 currentCombinedMatrix() const { return m_current_projection_matrix * m_current_model_view_matrix; }
+ QMatrix4x4 currentCombinedMatrix(int index) const { return m_current_projection_matrix[index] * m_current_model_view_matrix; }
qreal currentOpacity() const { return m_current_opacity; }
qreal determinant() const { return m_current_determinant; }
@@ -115,8 +117,8 @@ protected:
void addNodesToPreprocess(QSGNode *node);
void removeNodesToPreprocess(QSGNode *node);
- QMatrix4x4 m_current_projection_matrix; // includes adjustment, where applicable, so can be treated as Y up in NDC always
- QMatrix4x4 m_current_projection_matrix_native_ndc; // Vulkan has Y down in normalized device coordinates, others Y up...
+ QVarLengthArray<QMatrix4x4, 1> m_current_projection_matrix; // includes adjustment, where applicable, so can be treated as Y up in NDC always
+ QVarLengthArray<QMatrix4x4, 1> m_current_projection_matrix_native_ndc; // Vulkan has Y down in normalized device coordinates, others Y up...
QMatrix4x4 m_current_model_view_matrix;
qreal m_current_opacity;
qreal m_current_determinant;
@@ -154,7 +156,7 @@ QSGMaterialShader::RenderState QSGRenderer::state(QSGMaterialShader::RenderState
}
-class Q_QUICK_PRIVATE_EXPORT QSGNodeDumper : public QSGNodeVisitor {
+class Q_QUICK_EXPORT QSGNodeDumper : public QSGNodeVisitor {
public:
static void dump(QSGNode *n);
@@ -167,8 +169,6 @@ private:
int m_indent = 0;
};
-
-
QT_END_NAMESPACE
#endif
diff --git a/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp b/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
index 5245b6d950..b251d13edd 100644
--- a/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrendererinterface.cpp
@@ -40,11 +40,12 @@ QT_BEGIN_NAMESPACE
\value Unknown An unknown graphics API is in use
\value Software The Qt Quick 2D Renderer is in use
\value OpenVG OpenVG via EGL
- \value OpenGL OpenGL ES 2.0 or higher via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Direct3D11 Direct3D 11 via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Vulkan Vulkan 1.0 via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Metal Metal via a graphics abstraction layer. This value was introduced in Qt 5.14.
- \value Null Null (no output) via a graphics abstraction layer. This value was introduced in Qt 5.14.
+ \value [since 5.14] OpenGL OpenGL ES 2.0 or higher via a graphics abstraction layer.
+ \value [since 5.14] Direct3D11 Direct3D 11 via a graphics abstraction layer.
+ \value [since 6.6] Direct3D12 Direct3D 12 via a graphics abstraction layer.
+ \value [since 5.14] Vulkan Vulkan 1.0 via a graphics abstraction layer.
+ \value [since 5.14] Metal Metal via a graphics abstraction layer.
+ \value [since 5.14] Null Null (no output) via a graphics abstraction layer.
\omitvalue OpenGLRhi
\omitvalue Direct3D11Rhi
\omitvalue VulkanRhi
@@ -77,65 +78,69 @@ QT_BEGIN_NAMESPACE
\value PainterResource The resource is a pointer to the active QPainter
used by the scenegraph, when running with the software backend.
- \value RhiResource The resource is a pointer to the QRhi instance used by
- the scenegraph, when applicable. This value was introduced in Qt 5.14.
+ \value [since 5.14] RhiResource The resource is a pointer to the QRhi instance used by
+ the scenegraph, when applicable.
- \value RhiSwapchainResource The resource is a pointer to a QRhiSwapchain
+ \value [since 6.0] RhiSwapchainResource The resource is a pointer to a QRhiSwapchain
instance that is associated with the window. The value is null when the
- window is used in combination with QQuickRenderControl. This value was
- introduced in Qt 6.0.
+ window is used in combination with QQuickRenderControl.
- \value RhiRedirectCommandBuffer The resource is a pointer to a
+ \value [since 6.0] RhiRedirectCommandBuffer The resource is a pointer to a
QRhiCommandBuffer instance that is associated with the window and its
QQuickRenderControl. The value is null when the window is not associated
- with a QQuickRenderControl. This value was introduced in Qt 6.0.
+ with a QQuickRenderControl.
- \value RhiRedirectRenderTarget The resource is a pointer to a
+ \value [since 6.0] RhiRedirectRenderTarget The resource is a pointer to a
QRhiTextureRenderTarget instance that is associated with the window and its
QQuickRenderControl. The value is null when the window is not associated
with a QQuickRenderControl. Note that the value always reflects the main
texture render target and it does not depend on the Qt Quick scene, meaning
it does not take any additional texture-targeting render passes generated
- by ShaderEffect or QQuickItem layers into account. This value was
- introduced in Qt 6.0.
+ by ShaderEffect or QQuickItem layers into account.
- \value PhysicalDeviceResource The resource is a pointer to the pysical
+ \value [since 5.14] PhysicalDeviceResource The resource is a pointer to the pysical
device object used by the scenegraph, when applicable. For example, a
\c{VkPhysicalDevice *}. Note that with Vulkan the returned value is a
- pointer to the VkPhysicalDevice, not the handle itself. This value was
- introduced in Qt 5.14.
+ pointer to the VkPhysicalDevice, not the handle itself.
- \value OpenGLContextResource The resource is a pointer to the
+ \value [since 5.14] OpenGLContextResource The resource is a pointer to the
QOpenGLContext used by the scenegraph (on the render thread), when
- applicable. This value was introduced in Qt 5.14.
+ applicable.
- \value DeviceContextResource The resource is a pointer to the device
+ \value [since 5.14] DeviceContextResource The resource is a pointer to the device
context used by the scenegraph, when applicable. For example, a
- \c{ID3D11DeviceContext *}. This value was introduced in Qt 5.14.
+ \c{ID3D11DeviceContext *}.
- \value CommandEncoderResource The resource is a pointer to the currently
+ \value [since 5.14] CommandEncoderResource The resource is a pointer to the currently
active render command encoder object used by the scenegraph, when
applicable. For example, a \c{MTLRenderCommandEncoder *}. This object has
limited validity, and is only valid while the scene graph is recording a
- render pass for the next frame. This value was introduced in Qt 5.14.
+ render pass for the next frame.
- \value VulkanInstanceResource The resource is a pointer to the
- QVulkanInstance used by the scenegraph, when applicable. This value was
- introduced in Qt 5.14.
+ \value [since 5.14] VulkanInstanceResource The resource is a pointer to the
+ QVulkanInstance used by the scenegraph, when applicable.
- \value RenderPassResource The resource is a pointer to the main render pass
+ \value [since 5.14] RenderPassResource The resource is a pointer to the main render pass
used by the scenegraph, describing the color and depth/stecil attachments
and how they are used. For example, a \c{VkRenderPass *}. Note that the
value always reflects the main render target (either the on-screen window
or the texture QQuickRenderControl redirects to) and it does not depend on
the Qt Quick scene, meaning it does not take any additional
texture-targeting render passes generated by ShaderEffect or QQuickItem
- layers into account. This value was introduced in Qt 5.14.
+ layers into account.
- \value RedirectPaintDevice The resource is a pointer to QPaintDevice instance
+ \value [since 6.4] RedirectPaintDevice The resource is a pointer to QPaintDevice instance
that is associated with the window and its QQuickRenderControl. The value is
- null when the window is not associated with a QQuickRenderControl. This value
- was introduced in Qt 6.4.
+ null when the window is not associated with a QQuickRenderControl.
+
+ \value [since 6.6] GraphicsQueueFamilyIndexResource The resource is a pointer to the
+ graphics queue family index used by the scenegraph, when applicable. With
+ Vulkan, this is a pointer to a \c uint32_t index value.
+
+ \value [since 6.6] GraphicsQueueIndexResource The resource is a pointer to the graphics
+ queue index (uint32_t) used by the scenegraph, when applicable. With
+ Vulkan, this is a pointer to a \c uint32_t index value, which in practice
+ is the index of the VkQueue reported for \c CommandQueueResource.
*/
/*!
@@ -143,9 +148,8 @@ QT_BEGIN_NAMESPACE
\value UnknownShadingLanguage Not yet known due to no window and scenegraph associated
\value GLSL GLSL or GLSL ES
\value HLSL HLSL
- \value RhiShader Consumes QShader instances containing shader variants for
- multiple target languages and intermediate formats. This value was introduced in
- Qt 5.14.
+ \value [since 5.14] RhiShader Consumes QShader instances containing shader variants for
+ multiple target languages and intermediate formats.
*/
/*!
@@ -234,15 +238,12 @@ void *QSGRendererInterface::getResource(QQuickWindow *window, const char *resour
bool QSGRendererInterface::isApiRhiBased(GraphicsApi api)
{
switch (api) {
- case OpenGLRhi:
- Q_FALLTHROUGH();
- case Direct3D11Rhi:
- Q_FALLTHROUGH();
- case VulkanRhi:
- Q_FALLTHROUGH();
- case MetalRhi:
- Q_FALLTHROUGH();
- case NullRhi:
+ case OpenGL:
+ case Direct3D11:
+ case Direct3D12:
+ case Vulkan:
+ case Metal:
+ case Null:
return true;
default:
return false;
diff --git a/src/quick/scenegraph/coreapi/qsgrendererinterface.h b/src/quick/scenegraph/coreapi/qsgrendererinterface.h
index 7f08c8db34..d76bbed5cf 100644
--- a/src/quick/scenegraph/coreapi/qsgrendererinterface.h
+++ b/src/quick/scenegraph/coreapi/qsgrendererinterface.h
@@ -22,6 +22,7 @@ public:
Vulkan,
Metal,
Null,
+ Direct3D12,
OpenGLRhi = OpenGL,
Direct3D11Rhi = Direct3D11,
@@ -45,7 +46,9 @@ public:
CommandEncoderResource,
VulkanInstanceResource,
RenderPassResource,
- RedirectPaintDevice
+ RedirectPaintDevice,
+ GraphicsQueueFamilyIndexResource,
+ GraphicsQueueIndexResource,
};
enum ShaderType {
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode.cpp b/src/quick/scenegraph/coreapi/qsgrendernode.cpp
index 7a4627e1b4..333d62d40b 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrendernode.cpp
@@ -12,6 +12,23 @@ QT_BEGIN_NAMESPACE
targeting the graphics API that is in use by the scenegraph.
\inmodule QtQuick
\since 5.8
+
+ QSGRenderNode allows creating scene graph nodes that perform their own
+ custom rendering via QRhi (the common approach from Qt 6.6 on), directly
+ via a 3D graphics API such as OpenGL, Vulkan, or Metal, or, when the \c
+ software backend is in use, via QPainter.
+
+ QSGRenderNode is the enabler for one of the three ways to integrate custom
+ 2D/3D rendering into a Qt Quick scene. The other two options are to perform
+ the rendering \c before or \c after the Qt Quick scene's own rendering,
+ or to generate a whole separate render pass targeting a dedicated render
+ target (a texture) and then have an item in the scene display the texture.
+ The QSGRenderNode-based approach is similar to the former, in the sense
+ that no additional render passes or render targets are involved, and allows
+ injecting custom rendering commands "inline" with the Qt Quick scene's
+ own rendering.
+
+ \sa {Scene Graph - Custom QSGRenderNode}
*/
QSGRenderNode::QSGRenderNode()
@@ -30,6 +47,13 @@ QSGRenderNode::QSGRenderNode()
deleted. Therefore there is no need to issue additional waits here, unless
the render() implementation is using additional command queues.
+ With QRhi and resources such as QRhiBuffer, QRhiTexture,
+ QRhiGraphicsPipeline, etc., it is often good practice to use smart
+ pointers, such as std::unique_ptr, which can often avoid the need to
+ implement a destructor, and lead to more compact source code. Keep in mind
+ however that implementing releaseResources(), most likely issuing a number
+ of reset() calls on the unique_ptrs, is still important.
+
\sa releaseResources()
*/
QSGRenderNode::~QSGRenderNode()
@@ -42,6 +66,7 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
, m_clip_list(nullptr)
, m_opacity(1)
{
+ m_projectionMatrix.resize(1);
}
/*!
@@ -49,17 +74,15 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
mask where each bit represents graphics states changed by the \l render()
function:
- \list
- \li DepthState - depth write mask, depth test enabled, depth comparison function
- \li StencilState - stencil write masks, stencil test enabled, stencil operations,
- stencil comparison functions
- \li ScissorState - scissor enabled, scissor test enabled
- \li ColorState - clear color, color write mask
- \li BlendState - blend enabled, blend function
- \li CullState - front face, cull face enabled
- \li ViewportState - viewport
- \li RenderTargetState - render target
- \endlist
+ \value DepthState depth write mask, depth test enabled, depth comparison function
+ \value StencilState stencil write masks, stencil test enabled, stencil operations,
+ stencil comparison functions
+ \value ScissorState scissor enabled, scissor test enabled
+ \value ColorState clear color, color write mask
+ \value BlendState blend enabled, blend function
+ \value CullState front face, cull face enabled
+ \value ViewportState viewport
+ \value RenderTargetState render target
With APIs other than OpenGL, the only relevant values are the ones that
correspond to dynamic state changes recorded on the command list/buffer.
@@ -73,16 +96,16 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
bindings, root signature, descriptor heaps, etc.) are always set again by
the scenegraph so render() can freely change them.
- \note RenderTargetState is no longer supported with APIs like Vulkan. This
+ RenderTargetState is no longer supported with APIs like Vulkan. This
is by nature. render() is invoked while the Qt Quick scenegraph's main
command buffer is recording a renderpass, so there is no possibility of
changing the target and starting another renderpass (on that command buffer
at least). Therefore returning a value with RenderTargetState set is not
sensible.
- The software backend exposes its QPainter and saves and restores before and
- after invoking render(). Therefore reporting any changed states from here
- is not necessary.
+ \note The \c software backend exposes its QPainter and saves and restores
+ before and after invoking render(). Therefore reporting any changed states
+ from here is not necessary.
The function is called by the renderer so it can reset the states after
rendering this node. This makes the implementation of render() simpler
@@ -92,6 +115,10 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
in render().
\note This function may be called before render().
+
+ \note With Qt 6 and QRhi-based rendering the only relevant values are
+ ViewportState and ScissorState. Other values can be returned but are
+ ignored in practice.
*/
QSGRenderNode::StateFlags QSGRenderNode::changedStates() const
{
@@ -110,6 +137,12 @@ QSGRenderNode::StateFlags QSGRenderNode::changedStates() const
The default implementation is empty.
+ When implementing a QSGRenderNode that uses QRhi to render, query the QRhi
+ object from the QQuickWindow via \l{QQuickWindow::rhi()}. To get a
+ QRhiCommandBuffer for submitting work to, call commandBuffer(). To query
+ information about the active render target, call renderTarget(). See the
+ \l{{Scene Graph - Custom QSGRenderNode}} example for details.
+
\since 6.0
*/
void QSGRenderNode::prepare()
@@ -160,57 +193,33 @@ void QSGRenderNode::prepare()
Some scenegraph backends, software in particular, use no scissor or
stencil. There the clip region is provided as an ordinary QRegion.
- With the legacy, direct OpenGL based renderer, the following states are set
- on the render thread's context before this function is called:
-
- \list
- \li glColorMask(true, true, true, true)
- \li glDepthMask(false)
- \li glDisable(GL_DEPTH_TEST)
- \li glStencilFunc(GL_EQUAL, state.stencilValue, 0xff); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) depending on clip
- \li glScissor(state.scissorRect.x(), state.scissorRect.y(),
- state.scissorRect.width(), state.scissorRect.height()) depending on clip
- \li glEnable(GL_BLEND)
- \li glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
- \li glDisable(GL_CULL_FACE)
- \endlist
-
- States that are not listed above, but are covered by \l StateFlags, can
- have arbitrary values.
-
- \note There is no state set with other graphics APIs, considering that many
- of them do not have a concept of the traditional OpenGL state machine.
- Rather, it is up to the implementation to create pipeline state objects
- with the desired blending, scissor, and stencil tests enabled. Note that
- this also includes OpenGL via the RHI. New QSGRenderNode implementations
- are recommended to set all scissor, stencil and blend state explicitly (as
- shown in the above list), even if they are targeting OpenGL.
-
- \l changedStates() should return which states this function changes. If a
- state is not covered by \l StateFlags, the state should be set to the
- default value according to the OpenGL specification. For other APIs, see
- the documentation for changedStates() for more information.
-
- \note Depth writes are disabled when this function is called
- (glDepthMask(false) with OpenGL). Enabling depth writes can lead to
- unexpected results, depending on the scenegraph backend in use and the
- content in the scene, so exercise caution with this.
-
- For APIs other than OpenGL, it will likely be necessary to query certain
- API-specific resources (for example, the graphics device or the command
- list/buffer to add the commands to). This is done via QSGRendererInterface.
-
- Assume nothing about the pipelines and dynamic states bound on the command
- list/buffer when this function is called.
-
- With some graphics APIs it can be necessary to reimplement prepare() in
- addition, or alternatively connect to the QQuickWindow::beforeRendering()
- signal. These are called/emitted before recording the beginning of a
- renderpass on the command buffer (vkCmdBeginRenderPass with Vulkan, or
- starting to encode via MTLRenderCommandEncoder in case of Metal. Recording
- copy operations cannot be done inside render() with such APIs. Rather, do
- such operations either in prepare() or the slot connected to
- beforeRendering (with DirectConnection).
+ When implementing a QSGRenderNode that uses QRhi to render, query the QRhi
+ object from the QQuickWindow via \l{QQuickWindow::rhi()}. To get a
+ QRhiCommandBuffer for submitting work to, call commandBuffer(). To query
+ information about the active render target, call renderTarget(). See the
+ \l{{Scene Graph - Custom QSGRenderNode}} example for details.
+
+ With Qt 6 and its QRhi-based scene graph renderer, no assumptions should be
+ made about the active (OpenGL) state when this function is called, even
+ when OpenGL is in use. Assume nothing about the pipelines and dynamic
+ states bound on the command list/buffer when this function is called.
+
+ \note Depth writes are expected to be disabled. Enabling depth writes can
+ lead to unexpected results, depending on the scenegraph backend in use and
+ the content in the scene, so exercise caution with this.
+
+ \note In Qt 6, \l changedStates() has limited use. See the documentation
+ for changedStates() for more information.
+
+ With some graphics APIs, including when using QRhi directly, it can be
+ necessary to reimplement prepare() in addition, or alternatively connect to
+ the QQuickWindow::beforeRendering() signal. These are called/emitted before
+ recording the beginning of a renderpass on the command buffer
+ (vkCmdBeginRenderPass with Vulkan, or starting to encode via
+ MTLRenderCommandEncoder in case of Metal. Recording copy operations cannot
+ be done inside render() with such APIs. Rather, do such operations either
+ in prepare() or the slot connected to beforeRendering (with
+ DirectConnection).
\sa QSGRendererInterface, QQuickWindow::rendererInterface()
*/
@@ -263,7 +272,7 @@ void QSGRenderNode::releaseResources()
\value BoundedRectRendering Indicates that the implementation of render()
does not render outside the area reported from rect() in item
coordinates. Such node implementations can lead to more efficient rendering,
- depending on the scenegraph backend. For example, the software backend can
+ depending on the scenegraph backend. For example, the \c software backend can
continue to use the more optimal partial update path when all render nodes
in the scene have this flag set.
@@ -282,9 +291,11 @@ void QSGRenderNode::releaseResources()
transparent pixels. Setting this flag can improve performance in some
cases.
- \omitvalue NoExternalRendering
+ \value NoExternalRendering Indicates that the implementation of prepare()
+ and render() use the QRhi family of APIs, instead of directly calling a 3D
+ API such as OpenGL, Vulkan, or Metal.
- \sa render(), rect()
+ \sa render(), prepare(), rect(), QRhi
*/
/*!
@@ -341,7 +352,15 @@ QRectF QSGRenderNode::rect() const
*/
const QMatrix4x4 *QSGRenderNode::projectionMatrix() const
{
- return &d->m_projectionMatrix;
+ return &d->m_projectionMatrix[0];
+}
+
+/*!
+ \internal
+ */
+const QMatrix4x4 *QSGRenderNode::projectionMatrix(int index) const
+{
+ return &d->m_projectionMatrix[index];
}
/*!
@@ -368,6 +387,59 @@ qreal QSGRenderNode::inheritedOpacity() const
return d->m_opacity;
}
+/*!
+ \return the current render target.
+
+ This is provided mainly to enable prepare() and render() implementations
+ that use QRhi accessing the QRhiRenderTarget's
+ \l{QRhiRenderPassDescriptor}{renderPassDescriptor} or
+ \l{QRhiRenderTarget::pixelSize()}{pixel size}.
+
+ To build a QRhiGraphicsPipeline, which implies having to provide a
+ QRhiRenderPassDescriptor, query the renderPassDescriptor from the render
+ target. Be aware however that the render target may change over the
+ lifetime of the custom QQuickItem and the QSGRenderNode. For example,
+ consider what happens when dynamically setting \c{layer.enabled: true} on
+ the item or an ancestor of it: this triggers rendering into a texture, not
+ directly to the window, which means the QSGRenderNode is going to work with
+ a different render target from then on. The new render target may then have
+ a different pixel format, which can make already built graphics pipelines
+ incompatible. This can be handled with logic such as the following:
+
+ \code
+ if (m_pipeline && renderTarget()->renderPassDescriptor()->serializedFormat() != m_renderPassFormat) {
+ delete m_pipeline;
+ m_pipeline = nullptr;
+ }
+ if (!m_pipeline) {
+ // Build a new QRhiGraphicsPipeline.
+ // ...
+ // Store the serialized format for fast and simple comparisons later on.
+ m_renderPassFormat = renderTarget()->renderPassDescriptor()->serializedFormat();
+ }
+ \endcode
+
+ \since 6.6
+
+ \sa commandBuffer()
+ */
+QRhiRenderTarget *QSGRenderNode::renderTarget() const
+{
+ return d->m_rt.rt;
+}
+
+/*!
+ \return the current command buffer.
+
+ \since 6.6
+
+ \sa renderTarget()
+ */
+QRhiCommandBuffer *QSGRenderNode::commandBuffer() const
+{
+ return d->m_rt.cb;
+}
+
QSGRenderNode::RenderState::~RenderState()
{
}
@@ -421,7 +493,7 @@ QSGRenderNode::RenderState::~RenderState()
\return the current clip region or null for backends where clipping is
implemented via stencil or scissoring.
- The software backend uses no projection, scissor or stencil, meaning most
+ The \c software backend uses no projection, scissor or stencil, meaning most
of the render state is not in use. However, the clip region that can be set
on the QPainter still has to be communicated since reconstructing this
manually in render() is not reasonable. It can therefore be queried via
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode.h b/src/quick/scenegraph/coreapi/qsgrendernode.h
index fcf2b0e559..927a8dee59 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode.h
+++ b/src/quick/scenegraph/coreapi/qsgrendernode.h
@@ -9,6 +9,8 @@
QT_BEGIN_NAMESPACE
class QSGRenderNodePrivate;
+class QRhiRenderTarget;
+class QRhiCommandBuffer;
class Q_QUICK_EXPORT QSGRenderNode : public QSGNode
{
@@ -55,9 +57,12 @@ public:
virtual QRectF rect() const;
const QMatrix4x4 *projectionMatrix() const;
+ const QMatrix4x4 *projectionMatrix(int index) const;
const QMatrix4x4 *matrix() const;
const QSGClipNode *clipList() const;
qreal inheritedOpacity() const;
+ QRhiRenderTarget *renderTarget() const;
+ QRhiCommandBuffer *commandBuffer() const;
private:
QSGRenderNodePrivate *d;
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode_p.h b/src/quick/scenegraph/coreapi/qsgrendernode_p.h
index e00415f480..8bdd457b95 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode_p.h
+++ b/src/quick/scenegraph/coreapi/qsgrendernode_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGRenderNodePrivate
+class Q_QUICK_EXPORT QSGRenderNodePrivate
{
public:
QSGRenderNodePrivate();
@@ -32,7 +32,8 @@ public:
const QSGClipNode *m_clip_list;
qreal m_opacity;
QSGRenderTarget m_rt;
- QMatrix4x4 m_projectionMatrix;
+ QVarLengthArray<QMatrix4x4, 1> m_projectionMatrix;
+ QMatrix4x4 m_localMatrix; // ### Qt 7 m_matrix should not be a pointer
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp b/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp
index 6e8fcae06f..d1f4e46ed0 100644
--- a/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp
@@ -330,7 +330,7 @@ void RhiVisualizer::ChangeVis::gather(Node *n)
const QColor color = QColor::fromHsvF((visualizer->m_randomGenerator.generate() & 1023) / 1023.0f, 0.3f, 1.0f).toRgb();
const float alpha = 0.5f;
- QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix;
+ QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix[0];
if (n->element()->batch->root)
matrix = matrix * qsg_matrixForRoot(n->element()->batch->root);
@@ -445,7 +445,7 @@ void RhiVisualizer::BatchVis::gather(Batch *b)
if (b->positionAttribute != 0)
return;
- QMatrix4x4 matrix(visualizer->m_renderer->m_current_projection_matrix);
+ QMatrix4x4 matrix(visualizer->m_renderer->m_current_projection_matrix[0]);
if (b->root)
matrix = matrix * qsg_matrixForRoot(b->root);
@@ -575,7 +575,7 @@ void RhiVisualizer::ClipVis::gather(QSGNode *node)
{
if (node->type() == QSGNode::ClipNodeType) {
QSGClipNode *clipNode = static_cast<QSGClipNode *>(node);
- QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix;
+ QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix[0];
if (clipNode->matrix())
matrix = matrix * *clipNode->matrix();
@@ -674,7 +674,7 @@ void RhiVisualizer::ClipVis::render(QRhiCommandBuffer *cb)
void RhiVisualizer::OverdrawVis::gather(Node *n)
{
if (n->type() == QSGNode::GeometryNodeType && n->element()->batch) {
- QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix;
+ QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix[0];
matrix(2, 2) = visualizer->m_renderer->m_zRange;
matrix(2, 3) = 1.0f - n->element()->order * visualizer->m_renderer->m_zRange;
diff --git a/src/quick/scenegraph/coreapi/qsgtexture.cpp b/src/quick/scenegraph/coreapi/qsgtexture.cpp
index 31e68b9f31..08cd525a68 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture.cpp
+++ b/src/quick/scenegraph/coreapi/qsgtexture.cpp
@@ -5,16 +5,17 @@
#include "qsgtexture_platform.h"
#include <private/qqmlglobal_p.h>
#include <private/qsgmaterialshader_p.h>
+#include <private/qsgrenderer_p.h>
#include <private/qquickitem_p.h> // qquickwindow_p.h cannot be included on its own due to template nonsense
#include <private/qquickwindow_p.h>
#include <QtCore/private/qnativeinterface_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) && defined(__GLIBC__)
#define CAN_BACKTRACE_EXECINFO
#endif
-#if defined(Q_OS_MAC)
+#if defined(Q_OS_APPLE)
#define CAN_BACKTRACE_EXECINFO
#endif
@@ -30,11 +31,12 @@
#ifndef QT_NO_DEBUG
Q_GLOBAL_STATIC(QSet<QSGTexture *>, qsg_valid_texture_set)
Q_GLOBAL_STATIC(QMutex, qsg_valid_texture_mutex)
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
#endif
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQsgLeak)
+
bool operator==(const QSGSamplerDescription &a, const QSGSamplerDescription &b) noexcept
{
return a.filtering == b.filtering
@@ -83,8 +85,9 @@ QSGTexturePrivate::QSGTexturePrivate(QSGTexture *t)
#endif
#ifdef Q_OS_WIN
, m_d3d11TextureAccessor(t)
+ , m_d3d12TextureAccessor(t)
#endif
-#if defined(__OBJC__)
+#if QT_CONFIG(metal)
, m_metalTextureAccessor(t)
#endif
#if QT_CONFIG(vulkan)
@@ -100,7 +103,7 @@ QSGTexturePrivate::QSGTexturePrivate(QSGTexture *t)
static int qt_debug_texture_count = 0;
-#if (defined(Q_OS_LINUX) || defined (Q_OS_MAC)) && !defined(Q_OS_ANDROID)
+#if (defined(Q_OS_LINUX) || defined (Q_OS_APPLE)) && !defined(Q_OS_ANDROID)
DEFINE_BOOL_CONFIG_OPTION(qmlDebugLeakBacktrace, QML_DEBUG_LEAK_BACKTRACE)
#define BACKTRACE_SIZE 20
@@ -116,7 +119,7 @@ static QHash<QSGTexture*, SGTextureTraceItem*> qt_debug_allocated_textures;
inline static void qt_debug_print_texture_count()
{
- qDebug("Number of leaked textures: %i", qt_debug_texture_count);
+ qCDebug(lcQsgLeak, "Number of leaked textures: %i", qt_debug_texture_count);
qt_debug_texture_count = -1;
#if defined(CAN_BACKTRACE_EXECINFO)
@@ -243,8 +246,6 @@ static void qt_debug_remove_texture(QSGTexture* texture)
\note All classes with QSG prefix should be used solely on the scene graph's
rendering thread. See \l {Scene Graph and Rendering} for more information.
-
- \sa {Scene Graph - Rendering FBOs}
*/
/*!
@@ -304,7 +305,7 @@ QSGTexture::QSGTexture()
: QObject(*(new QSGTexturePrivate(this)))
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check)
+ if (lcQsgLeak().isDebugEnabled())
qt_debug_add_texture(this);
QMutexLocker locker(qsg_valid_texture_mutex());
@@ -319,7 +320,7 @@ QSGTexture::QSGTexture(QSGTexturePrivate &dd)
: QObject(dd)
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check)
+ if (lcQsgLeak().isDebugEnabled())
qt_debug_add_texture(this);
QMutexLocker locker(qsg_valid_texture_mutex());
@@ -333,7 +334,7 @@ QSGTexture::QSGTexture(QSGTexturePrivate &dd)
QSGTexture::~QSGTexture()
{
#ifndef QT_NO_DEBUG
- if (qsg_leak_check)
+ if (lcQsgLeak().isDebugEnabled())
qt_debug_remove_texture(this);
QMutexLocker locker(qsg_valid_texture_mutex());
@@ -580,8 +581,6 @@ QSGTexture::WrapMode QSGTexture::verticalWrapMode() const
\warning This function can only be called from the rendering thread.
\since 6.0
-
- \internal
*/
QRhiTexture *QSGTexture::rhiTexture() const
{
@@ -602,8 +601,6 @@ QRhiTexture *QSGTexture::rhiTexture() const
\warning This function can only be called from the rendering thread.
\since 6.0
-
- \internal
*/
void QSGTexture::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
{
@@ -686,6 +683,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting OpenGL texture objects.
\since 6.0
*/
@@ -786,6 +784,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting Direct3D 11 texture objects.
\since 6.0
*/
@@ -837,6 +836,78 @@ void *QSGTexturePlatformD3D11::nativeTexture() const
return reinterpret_cast<void *>(quintptr(tex->nativeTexture().object));
return 0;
}
+
+namespace QNativeInterface {
+/*!
+ \class QNativeInterface::QSGD3D12Texture
+ \inmodule QtQuick
+ \ingroup native-interfaces
+ \ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
+ \brief Provides access to and enables adopting Direct3D 12 texture objects.
+ \since 6.6
+*/
+
+/*!
+ \fn void *QNativeInterface::QSGD3D12Texture::nativeTexture() const
+ \return the ID3D12Texture object.
+ */
+
+QT_DEFINE_NATIVE_INTERFACE(QSGD3D12Texture);
+
+/*!
+ Creates a new QSGTexture wrapping an existing Direct 3D 12 \a texture object
+ for \a window.
+
+ The native object is wrapped, but not owned, by the resulting QSGTexture.
+ The caller of the function is responsible for deleting the returned
+ QSGTexture, but that will not destroy the underlying native object.
+
+ This function is currently suitable for 2D RGBA textures only.
+
+ \warning This function will return null if the scene graph has not yet been
+ initialized.
+
+ Use \a options to customize the texture attributes. Only the
+ TextureHasAlphaChannel and TextureHasMipmaps are taken into account here.
+
+ \a size specifies the size in pixels.
+
+ \a resourceState must specify the
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_resource_states}{current state}
+ of the texture resource.
+
+ \note This function must be called on the scene graph rendering thread.
+
+ \sa QQuickWindow::sceneGraphInitialized(), QSGTexture,
+ {Scene Graph - Metal Texture Import}, {Scene Graph - Vulkan Texture Import}
+
+ \since 6.6
+ */
+QSGTexture *QSGD3D12Texture::fromNative(void *texture,
+ int resourceState,
+ QQuickWindow *window,
+ const QSize &size,
+ QQuickWindow::CreateTextureOptions options)
+{
+ return QQuickWindowPrivate::get(window)->createTextureFromNativeTexture(quint64(texture), resourceState, size, options);
+}
+} // QNativeInterface
+
+int QSGTexturePlatformD3D12::nativeResourceState() const
+{
+ if (auto *tex = m_texture->rhiTexture())
+ return tex->nativeTexture().layout;
+ return 0;
+}
+
+void *QSGTexturePlatformD3D12::nativeTexture() const
+{
+ if (auto *tex = m_texture->rhiTexture())
+ return reinterpret_cast<void *>(quintptr(tex->nativeTexture().object));
+ return 0;
+}
+
#endif // win
#if defined(__OBJC__) || defined(Q_QDOC)
@@ -846,6 +917,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting Metal texture objects.
\since 6.0
*/
@@ -893,6 +965,7 @@ namespace QNativeInterface {
\inmodule QtQuick
\ingroup native-interfaces
\ingroup native-interfaces-qsgtexture
+ \inheaderfile QSGTexture
\brief Provides access to and enables adopting Vulkan image objects.
\since 6.0
*/
@@ -974,11 +1047,12 @@ void *QSGTexture::resolveInterface(const char *name, int revision) const
#if QT_CONFIG(vulkan)
QT_NATIVE_INTERFACE_RETURN_IF(QSGVulkanTexture, &dd->m_vulkanTextureAccessor);
#endif
-#if defined(__OBJC__)
+#if QT_CONFIG(metal)
QT_NATIVE_INTERFACE_RETURN_IF(QSGMetalTexture, &dd->m_metalTextureAccessor);
#endif
#if defined(Q_OS_WIN)
QT_NATIVE_INTERFACE_RETURN_IF(QSGD3D11Texture, &dd->m_d3d11TextureAccessor);
+ QT_NATIVE_INTERFACE_RETURN_IF(QSGD3D12Texture, &dd->m_d3d12TextureAccessor);
#endif
#if QT_CONFIG(opengl)
QT_NATIVE_INTERFACE_RETURN_IF(QSGOpenGLTexture, &dd->m_openglTextureAccessor);
diff --git a/src/quick/scenegraph/coreapi/qsgtexture_mac.mm b/src/quick/scenegraph/coreapi/qsgtexture_mac.mm
index fb3c9fcf04..c68a1b732f 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture_mac.mm
+++ b/src/quick/scenegraph/coreapi/qsgtexture_mac.mm
@@ -6,7 +6,7 @@
#include <private/qquickitem_p.h>
#include <private/qquickwindow_p.h>
#include <QtCore/private/qnativeinterface_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgtexture_p.h b/src/quick/scenegraph/coreapi/qsgtexture_p.h
index 8e57a174c6..178bd23265 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture_p.h
+++ b/src/quick/scenegraph/coreapi/qsgtexture_p.h
@@ -39,7 +39,7 @@ bool operator!=(const QSGSamplerDescription &a, const QSGSamplerDescription &b)
size_t qHash(const QSGSamplerDescription &s, size_t seed = 0) noexcept;
#if QT_CONFIG(opengl)
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformOpenGL : public QNativeInterface::QSGOpenGLTexture
+class Q_QUICK_EXPORT QSGTexturePlatformOpenGL : public QNativeInterface::QSGOpenGLTexture
{
public:
QSGTexturePlatformOpenGL(QSGTexture *t) : m_texture(t) { }
@@ -50,7 +50,7 @@ public:
#endif
#ifdef Q_OS_WIN
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformD3D11 : public QNativeInterface::QSGD3D11Texture
+class Q_QUICK_EXPORT QSGTexturePlatformD3D11 : public QNativeInterface::QSGD3D11Texture
{
public:
QSGTexturePlatformD3D11(QSGTexture *t) : m_texture(t) { }
@@ -58,21 +58,30 @@ public:
void *nativeTexture() const override;
};
+class Q_QUICK_EXPORT QSGTexturePlatformD3D12 : public QNativeInterface::QSGD3D12Texture
+{
+public:
+ QSGTexturePlatformD3D12(QSGTexture *t) : m_texture(t) { }
+ QSGTexture *m_texture;
+
+ int nativeResourceState() const override;
+ void *nativeTexture() const override;
+};
#endif
-#if defined(__OBJC__)
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformMetal : public QNativeInterface::QSGMetalTexture
+#if QT_CONFIG(metal)
+class Q_QUICK_EXPORT QSGTexturePlatformMetal : public QNativeInterface::QSGMetalTexture
{
public:
QSGTexturePlatformMetal(QSGTexture *t) : m_texture(t) { }
QSGTexture *m_texture;
- id<MTLTexture> nativeTexture() const override;
+ QT_OBJC_PROTOCOL(MTLTexture) nativeTexture() const override;
};
#endif
#if QT_CONFIG(vulkan)
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePlatformVulkan : public QNativeInterface::QSGVulkanTexture
+class Q_QUICK_EXPORT QSGTexturePlatformVulkan : public QNativeInterface::QSGVulkanTexture
{
public:
QSGTexturePlatformVulkan(QSGTexture *t) : m_texture(t) { }
@@ -83,7 +92,7 @@ public:
};
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGTexturePrivate : public QObjectPrivate
+class Q_QUICK_EXPORT QSGTexturePrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QSGTexture)
public:
@@ -110,8 +119,9 @@ public:
#endif
#ifdef Q_OS_WIN
QSGTexturePlatformD3D11 m_d3d11TextureAccessor;
+ QSGTexturePlatformD3D12 m_d3d12TextureAccessor;
#endif
-#if defined(__OBJC__)
+#if QT_CONFIG(metal)
QSGTexturePlatformMetal m_metalTextureAccessor;
#endif
#if QT_CONFIG(vulkan)
@@ -119,7 +129,7 @@ public:
#endif
};
-Q_QUICK_PRIVATE_EXPORT bool qsg_safeguard_texture(QSGTexture *);
+Q_QUICK_EXPORT bool qsg_safeguard_texture(QSGTexture *);
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgtexture_platform.h b/src/quick/scenegraph/coreapi/qsgtexture_platform.h
index e8aef9078f..63c35c381d 100644
--- a/src/quick/scenegraph/coreapi/qsgtexture_platform.h
+++ b/src/quick/scenegraph/coreapi/qsgtexture_platform.h
@@ -15,8 +15,14 @@
#include <QtGui/qvulkaninstance.h>
#endif
-#if defined(__OBJC__) || defined(Q_QDOC)
-@protocol MTLTexture;
+#if QT_CONFIG(metal) || defined(Q_QDOC)
+# if defined(__OBJC__) || defined(Q_QDOC)
+ @protocol MTLTexture;
+# define QT_OBJC_PROTOCOL(protocol) id<protocol>
+# else
+ typedef struct objc_object *id;
+# define QT_OBJC_PROTOCOL(protocol) id
+# endif
#endif
QT_BEGIN_NAMESPACE
@@ -49,14 +55,25 @@ struct Q_QUICK_EXPORT QSGD3D11Texture
const QSize &size,
QQuickWindow::CreateTextureOptions options = {});
};
+struct Q_QUICK_EXPORT QSGD3D12Texture
+{
+ QT_DECLARE_NATIVE_INTERFACE(QSGD3D12Texture, 1, QSGTexture)
+ virtual void *nativeTexture() const = 0;
+ virtual int nativeResourceState() const = 0;
+ static QSGTexture *fromNative(void *texture,
+ int resourceState,
+ QQuickWindow *window,
+ const QSize &size,
+ QQuickWindow::CreateTextureOptions options = {});
+};
#endif
-#if defined(__OBJC__) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
struct Q_QUICK_EXPORT QSGMetalTexture
{
QT_DECLARE_NATIVE_INTERFACE(QSGMetalTexture, 1, QSGTexture)
- virtual id<MTLTexture> nativeTexture() const = 0;
- static QSGTexture *fromNative(id<MTLTexture> texture,
+ virtual QT_OBJC_PROTOCOL(MTLTexture) nativeTexture() const = 0;
+ static QSGTexture *fromNative(QT_OBJC_PROTOCOL(MTLTexture) texture,
QQuickWindow *window,
const QSize &size,
QQuickWindow::CreateTextureOptions options = {});
diff --git a/src/quick/scenegraph/qsgadaptationlayer.cpp b/src/quick/scenegraph/qsgadaptationlayer.cpp
index c20a126dce..9ce21d6b8f 100644
--- a/src/quick/scenegraph/qsgadaptationlayer.cpp
+++ b/src/quick/scenegraph/qsgadaptationlayer.cpp
@@ -18,6 +18,13 @@
QT_BEGIN_NAMESPACE
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_update_entry, int count)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_update_exit)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphRender_entry)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphRender_exit)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphStore_entry)
+Q_TRACE_POINT(qtquick, QSGDistanceFieldGlyphCache_glyphStore_exit)
+
static QElapsedTimer qsg_render_timer;
QSGDistanceFieldGlyphCache::Texture QSGDistanceFieldGlyphCache::s_emptyTexture;
@@ -226,16 +233,6 @@ void QSGDistanceFieldGlyphCache::setGlyphsPosition(const QList<GlyphPosition> &g
}
}
-void QSGDistanceFieldGlyphCache::registerOwnerElement(QQuickItem *ownerElement)
-{
- Q_UNUSED(ownerElement);
-}
-
-void QSGDistanceFieldGlyphCache::unregisterOwnerElement(QQuickItem *ownerElement)
-{
- Q_UNUSED(ownerElement);
-}
-
void QSGDistanceFieldGlyphCache::processPendingGlyphs()
{
/* Intentionally empty */
diff --git a/src/quick/scenegraph/qsgadaptationlayer_p.h b/src/quick/scenegraph/qsgadaptationlayer_p.h
index 0e3d32528e..117e810411 100644
--- a/src/quick/scenegraph/qsgadaptationlayer_p.h
+++ b/src/quick/scenegraph/qsgadaptationlayer_p.h
@@ -31,7 +31,7 @@
#include <QtGui/private/qdatabuffer_p.h>
#include <private/qdistancefield_p.h>
#include <private/qintrusivelist_p.h>
-#include <QtGui/private/qshader_p.h>
+#include <rhi/qshader.h>
// ### remove
#include <QtQuick/private/qquicktext_p.h>
@@ -52,7 +52,7 @@ class QSGRenderNode;
class QSGRenderContext;
class QRhiTexture;
-class Q_QUICK_PRIVATE_EXPORT QSGNodeVisitorEx
+class Q_QUICK_EXPORT QSGNodeVisitorEx
{
public:
virtual ~QSGNodeVisitorEx();
@@ -89,7 +89,7 @@ public:
};
-class Q_QUICK_PRIVATE_EXPORT QSGVisitableNode : public QSGGeometryNode
+class Q_QUICK_EXPORT QSGVisitableNode : public QSGGeometryNode
{
public:
QSGVisitableNode() { setFlag(IsVisitableNode); }
@@ -98,7 +98,7 @@ public:
virtual void accept(QSGNodeVisitorEx *) = 0;
};
-class Q_QUICK_PRIVATE_EXPORT QSGInternalRectangleNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGInternalRectangleNode : public QSGVisitableNode
{
public:
~QSGInternalRectangleNode() override;
@@ -110,6 +110,10 @@ public:
virtual void setGradientStops(const QGradientStops &stops) = 0;
virtual void setGradientVertical(bool vertical) = 0;
virtual void setRadius(qreal radius) = 0;
+ virtual void setTopLeftRadius(qreal radius) = 0;
+ virtual void setTopRightRadius(qreal radius) = 0;
+ virtual void setBottomLeftRadius(qreal radius) = 0;
+ virtual void setBottomRightRadius(qreal radius) = 0;
virtual void setAntialiasing(bool antialiasing) { Q_UNUSED(antialiasing); }
virtual void setAligned(bool aligned) = 0;
@@ -119,7 +123,7 @@ public:
};
-class Q_QUICK_PRIVATE_EXPORT QSGInternalImageNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGInternalImageNode : public QSGVisitableNode
{
public:
~QSGInternalImageNode() override;
@@ -144,7 +148,7 @@ public:
void accept(QSGNodeVisitorEx *visitor) override { if (visitor->visit(this)) visitor->visitChildren(this); visitor->endVisit(this); }
};
-class Q_QUICK_PRIVATE_EXPORT QSGPainterNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGPainterNode : public QSGVisitableNode
{
public:
~QSGPainterNode() override;
@@ -205,7 +209,7 @@ protected:
#if QT_CONFIG(quick_sprite)
-class Q_QUICK_PRIVATE_EXPORT QSGSpriteNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGSpriteNode : public QSGVisitableNode
{
public:
~QSGSpriteNode() override;
@@ -226,7 +230,7 @@ public:
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGGuiThreadShaderEffectManager : public QObject
+class Q_QUICK_EXPORT QSGGuiThreadShaderEffectManager : public QObject
{
Q_OBJECT
@@ -267,7 +271,6 @@ public:
QShader rhiShader;
Type type;
QVector<Variable> variables;
- uint constantDataSize;
// Vertex inputs are not tracked here as QSGGeometry::AttributeSet
// hardwires that anyways so it is up to the shader to provide
@@ -283,10 +286,10 @@ Q_SIGNALS:
};
#ifndef QT_NO_DEBUG_STREAM
-Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &v);
+Q_QUICK_EXPORT QDebug operator<<(QDebug debug, const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &v);
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGShaderEffectNode : public QObject, public QSGVisitableNode
+class Q_QUICK_EXPORT QSGShaderEffectNode : public QObject, public QSGVisitableNode
{
Q_OBJECT
@@ -337,6 +340,7 @@ public:
ShaderSyncData vertex;
ShaderSyncData fragment;
void *materialTypeCacheKey;
+ qint8 viewCount;
};
// Each ShaderEffect item has one node (render thread) and one manager (gui thread).
@@ -353,10 +357,10 @@ Q_SIGNALS:
Q_DECLARE_OPERATORS_FOR_FLAGS(QSGShaderEffectNode::DirtyShaderFlags)
#ifndef QT_NO_DEBUG_STREAM
-Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug debug, const QSGShaderEffectNode::VariableData &vd);
+Q_QUICK_EXPORT QDebug operator<<(QDebug debug, const QSGShaderEffectNode::VariableData &vd);
#endif
-class Q_QUICK_PRIVATE_EXPORT QSGGlyphNode : public QSGVisitableNode
+class Q_QUICK_EXPORT QSGGlyphNode : public QSGVisitableNode
{
public:
enum AntialiasingMode
@@ -384,16 +388,12 @@ public:
virtual void update() = 0;
- void setOwnerElement(QQuickItem *ownerElement) { m_ownerElement = ownerElement; }
- QQuickItem *ownerElement() const { return m_ownerElement; }
-
void accept(QSGNodeVisitorEx *visitor) override { if (visitor->visit(this)) visitor->visitChildren(this); visitor->endVisit(this); }
protected:
QRectF m_bounding_rect;
- QQuickItem *m_ownerElement = nullptr;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldGlyphConsumer
+class Q_QUICK_EXPORT QSGDistanceFieldGlyphConsumer
{
public:
virtual ~QSGDistanceFieldGlyphConsumer();
@@ -403,7 +403,7 @@ public:
};
typedef QIntrusiveList<QSGDistanceFieldGlyphConsumer, &QSGDistanceFieldGlyphConsumer::node> QSGDistanceFieldGlyphConsumerList;
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldGlyphCache
+class Q_QUICK_EXPORT QSGDistanceFieldGlyphCache
{
public:
QSGDistanceFieldGlyphCache(const QRawFont &font,
@@ -468,8 +468,6 @@ public:
void registerGlyphNode(QSGDistanceFieldGlyphConsumer *node) { m_registeredNodes.insert(node); }
void unregisterGlyphNode(QSGDistanceFieldGlyphConsumer *node) { m_registeredNodes.remove(node); }
- virtual void registerOwnerElement(QQuickItem *ownerElement);
- virtual void unregisterOwnerElement(QQuickItem *ownerElement);
virtual void processPendingGlyphs();
virtual bool eightBitFormatIsAlphaSwizzled() const = 0;
diff --git a/src/quick/scenegraph/qsgbasicglyphnode_p.h b/src/quick/scenegraph/qsgbasicglyphnode_p.h
index 5762673ce6..2063a2476c 100644
--- a/src/quick/scenegraph/qsgbasicglyphnode_p.h
+++ b/src/quick/scenegraph/qsgbasicglyphnode_p.h
@@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE
class QSGMaterial;
-class Q_QUICK_PRIVATE_EXPORT QSGBasicGlyphNode: public QSGGlyphNode
+class Q_QUICK_EXPORT QSGBasicGlyphNode: public QSGGlyphNode
{
public:
QSGBasicGlyphNode();
diff --git a/src/quick/scenegraph/qsgbasicinternalimagenode.cpp b/src/quick/scenegraph/qsgbasicinternalimagenode.cpp
index 8eb39b5f06..5c49bdb54a 100644
--- a/src/quick/scenegraph/qsgbasicinternalimagenode.cpp
+++ b/src/quick/scenegraph/qsgbasicinternalimagenode.cpp
@@ -10,13 +10,13 @@ QT_BEGIN_NAMESPACE
namespace
{
- struct SmoothVertex
+ struct SmoothImageVertex
{
float x, y, u, v;
float dx, dy, du, dv;
};
- const QSGGeometry::AttributeSet &smoothAttributeSet()
+ const QSGGeometry::AttributeSet &smoothImageAttributeSet()
{
static QSGGeometry::Attribute data[] = {
QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
@@ -24,7 +24,7 @@ namespace
QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::TexCoord1Attribute),
QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::TexCoord2Attribute)
};
- static QSGGeometry::AttributeSet attrs = { 4, sizeof(SmoothVertex), data };
+ static QSGGeometry::AttributeSet attrs = { 4, sizeof(SmoothImageVertex), data };
return attrs;
}
}
@@ -97,7 +97,7 @@ void QSGBasicInternalImageNode::setAntialiasing(bool antialiasing)
return;
m_antialiasing = antialiasing;
if (m_antialiasing) {
- setGeometry(new QSGGeometry(smoothAttributeSet(), 0));
+ setGeometry(new QSGGeometry(smoothImageAttributeSet(), 0));
setFlag(OwnsGeometry, true);
} else {
setGeometry(&m_geometry);
@@ -308,7 +308,7 @@ QSGGeometry *QSGBasicInternalImageNode::updateGeometry(const QRectF &targetRect,
if (antialiasing) {
if (!geometry || geometry->indexType() != indexType) {
- geometry = new QSGGeometry(smoothAttributeSet(),
+ geometry = new QSGGeometry(smoothImageAttributeSet(),
hCells * vCells * 4 + (hCells + vCells - 1) * 4,
hCells * vCells * 6 + (hCells + vCells) * 12,
indexType);
@@ -320,7 +320,7 @@ QSGGeometry *QSGBasicInternalImageNode::updateGeometry(const QRectF &targetRect,
Q_ASSERT(g);
g->setDrawingMode(QSGGeometry::DrawTriangles);
- SmoothVertex *vertices = reinterpret_cast<SmoothVertex *>(g->vertexData());
+ auto *vertices = reinterpret_cast<SmoothImageVertex *>(g->vertexData());
memset(vertices, 0, g->vertexCount() * g->sizeOfVertex());
void *indexData = g->indexData();
@@ -360,7 +360,7 @@ QSGGeometry *QSGBasicInternalImageNode::updateGeometry(const QRectF &targetRect,
bool isLeft = i == 0;
bool isRight = i == hCells - 1;
- SmoothVertex *v = vertices + index;
+ SmoothImageVertex *v = vertices + index;
int topLeft = index;
for (int k = (isTop || isLeft ? 2 : 1); k--; ++v, ++index) {
@@ -521,12 +521,12 @@ void QSGBasicInternalImageNode::updateGeometry()
QSGGeometry *g = geometry();
Q_ASSERT(g != &m_geometry);
if (g->indexType() != QSGGeometry::UnsignedShortType) {
- setGeometry(new QSGGeometry(smoothAttributeSet(), 0));
+ setGeometry(new QSGGeometry(smoothImageAttributeSet(), 0));
g = geometry();
}
g->allocate(8, 14);
g->setDrawingMode(QSGGeometry::DrawTriangleStrip);
- SmoothVertex *vertices = reinterpret_cast<SmoothVertex *>(g->vertexData());
+ auto *vertices = reinterpret_cast<SmoothImageVertex *>(g->vertexData());
float delta = float(qAbs(m_targetRect.width()) < qAbs(m_targetRect.height())
? m_targetRect.width() : m_targetRect.height()) * 0.5f;
float sx = float(sr.width() / m_targetRect.width());
diff --git a/src/quick/scenegraph/qsgbasicinternalimagenode_p.h b/src/quick/scenegraph/qsgbasicinternalimagenode_p.h
index c4aaba02fd..f5b43091c8 100644
--- a/src/quick/scenegraph/qsgbasicinternalimagenode_p.h
+++ b/src/quick/scenegraph/qsgbasicinternalimagenode_p.h
@@ -19,7 +19,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGBasicInternalImageNode : public QSGInternalImageNode
+class Q_QUICK_EXPORT QSGBasicInternalImageNode : public QSGInternalImageNode
{
public:
QSGBasicInternalImageNode();
diff --git a/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp b/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp
index 9ce0ceefbc..8d1104a407 100644
--- a/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp
+++ b/src/quick/scenegraph/qsgbasicinternalrectanglenode.cpp
@@ -74,8 +74,7 @@ namespace
}
QSGBasicInternalRectangleNode::QSGBasicInternalRectangleNode()
- : m_radius(0)
- , m_pen_width(0)
+ : QSGInternalRectangleNode()
, m_aligned(true)
, m_antialiasing(false)
, m_gradient_is_opaque(true)
@@ -155,6 +154,35 @@ void QSGBasicInternalRectangleNode::setRadius(qreal radius)
m_dirty_geometry = true;
}
+void QSGBasicInternalRectangleNode::setTopLeftRadius(qreal radius)
+{
+ if (radius == m_topLeftRadius)
+ return;
+ m_topLeftRadius = radius;
+ m_dirty_geometry = true;
+}
+void QSGBasicInternalRectangleNode::setTopRightRadius(qreal radius)
+{
+ if (radius == m_topRightRadius)
+ return;
+ m_topRightRadius = radius;
+ m_dirty_geometry = true;
+}
+void QSGBasicInternalRectangleNode::setBottomLeftRadius(qreal radius)
+{
+ if (radius == m_bottomLeftRadius)
+ return;
+ m_bottomLeftRadius = radius;
+ m_dirty_geometry = true;
+}
+void QSGBasicInternalRectangleNode::setBottomRightRadius(qreal radius)
+{
+ if (radius == m_bottomRightRadius)
+ return;
+ m_bottomRightRadius = radius;
+ m_dirty_geometry = true;
+}
+
void QSGBasicInternalRectangleNode::setAntialiasing(bool antialiasing)
{
if (!supportsAntialiasing())
@@ -217,33 +245,93 @@ void QSGBasicInternalRectangleNode::updateGeometry()
Color4ub transparent = { 0, 0, 0, 0 };
const QGradientStops &stops = m_gradient_stops;
- float length = (m_gradient_is_vertical ? height : width);
+ float gradientStart = (m_gradient_is_vertical ? m_rect.top() : m_rect.left());
+ float gradientLength = (m_gradient_is_vertical ? height : width);
float secondaryLength = (m_gradient_is_vertical ? width : height);
int nextGradientStop = 0;
- float gradientPos = penWidth / length;
+ float gradientPos = penWidth / gradientLength;
while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos)
++nextGradientStop;
int lastGradientStop = stops.size() - 1;
- float lastGradientPos = 1.0f - penWidth / length;
+ float lastGradientPos = 1.0f - penWidth / gradientLength;
while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos)
--lastGradientStop;
int gradientIntersections = (lastGradientStop - nextGradientStop + 1);
- if (m_radius > 0) {
+ if (m_radius > 0
+ || m_topLeftRadius > 0
+ || m_topRightRadius > 0
+ || m_bottomLeftRadius > 0
+ || m_bottomRightRadius > 0) {
// Rounded corners.
- // Radius should never exceeds half of the width or half of the height
- float radius = qMin(qMin(width, height) * 0.5f, float(m_radius));
- QRectF innerRect = m_rect;
- innerRect.adjust(radius, radius, -radius, -radius);
-
- float innerRadius = radius - penWidth * 1.0f;
- float outerRadius = radius;
- float delta = qMin(width, height) * 0.5f;
+ // Radius should never exceed half the width or half the height.
+ float radiusTL = qMin(qMin(width, height) * 0.4999f, float(m_topLeftRadius < 0 ? m_radius : m_topLeftRadius));
+ float radiusTR = qMin(qMin(width, height) * 0.4999f, float(m_topRightRadius < 0 ? m_radius : m_topRightRadius));
+ float radiusBL = qMin(qMin(width, height) * 0.4999f, float(m_bottomLeftRadius < 0 ? m_radius : m_bottomLeftRadius));
+ float radiusBR = qMin(qMin(width, height) * 0.4999f, float(m_bottomRightRadius < 0 ? m_radius : m_bottomRightRadius));
+
+ // The code produces some artefacts when radius <= 0.5. A radius of half a pixel
+ // does not make much sense anyway, so we draw a normal corner in such a case.
+ if (radiusTL <= 0.5)
+ radiusTL = 0;
+ if (radiusTR <= 0.5)
+ radiusTR = 0;
+ if (radiusBL <= 0.5)
+ radiusBL = 0;
+ if (radiusBR <= 0.5)
+ radiusBR = 0;
+
+ // We want to keep a minimal inner radius in order to make the inner
+ // x-coordinates of an arc mathematically unique and identifiable.
+ const float innerRadiusTL = qMax(radiusTL - penWidth * 1.0f, 0.01);
+ const float innerRadiusTR = qMax(radiusTR - penWidth * 1.0f, 0.01);
+ const float innerRadiusBL = qMax(radiusBL - penWidth * 1.0f, 0.01);
+ const float innerRadiusBR = qMax(radiusBR - penWidth * 1.0f, 0.01);
+ const float outerRadiusTL = radiusTL;
+ const float outerRadiusTR = radiusTR;
+ const float outerRadiusBL = radiusBL;
+ const float outerRadiusBR = radiusBR;
+ const float delta = qMin(width, height) * 0.5f;
+
+ int segmentsTL = radiusTL == 0 ? 0 : qBound(3, qCeil(radiusTL * (M_PI / 6)), 18);
+ int segmentsTR = radiusTR == 0 ? 0 : qBound(3, qCeil(radiusTR * (M_PI / 6)), 18);
+ int segmentsBL = radiusBL == 0 ? 0 : qBound(3, qCeil(radiusBL * (M_PI / 6)), 18);
+ int segmentsBR = radiusBR == 0 ? 0 : qBound(3, qCeil(radiusBR * (M_PI / 6)), 18);
+
+ // If the radii on opposite sites in genraration direction are the same,
+ // we will set the segments of one side to 0 as these points would be
+ // calculated twice. Also, this optimizes for the case of similar radii
+ if (m_gradient_is_vertical) {
+ if (innerRadiusTL == innerRadiusTR) {
+ if (segmentsTL <= segmentsTR)
+ segmentsTL = 0;
+ else
+ segmentsTR = 0;
+ }
+ if (innerRadiusBL == innerRadiusBR){
+ if (segmentsBL <= segmentsBR)
+ segmentsBL = 0;
+ else
+ segmentsBR = 0;
+ }
+ } else {
+ if (innerRadiusTL == innerRadiusBL) {
+ if (segmentsTL <= segmentsBL)
+ segmentsTL = 0;
+ else
+ segmentsBL = 0;
+ }
+ if (innerRadiusTR == innerRadiusBR) {
+ if (segmentsTR <= segmentsBR)
+ segmentsTR = 0;
+ else
+ segmentsBR = 0;
+ }
+ }
- // Number of segments per corner, approximately one per 3 pixels.
- int segments = qBound(3, qCeil(outerRadius * (M_PI / 6)), 18);
+ const int sumSegments = segmentsTL + segmentsTR + segmentsBL + segmentsBR;
/*
@@ -265,8 +353,8 @@ void QSGBasicInternalRectangleNode::updateGeometry()
*/
- int innerVertexCount = (segments + 1) * 4 + gradientIntersections * 2;
- int outerVertexCount = (segments + 1) * 4;
+ const int innerVertexCount = (sumSegments + 4) * 2 + gradientIntersections * 2;
+ const int outerVertexCount = (sumSegments + 4) * 2;
int vertexCount = innerVertexCount;
if (m_antialiasing || penWidth)
vertexCount += innerVertexCount;
@@ -275,10 +363,11 @@ void QSGBasicInternalRectangleNode::updateGeometry()
if (m_antialiasing && penWidth)
vertexCount += outerVertexCount;
- int fillIndexCount = innerVertexCount;
- int innerAAIndexCount = innerVertexCount * 2 + 2;
- int borderIndexCount = innerVertexCount * 2 + 2;
- int outerAAIndexCount = outerVertexCount * 2 + 2;
+
+ const int fillIndexCount = innerVertexCount;
+ const int innerAAIndexCount = innerVertexCount * 2 + 2;
+ const int borderIndexCount = innerVertexCount * 2 + 2;
+ const int outerAAIndexCount = outerVertexCount * 2 + 2;
int indexCount = 0;
int fillHead = 0;
int innerAAHead = 0;
@@ -309,48 +398,322 @@ void QSGBasicInternalRectangleNode::updateGeometry()
quint16 *indices = g->indexDataAsUShort();
quint16 index = 0;
- float pp = 0; // previous inner primary coordinate.
- float pss = 0; // previous inner secondary start coordinate.
- float pse = 0; // previous inner secondary end coordinate.
- float angle = 0.5f * float(M_PI) / segments;
- float cosStep = qFastCos(angle);
- float sinStep = qFastSin(angle);
-
- float innerStart = (m_gradient_is_vertical ? innerRect.top() : innerRect.left());
- float innerEnd = (m_gradient_is_vertical ? innerRect.bottom() : innerRect.right());
- float innerLength = (m_gradient_is_vertical ? innerRect.height() : innerRect.width());
- float innerSecondaryStart = (m_gradient_is_vertical ? innerRect.left() : innerRect.top());
- float innerSecondaryEnd = (m_gradient_is_vertical ? innerRect.right() : innerRect.bottom());
+ float innerXPrev = 0.; // previous inner primary coordinate, both sides.
+ float innerYLeftPrev = 0.; // previous inner secondary coordinate, left.
+ float innerYRightPrev = 0.; // previous inner secondary coordinate, right.
+
+ const float angleTL = 0.5f * float(M_PI) / segmentsTL;
+ const float cosStepTL = qFastCos(angleTL);
+ const float sinStepTL = qFastSin(angleTL);
+ const float angleTR = 0.5f * float(M_PI) / segmentsTR;
+ const float cosStepTR = qFastCos(angleTR);
+ const float sinStepTR = qFastSin(angleTR);
+ const float angleBL = 0.5f * float(M_PI) / segmentsBL;
+ const float cosStepBL = qFastCos(angleBL);
+ const float sinStepBL = qFastSin(angleBL);
+ const float angleBR = 0.5f * float(M_PI) / segmentsBR;
+ const float cosStepBR = qFastCos(angleBR);
+ const float sinStepBR = qFastSin(angleBR);
+
+ //The x- and y-Axis are transposed, depending on gradient being vertical or horizontal
+ //Lets define some coordinates and radii. The first index is the part, the second index
+ //is the left or right side, as seen when moving from part0 to part1
+
+ // left vertices | right vertices
+ // |
+ // *************|**************
+ // * | | | *
+ // *--o | o--*
+ // * innerX/Y | innerX/Y *
+ // * | *
+ // * | * part 0
+ // * | *
+ // * | *
+ // * | *
+ // -----------------+--------------------> y
+ // * | *
+ // * | *
+ // * | *
+ // * | * part 1
+ // * | *
+ // * innerX/Y | innerX/Y *
+ // *--o | o--*
+ // * | | | *
+ // *************|**************
+ // |
+ // v x
+ //
+ // direction of vertex generation
+
+ const float outerXCenter[][2] = {{
+ float(m_gradient_is_vertical ? m_rect.top() + radiusTL : m_rect.left() + radiusTL),
+ float(m_gradient_is_vertical ? m_rect.top() + radiusTR : m_rect.left() + radiusBL)
+ }, {
+ float(m_gradient_is_vertical ? m_rect.bottom() - radiusBL : m_rect.right() - radiusTR),
+ float(m_gradient_is_vertical ? m_rect.bottom() - radiusBR : m_rect.right() - radiusBR)
+ }};
+
+ const float outerYCenter[][2] = {{
+ float(!m_gradient_is_vertical ? m_rect.top() + outerRadiusTL : m_rect.left() + outerRadiusTL),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - outerRadiusBL : m_rect.right() - outerRadiusTR)
+ }, {
+ float(!m_gradient_is_vertical ? m_rect.top() + outerRadiusTR : m_rect.left() + outerRadiusBL),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - outerRadiusBR : m_rect.right() - outerRadiusBR)
+ }};
+
+ const float innerXCenter[][2] = { {
+ float(m_gradient_is_vertical ? m_rect.top() + innerRadiusTL + penWidth : m_rect.left() + innerRadiusTL + penWidth),
+ float(m_gradient_is_vertical ? m_rect.top() + innerRadiusTR + penWidth: m_rect.left() + innerRadiusBL + penWidth)
+ }, {
+ float(m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBL - penWidth: m_rect.right() - innerRadiusTR - penWidth),
+ float(m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBR - penWidth: m_rect.right() - innerRadiusBR - penWidth)
+ }};
+
+ const float innerYCenter[][2] = { {
+ float(!m_gradient_is_vertical ? m_rect.top() + innerRadiusTL + penWidth : m_rect.left() + innerRadiusTL + penWidth),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBL - penWidth : m_rect.right() - innerRadiusTR - penWidth)
+ },{
+ float(!m_gradient_is_vertical ? m_rect.top() + innerRadiusTR + penWidth : m_rect.left() + innerRadiusBL + penWidth),
+ float(!m_gradient_is_vertical ? m_rect.bottom() - innerRadiusBR - penWidth : m_rect.right() - innerRadiusBR - penWidth)
+ }};
+
+ const float innerRadius[][2] = {{
+ innerRadiusTL,
+ !m_gradient_is_vertical ? innerRadiusBL : innerRadiusTR
+ }, {
+ !m_gradient_is_vertical ? innerRadiusTR : innerRadiusBL,
+ innerRadiusBR
+ }};
+
+ const float outerRadius[][2] = {{
+ outerRadiusTL,
+ !m_gradient_is_vertical ? outerRadiusBL : outerRadiusTR
+ }, {
+ !m_gradient_is_vertical ? outerRadiusTR : outerRadiusBL,
+ outerRadiusBR
+ }};
+
+ const int segments[][2] = {{
+ segmentsTL,
+ !m_gradient_is_vertical ? segmentsBL : segmentsTR
+ }, {
+ !m_gradient_is_vertical ? segmentsTR : segmentsBL,
+ segmentsBR
+ }};
+
+ const float cosStep[][2] = {{
+ cosStepTL,
+ !m_gradient_is_vertical ? cosStepBL : cosStepTR
+ }, {
+ !m_gradient_is_vertical ? cosStepTR : cosStepBL,
+ cosStepBR
+ }};
+
+ const float sinStep[][2] = {{
+ sinStepTL,
+ !m_gradient_is_vertical ? sinStepBL : sinStepTR
+ }, {
+ !m_gradient_is_vertical ? sinStepTR : sinStepBL,
+ sinStepBR
+ }};
+
+ auto fillColorFromX = [&](float x) {
+
+ float t = (x - gradientStart) / gradientLength;
+ t = qBound(0.0, t, 1.0);
+
+ int i = 1;
+ if (t < stops.first().first)
+ return colorToColor4ub(stops.first().second);
+ while (i < stops.size()) {
+ const QGradientStop &prev = stops.at(i - 1);
+ const QGradientStop &next = stops.at(i);
+ if (prev.first <= t && next.first > t) {
+ t = (t - prev.first) / (next.first - prev.first);
+ return colorToColor4ub(prev.second) * (1. - t) + colorToColor4ub(next.second) * t; }
+ i++;
+ }
+ return colorToColor4ub(stops.last().second);
+ };
for (int part = 0; part < 2; ++part) {
- float c = 1 - part;
- float s = part;
- for (int i = 0; i <= segments; ++i) {
- float p, ss, se;
- if (innerRadius > 0) {
- p = (part ? innerEnd : innerStart) - innerRadius * c; // current inner primary coordinate.
- ss = innerSecondaryStart - innerRadius * s; // current inner secondary start coordinate.
- se = innerSecondaryEnd + innerRadius * s; // current inner secondary end coordinate.
- gradientPos = ((part ? innerLength : 0) + radius - innerRadius * c) / length;
+ // cosine of the angle of the current segment, starting at 1 for part 0 and 0 for part 1
+ float cosSegmentAngleLeft = 1. - part;
+ // sine of the angle of the current segment
+ float sinSegmentAngleLeft = part;
+
+ float cosSegmentAngleRight = 1. - part;
+ float sinSegmentAngleRight = part;
+
+ bool advanceLeft = true;
+
+ // We draw both the left part and the right part of the rectangle at the same time.
+ // We also draw a vertex on the left side for every vertex on the right side. This
+ // syncronisation is required to make sure that all gradient stops can be inserted.
+ for (int iLeft = 0, iRight = 0; iLeft <= segments[part][0] || iRight <= segments[part][1]; ) {
+
+ float xLeft, yLeft,
+ xRight, yRight;
+
+ float outerXLeft, outerYLeft,
+ outerXRight, outerYRight;
+
+ float sinAngleLeft, cosAngleLeft,
+ sinAngleRight, cosAngleRight;
+
+ // calculate inner x-coordinates
+ xLeft = innerXCenter[part][0] - innerRadius[part][0] * cosSegmentAngleLeft;
+ xRight = innerXCenter[part][1] - innerRadius[part][1] * cosSegmentAngleRight;
+
+ // calcuate inner y-coordinates
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinSegmentAngleLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinSegmentAngleRight;
+
+ // Synchronize left and right hand x-coordinates. This is required to
+ // make sure that we can insert all gradient stops that require exactly two triangles at
+ // every x-coordinate. Take the smaller of both x-coordinates and then find the matching
+ // y-coordinates.
+ if ((iLeft <= segments[part][0] && xLeft <= xRight) || iRight > segments[part][1]) {
+ advanceLeft = true;
} else {
- p = (part ? innerEnd + innerRadius : innerStart - innerRadius); // current inner primary coordinate.
- ss = innerSecondaryStart - innerRadius; // current inner secondary start coordinate.
- se = innerSecondaryEnd + innerRadius; // current inner secondary end coordinate.
- gradientPos = ((part ? innerLength + innerRadius : -innerRadius) + radius) / length;
+ advanceLeft = false;
}
- float outerEdge = (part ? innerEnd : innerStart) - outerRadius * c; // current outer primary coordinate.
- float outerSecondaryStart = innerSecondaryStart - outerRadius * s; // current outer secondary start coordinate.
- float outerSecondaryEnd = innerSecondaryEnd + outerRadius * s; // current outer secondary end coordinate.
+ // Inner: Find the matching y-coordinates for the x-coordinate found above.
+ // Outer: Also set the sine and cosine to make sure that outer vertices are
+ // drawn correctly.
+ if (innerRadius[part][0] == innerRadius[part][1]) {
+ // Special case of equal radii. Optimize to avoid performance regression:
+ // Left and right is always equal and we can just copy the angles and
+ // mirror the coordinates.
+ if (advanceLeft) {
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ } else {
+ sinAngleLeft = sinSegmentAngleLeft;
+ cosAngleLeft = cosSegmentAngleLeft;
+ }
+ if (outerRadius[part][1] == 0) {
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ } else {
+ sinAngleRight = sinSegmentAngleLeft;
+ cosAngleRight = cosSegmentAngleLeft;
+ }
+ xRight = xLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinAngleRight;
+ } else {
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ } else {
+ sinAngleLeft = sinSegmentAngleRight;
+ cosAngleLeft = cosSegmentAngleRight;
+ }
+ if (outerRadius[part][1] == 0) {
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ } else {
+ sinAngleRight = sinSegmentAngleRight;
+ cosAngleRight = cosSegmentAngleRight;
+ }
+ xLeft = xRight;
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinAngleLeft;
+ }
+ } else if (advanceLeft) {
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ } else {
+ sinAngleLeft = sinSegmentAngleLeft;
+ cosAngleLeft = cosSegmentAngleLeft;
+ }
+ if (outerRadius[part][1] == 0) {
+ // Outer: If the outer radius is zero we can return both sin and cos = 1
+ // to form a nice corner. Inner: Accept the x-coordinate from the other
+ // side and match the y-coordinate
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ xRight = xLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinAngleRight;
+ } else if (xLeft >= innerXCenter[0][1] && xLeft <= innerXCenter[1][1]) {
+ // Outer: If we are on the straight line between the inner centers, we can
+ // just return sin = 1 and cos = 0. Inner: Accept the x-coordinate from the
+ // other side and match the y-coordinate
+ sinAngleRight = 1.;
+ cosAngleRight = 0.;
+ xRight = xLeft;
+ yRight = innerYCenter[part][1] + innerRadius[part][1] * sinAngleRight;
+ } else {
+ // Inner: If we are on the rounded part of the oposite side, we have to find a vertex
+ // on that curve that matches the x-coordinate we selected.
+ // We always select the smaller x-coordinate and can therefore use a linear
+ // interpolation between the last point on this side and the point on this side
+ // that was not accepted because it was too large.
+ if (xRight != innerXPrev) {
+ float t = (xLeft - innerXPrev) / (xRight - innerXPrev);
+ yRight = innerYRightPrev * (1. - t) + yRight * t;
+ xRight = xLeft;
+ }
+ // Outer: With the coordinates from the interpolation we can calculate the sine
+ // and cosine of the respective angle quickly.
+ sinAngleRight = (yRight - innerYCenter[part][1]) / innerRadius[part][1];
+ cosAngleRight = -(xRight - innerXCenter[part][1]) / innerRadius[part][1];
+ }
+ } else {
+ // same as above but for the other side.
+ if (outerRadius[part][1] == 0) {
+ sinAngleRight = 1.;
+ cosAngleRight = part ? -1. : 1.;
+ } else {
+ sinAngleRight = sinSegmentAngleRight;
+ cosAngleRight = cosSegmentAngleRight;
+ }
+ if (outerRadius[part][0] == 0) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = part ? -1. : 1.;
+ xLeft = xRight;
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinAngleLeft;
+ } else if (xRight >= innerXCenter[0][0] && xRight <= innerXCenter[1][0]) {
+ sinAngleLeft = 1.;
+ cosAngleLeft = 0.;
+ xLeft = xRight;
+ yLeft = innerYCenter[part][0] - innerRadius[part][0] * sinAngleLeft;
+ } else {
+ if (xLeft != innerXPrev) {
+ float t = (xRight - innerXPrev) / (xLeft - innerXPrev);
+ yLeft = innerYLeftPrev * (1. - t) + yLeft * t;
+ xLeft = xRight;
+ }
+ sinAngleLeft = -(yLeft - innerYCenter[part][0]) / innerRadius[part][0];
+ cosAngleLeft = -(xLeft - innerXCenter[part][0]) / innerRadius[part][0];
+ }
+ }
+
+ gradientPos = (xLeft - gradientStart) / gradientLength;
+
+ // calculate the matching outer coordinates
+ outerXLeft = outerXCenter[part][0] - outerRadius[part][0] * cosAngleLeft;
+ outerYLeft = outerYCenter[part][0] - outerRadius[part][0] * sinAngleLeft;
+ outerXRight = outerXCenter[part][1] - outerRadius[part][1] * cosAngleRight;
+ outerYRight = outerYCenter[part][1] + outerRadius[part][1] * sinAngleRight;
+
+ // insert gradient stops as required
while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
- // Insert vertices at gradient stops.
- float gp = (innerStart - radius) + stops.at(nextGradientStop).first * length;
- float t = (gp - pp) / (p - pp);
- float gis = pss * (1 - t) + t * ss; // gradient inner start
- float gie = pse * (1 - t) + t * se; // gradient inner end
+ float gradientX;
+ float gradientYLeft;
+ float gradientYRight;
+
+ // Insert vertices at gradient stops
+ gradientX = gradientStart + stops.at(nextGradientStop).first * gradientLength;
+ // bilinear interpolation of known vertices
+ float t = (gradientX - innerXPrev) / (xLeft - innerXPrev);
+ gradientYLeft = innerYLeftPrev * (1. - t) + t * yLeft;
+ gradientYRight = innerYRightPrev * (1. - t) + t * yRight;
- fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
+ fillColor = fillColorFromX(gradientX);
if (hasFill) {
indices[fillHead++] = index;
@@ -373,39 +736,35 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 3;
bool lower = stops.at(nextGradientStop).first > 0.5f;
- float dp = lower ? qMin(0.0f, length - gp - delta) : qMax(0.0f, delta - gp);
- smoothVertices[index++].set(gp, gie, fillColor, dp, secondaryLength - gie - delta, m_gradient_is_vertical);
- smoothVertices[index++].set(gp, gis, fillColor, dp, delta - gis, m_gradient_is_vertical);
+ float dp = lower ? qMin(0.0f, gradientLength - gradientX - delta) : qMax(0.0f, delta - gradientX);
+ smoothVertices[index++].set(gradientX, gradientYRight, fillColor, dp, secondaryLength - gradientYRight - delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYLeft, fillColor, dp, delta - gradientYLeft, m_gradient_is_vertical);
if (penWidth) {
- smoothVertices[index++].set(gp, gie, borderColor, -0.49f * penWidth * c, 0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(gp, gis, borderColor, -0.49f * penWidth * c, -0.49f * penWidth * s, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYRight, borderColor, -0.49f * penWidth * cosAngleRight, 0.49f * penWidth * sinAngleRight, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYLeft, borderColor, -0.49f * penWidth * cosAngleLeft, -0.49f * penWidth * sinAngleLeft, m_gradient_is_vertical);
} else {
dp = lower ? delta : -delta;
- smoothVertices[index++].set(gp, gie, transparent, dp, delta, m_gradient_is_vertical);
- smoothVertices[index++].set(gp, gis, transparent, dp, -delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYRight, transparent, dp, delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(gradientX, gradientYLeft, transparent, dp, -delta, m_gradient_is_vertical);
}
} else {
- vertices[index++].set(gp, gie, fillColor, m_gradient_is_vertical);
- vertices[index++].set(gp, gis, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYRight, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYLeft, fillColor, m_gradient_is_vertical);
if (penWidth) {
- vertices[index++].set(gp, gie, borderColor, m_gradient_is_vertical);
- vertices[index++].set(gp, gis, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYRight, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(gradientX, gradientYLeft, borderColor, m_gradient_is_vertical);
}
}
- ++nextGradientStop;
+
+ innerXPrev = gradientX;
+ innerYLeftPrev = gradientYLeft;
+ innerYRightPrev = gradientYRight;
+
+ nextGradientStop++;
}
if (!stops.isEmpty()) {
- if (nextGradientStop == 0) {
- fillColor = colorToColor4ub(stops.at(0).second);
- } else if (nextGradientStop == stops.size()) {
- fillColor = colorToColor4ub(stops.last().second);
- } else {
- const QGradientStop &prev = stops.at(nextGradientStop - 1);
- const QGradientStop &next = stops.at(nextGradientStop);
- float t = (gradientPos - prev.first) / (next.first - prev.first);
- fillColor = colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t;
- }
+ fillColor = fillColorFromX(xLeft);
}
if (hasFill) {
@@ -426,48 +785,57 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 1;
indices[innerAATail++] = index + 3;
- float dp = part ? qMin(0.0f, length - p - delta) : qMax(0.0f, delta - p);
- smoothVertices[index++].set(p, se, fillColor, dp, secondaryLength - se - delta, m_gradient_is_vertical);
- smoothVertices[index++].set(p, ss, fillColor, dp, delta - ss, m_gradient_is_vertical);
+ float dp = part ? qMin(0.0f, gradientLength - xRight - delta) : qMax(0.0f, delta - xRight);
+ smoothVertices[index++].set(xRight, yRight, fillColor, dp, secondaryLength - yRight - delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xLeft, yLeft, fillColor, dp, delta - yLeft, m_gradient_is_vertical);
dp = part ? delta : -delta;
if (penWidth) {
- smoothVertices[index++].set(p, se, borderColor, -0.49f * penWidth * c, 0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(p, ss, borderColor, -0.49f * penWidth * c, -0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryEnd, borderColor, 0.49f * penWidth * c, -0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryStart, borderColor, 0.49f * penWidth * c, 0.49f * penWidth * s, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryEnd, transparent, dp, delta, m_gradient_is_vertical);
- smoothVertices[index++].set(outerEdge, outerSecondaryStart, transparent, dp, -delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xRight, yRight, borderColor, -0.49f * penWidth * cosAngleRight, 0.49f * penWidth * sinAngleRight, m_gradient_is_vertical);
+ smoothVertices[index++].set(xLeft, yLeft, borderColor, -0.49f * penWidth * cosAngleLeft, -0.49f * penWidth * sinAngleLeft, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXRight, outerYRight, borderColor, 0.49f * penWidth * cosAngleRight, -0.49f * penWidth * sinAngleRight, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXLeft, outerYLeft, borderColor, 0.49f * penWidth * cosAngleLeft, 0.49f * penWidth * sinAngleLeft, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXRight, outerYRight, transparent, dp, delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(outerXLeft, outerYLeft, transparent, dp, -delta, m_gradient_is_vertical);
indices[--outerAAHead] = index - 2;
indices[--outerAAHead] = index - 4;
indices[outerAATail++] = index - 3;
indices[outerAATail++] = index - 1;
} else {
- smoothVertices[index++].set(p, se, transparent, dp, delta, m_gradient_is_vertical);
- smoothVertices[index++].set(p, ss, transparent, dp, -delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xRight, yRight, transparent, dp, delta, m_gradient_is_vertical);
+ smoothVertices[index++].set(xLeft, yLeft, transparent, dp, -delta, m_gradient_is_vertical);
}
} else {
- vertices[index++].set(p, se, fillColor, m_gradient_is_vertical);
- vertices[index++].set(p, ss, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(xRight, yRight, fillColor, m_gradient_is_vertical);
+ vertices[index++].set(xLeft, yLeft, fillColor, m_gradient_is_vertical);
if (penWidth) {
- vertices[index++].set(p, se, borderColor, m_gradient_is_vertical);
- vertices[index++].set(p, ss, borderColor, m_gradient_is_vertical);
- vertices[index++].set(outerEdge, outerSecondaryEnd, borderColor, m_gradient_is_vertical);
- vertices[index++].set(outerEdge, outerSecondaryStart, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(xRight, yRight, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(xLeft, yLeft, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(outerXRight, outerYRight, borderColor, m_gradient_is_vertical);
+ vertices[index++].set(outerXLeft, outerYLeft, borderColor, m_gradient_is_vertical);
}
}
- pp = p;
- pss = ss;
- pse = se;
+ innerXPrev = xLeft;
+ innerYLeftPrev = yLeft;
+ innerYRightPrev = yRight;
- // Rotate
- qreal tmp = c;
- c = c * cosStep - s * sinStep;
- s = s * cosStep + tmp * sinStep;
+ // Advance the point. This corresponds to a rotation of the respective segment
+ if (advanceLeft) {
+ iLeft++;
+ qreal tmp = cosSegmentAngleLeft;
+ cosSegmentAngleLeft = cosSegmentAngleLeft * cosStep[part][0] - sinSegmentAngleLeft * sinStep[part][0];
+ sinSegmentAngleLeft = sinSegmentAngleLeft * cosStep[part][0] + tmp * sinStep[part][0];
+ } else {
+ iRight++;
+ qreal tmp = cosSegmentAngleRight;
+ cosSegmentAngleRight = cosSegmentAngleRight * cosStep[part][1] - sinSegmentAngleRight * sinStep[part][1];
+ sinSegmentAngleRight = sinSegmentAngleRight * cosStep[part][1] + tmp * sinStep[part][1];
+ }
}
}
+
Q_ASSERT(index == vertexCount);
// Close the triangle strips.
@@ -552,11 +920,11 @@ void QSGBasicInternalRectangleNode::updateGeometry()
for (int part = -1; part <= 1; part += 2) {
float innerEdge = (part == 1 ? innerEnd : innerStart);
float outerEdge = (part == 1 ? outerEnd : outerStart);
- gradientPos = (innerEdge - innerStart + penWidth) / length;
+ gradientPos = (innerEdge - innerStart + penWidth) / gradientLength;
while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
// Insert vertices at gradient stops.
- float gp = (innerStart - penWidth) + stops.at(nextGradientStop).first * length;
+ float gp = (innerStart - penWidth) + stops.at(nextGradientStop).first * gradientLength;
fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
@@ -581,7 +949,7 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 3;
bool lower = stops.at(nextGradientStop).first > 0.5f;
- float dp = lower ? qMin(0.0f, length - gp - delta) : qMax(0.0f, delta - gp);
+ float dp = lower ? qMin(0.0f, gradientLength - gp - delta) : qMax(0.0f, delta - gp);
smoothVertices[index++].set(gp, innerSecondaryEnd, fillColor, dp, secondaryLength - innerSecondaryEnd - delta, m_gradient_is_vertical);
smoothVertices[index++].set(gp, innerSecondaryStart, fillColor, dp, delta - innerSecondaryStart, m_gradient_is_vertical);
if (penWidth) {
@@ -633,7 +1001,7 @@ void QSGBasicInternalRectangleNode::updateGeometry()
indices[innerAATail++] = index + 1;
indices[innerAATail++] = index + 3;
- float dp = part == 1 ? qMin(0.0f, length - innerEdge - delta) : qMax(0.0f, delta - innerEdge);
+ float dp = part == 1 ? qMin(0.0f, gradientLength - innerEdge - delta) : qMax(0.0f, delta - innerEdge);
smoothVertices[index++].set(innerEdge, innerSecondaryEnd, fillColor, dp, secondaryLength - innerSecondaryEnd - delta, m_gradient_is_vertical);
smoothVertices[index++].set(innerEdge, innerSecondaryStart, fillColor, dp, delta - innerSecondaryStart, m_gradient_is_vertical);
diff --git a/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h b/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h
index 808d1535e4..b806904f07 100644
--- a/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h
+++ b/src/quick/scenegraph/qsgbasicinternalrectanglenode_p.h
@@ -20,7 +20,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGBasicInternalRectangleNode : public QSGInternalRectangleNode
+class Q_QUICK_EXPORT QSGBasicInternalRectangleNode : public QSGInternalRectangleNode
{
public:
QSGBasicInternalRectangleNode();
@@ -32,6 +32,10 @@ public:
void setGradientStops(const QGradientStops &stops) override;
void setGradientVertical(bool vertical) override;
void setRadius(qreal radius) override;
+ void setTopLeftRadius(qreal radius) override;
+ void setTopRightRadius(qreal radius) override;
+ void setBottomLeftRadius(qreal radius) override;
+ void setBottomRightRadius(qreal radius) override;
void setAntialiasing(bool antialiasing) override;
void setAligned(bool aligned) override;
void update() override;
@@ -48,8 +52,12 @@ protected:
QGradientStops m_gradient_stops;
QColor m_color;
QColor m_border_color;
- qreal m_radius;
- qreal m_pen_width;
+ float m_radius = 0.0f;
+ float m_topLeftRadius = -1.0f;
+ float m_topRightRadius = -1.0f;
+ float m_bottomLeftRadius = -1.0f;
+ float m_bottomRightRadius = -1.0f;
+ float m_pen_width = 0.0f;
uint m_aligned : 1;
uint m_antialiasing : 1;
diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp
index f56e9898c6..01e0707556 100644
--- a/src/quick/scenegraph/qsgcontext.cpp
+++ b/src/quick/scenegraph/qsgcontext.cpp
@@ -4,8 +4,9 @@
#include <QtQuick/private/qsgcontext_p.h>
#include <QtQuick/private/qsgtexture_p.h>
#include <QtQuick/private/qsgrenderer_p.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include <QtQuick/private/qsgadaptationlayer_p.h>
+#include <QtQuick/private/qsginternaltextnode_p.h>
#include <QGuiApplication>
#include <QScreen>
@@ -63,6 +64,13 @@ Q_LOGGING_CATEGORY(QSG_LOG_TIME_GLYPH, "qt.scenegraph.time.glyph")
// Timing inside the renderer base class
Q_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERER, "qt.scenegraph.time.renderer")
+// Leak checks
+Q_LOGGING_CATEGORY(lcQsgLeak, "qt.scenegraph.leaks")
+
+// Applicable for render loops that install their own animation driver, such as
+// the 'threaded' loop. This env.var. is documented in the scenegraph docs.
+DEFINE_BOOL_CONFIG_OPTION(useElapsedTimerBasedAnimationDriver, QSG_USE_SIMPLE_ANIMATION_DRIVER);
+
bool qsg_useConsistentTiming()
{
int use = -1;
@@ -76,21 +84,9 @@ bool qsg_useConsistentTiming()
class QSGAnimationDriver : public QAnimationDriver
{
- Q_OBJECT
public:
- enum Mode {
- VSyncMode,
- TimerMode
- };
-
QSGAnimationDriver(QObject *parent)
: QAnimationDriver(parent)
- , m_time(0)
- , m_vsync(0)
- , m_mode(VSyncMode)
- , m_lag(0)
- , m_bad(0)
- , m_good(0)
{
QScreen *screen = QGuiApplication::primaryScreen();
if (screen) {
@@ -103,6 +99,35 @@ public:
} else {
m_vsync = 16.67f;
}
+ }
+
+ float vsyncInterval() const { return m_vsync; }
+
+ virtual bool isVSyncDependent() const = 0;
+
+protected:
+ float m_vsync = 0;
+};
+
+// default as in default for the threaded render loop
+class QSGDefaultAnimationDriver : public QSGAnimationDriver
+{
+ Q_OBJECT
+public:
+ enum Mode {
+ VSyncMode,
+ TimerMode
+ };
+
+ QSGDefaultAnimationDriver(QObject *parent)
+ : QSGAnimationDriver(parent)
+ , m_time(0)
+ , m_mode(VSyncMode)
+ , m_lag(0)
+ , m_bad(0)
+ , m_good(0)
+ {
+ QScreen *screen = QGuiApplication::primaryScreen();
if (screen && !qsg_useConsistentTiming()) {
if (m_vsync <= 0)
m_mode = TimerMode;
@@ -192,10 +217,12 @@ public:
advanceAnimation();
}
- float vsyncInterval() const { return m_vsync; } // this should always return something sane, regardless of m_mode
+ bool isVSyncDependent() const override
+ {
+ return true;
+ }
double m_time;
- float m_vsync;
Mode m_mode;
QElapsedTimer m_timer;
QElapsedTimer m_wallTime;
@@ -204,6 +231,81 @@ public:
int m_good;
};
+// Advance based on QElapsedTimer. (so like the TimerMode of QSGDefaultAnimationDriver)
+// Does not depend on vsync-based throttling.
+//
+// NB this is not the same as not installing a QAnimationDriver: the built-in
+// approach in QtCore is to rely on 16 ms timer events which are potentially a
+// lot less accurate.
+//
+// This has the benefits of:
+// - not needing any of the infrastructure for falling back to a
+// QTimer when there are multiple windows,
+// - needing no heuristics trying determine if vsync-based throttling
+// is missing or broken,
+// - being compatible with any kind of temporal drifts in vsync throttling
+// which is reportedly happening in various environments and platforms
+// still,
+// - not being tied to the primary screen's refresh rate, i.e. this is
+// correct even if the window is on some secondary screen with a
+// different refresh rate,
+// - not having to worry about the potential effects of variable refresh
+// rate solutions,
+// - render thread animators work correctly regardless of vsync.
+//
+// On the downside, some animations might appear less smooth (compared to the
+// ideal single window case of QSGDefaultAnimationDriver).
+//
+class QSGElapsedTimerAnimationDriver : public QSGAnimationDriver
+{
+public:
+ QSGElapsedTimerAnimationDriver(QObject *parent)
+ : QSGAnimationDriver(parent)
+ {
+ qCDebug(QSG_LOG_INFO, "Animation Driver: using QElapsedTimer, thread %p %s",
+ QThread::currentThread(),
+ QThread::currentThread() == qGuiApp->thread() ? "(gui/main thread)" : "(render thread)");
+ }
+
+ void start() override
+ {
+ m_wallTime.restart();
+ QAnimationDriver::start();
+ }
+
+ qint64 elapsed() const override
+ {
+ return m_wallTime.elapsed();
+ }
+
+ void advance() override
+ {
+ advanceAnimation();
+ }
+
+ bool isVSyncDependent() const override
+ {
+ return false;
+ }
+
+private:
+ QElapsedTimer m_wallTime;
+};
+
+QSGRenderContext::FontKey::FontKey(const QRawFont &font, int quality)
+{
+ QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
+ if (fe != nullptr)
+ faceId = fe->faceId();
+ style = font.style();
+ weight = font.weight();
+ renderTypeQuality = quality;
+ if (faceId.filename.isEmpty()) {
+ familyName = font.familyName();
+ styleName = font.styleName();
+ }
+}
+
/*!
\class QSGContext
@@ -245,6 +347,16 @@ QSGInternalRectangleNode *QSGContext::createInternalRectangleNode(const QRectF &
return node;
}
+QSGInternalTextNode *QSGContext::createInternalTextNode(QSGRenderContext *renderContext)
+{
+ return new QSGInternalTextNode(renderContext);
+}
+
+QSGTextNode *QSGContext::createTextNode(QSGRenderContext *renderContext)
+{
+ return createInternalTextNode(renderContext);
+}
+
/*!
Creates a new shader effect helper instance. This function is called on the
GUI thread, unlike the others. This is necessary in order to provide
@@ -270,7 +382,10 @@ QSGShaderEffectNode *QSGContext::createShaderEffectNode(QSGRenderContext *)
*/
QAnimationDriver *QSGContext::createAnimationDriver(QObject *parent)
{
- return new QSGAnimationDriver(parent);
+ if (useElapsedTimerBasedAnimationDriver())
+ return new QSGElapsedTimerAnimationDriver(parent);
+
+ return new QSGDefaultAnimationDriver(parent);
}
/*!
@@ -282,6 +397,14 @@ float QSGContext::vsyncIntervalForAnimationDriver(QAnimationDriver *driver)
return static_cast<QSGAnimationDriver *>(driver)->vsyncInterval();
}
+/*!
+ \return true if \a driver relies on vsync-based throttling in some form.
+ */
+bool QSGContext::isVSyncDependent(QAnimationDriver *driver)
+{
+ return static_cast<QSGAnimationDriver *>(driver)->isVSyncDependent();
+}
+
QSize QSGContext::minimumFBOSize() const
{
return QSize(1, 1);
@@ -365,6 +488,16 @@ void QSGRenderContext::preprocess()
}
/*!
+ Factory function for curve atlases that can be used to provide geometry for the curve
+ renderer for a given font.
+*/
+QSGCurveGlyphAtlas *QSGRenderContext::curveGlyphAtlas(const QRawFont &font)
+{
+ Q_UNUSED(font);
+ return nullptr;
+}
+
+/*!
Factory function for scene graph backends of the distance-field glyph cache.
*/
QSGDistanceFieldGlyphCache *QSGRenderContext::distanceFieldGlyphCache(const QRawFont &, int)
@@ -380,7 +513,13 @@ void QSGRenderContext::invalidateGlyphCaches()
void QSGRenderContext::registerFontengineForCleanup(QFontEngine *engine)
{
engine->ref.ref();
- m_fontEnginesToClean << engine;
+ m_fontEnginesToClean[engine]++;
+}
+
+void QSGRenderContext::unregisterFontengineForCleanup(QFontEngine *engine)
+{
+ m_fontEnginesToClean[engine]--;
+ Q_ASSERT(m_fontEnginesToClean.value(engine) >= 0);
}
QRhi *QSGRenderContext::rhi() const
diff --git a/src/quick/scenegraph/qsgcontext_p.h b/src/quick/scenegraph/qsgcontext_p.h
index 742b3b1448..885b207f40 100644
--- a/src/quick/scenegraph/qsgcontext_p.h
+++ b/src/quick/scenegraph/qsgcontext_p.h
@@ -24,15 +24,20 @@
#include <private/qtquickglobal_p.h>
#include <private/qrawfont_p.h>
+#include <private/qfontengine_p.h>
#include <QtQuick/qsgnode.h>
#include <QtQuick/qsgrendererinterface.h>
+#include <QtQuick/qsgtextnode.h>
+
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
class QSGContextPrivate;
class QSGInternalRectangleNode;
class QSGInternalImageNode;
+class QSGInternalTextNode;
class QSGPainterNode;
class QSGGlyphNode;
class QSGRenderer;
@@ -50,6 +55,7 @@ class QSGRendererInterface;
class QSGShaderEffectNode;
class QSGGuiThreadShaderEffectManager;
class QSGRectangleNode;
+class QSGTextNode;
class QSGImageNode;
class QSGNinePatchNode;
class QSGSpriteNode;
@@ -60,6 +66,8 @@ class QRhiRenderTarget;
class QRhiRenderPassDescriptor;
class QRhiCommandBuffer;
class QQuickGraphicsConfiguration;
+class QQuickItem;
+class QSGCurveGlyphAtlas;
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERLOOP)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_COMPILATION)
@@ -70,7 +78,7 @@ Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERER)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_INFO)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_RENDERLOOP)
-class Q_QUICK_PRIVATE_EXPORT QSGContext : public QObject
+class Q_QUICK_EXPORT QSGContext : public QObject
{
Q_OBJECT
@@ -91,8 +99,9 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode(const QRectF &rect, const QColor &c);
virtual QSGInternalRectangleNode *createInternalRectangleNode() = 0;
virtual QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) = 0;
+ virtual QSGInternalTextNode *createInternalTextNode(QSGRenderContext *renderContext);
virtual QSGPainterNode *createPainterNode(QQuickPaintedItem *item) = 0;
- virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) = 0;
+ virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) = 0;
virtual QSGLayer *createLayer(QSGRenderContext *renderContext) = 0;
virtual QSGGuiThreadShaderEffectManager *createGuiThreadShaderEffectManager();
virtual QSGShaderEffectNode *createShaderEffectNode(QSGRenderContext *renderContext);
@@ -101,12 +110,14 @@ public:
#endif
virtual QAnimationDriver *createAnimationDriver(QObject *parent);
virtual float vsyncIntervalForAnimationDriver(QAnimationDriver *driver);
+ virtual bool isVSyncDependent(QAnimationDriver *driver);
virtual QSize minimumFBOSize() const;
virtual QSurfaceFormat defaultSurfaceFormat() const = 0;
virtual QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext);
+ virtual QSGTextNode *createTextNode(QSGRenderContext *renderContext);
virtual QSGRectangleNode *createRectangleNode() = 0;
virtual QSGImageNode *createImageNode() = 0;
virtual QSGNinePatchNode *createNinePatchNode() = 0;
@@ -119,7 +130,7 @@ public:
static QString backend();
};
-class Q_QUICK_PRIVATE_EXPORT QSGRenderContext : public QObject
+class Q_QUICK_EXPORT QSGRenderContext : public QObject
{
Q_OBJECT
public:
@@ -157,6 +168,7 @@ public:
virtual void preprocess();
virtual void invalidateGlyphCaches();
virtual QSGDistanceFieldGlyphCache *distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality);
+ virtual QSGCurveGlyphAtlas *curveGlyphAtlas(const QRawFont &font);
QSGTexture *textureForFactory(QQuickTextureFactory *factory, QQuickWindow *window);
virtual QSGTexture *createTexture(const QImage &image, uint flags = CreateTexture_Alpha) const = 0;
@@ -165,6 +177,7 @@ public:
virtual int maxTextureSize() const = 0;
+ void unregisterFontengineForCleanup(QFontEngine *engine);
void registerFontengineForCleanup(QFontEngine *engine);
virtual QRhi *rhi() const;
@@ -178,17 +191,48 @@ public Q_SLOTS:
void textureFactoryDestroyed(QObject *o);
protected:
+ struct FontKey {
+ FontKey(const QRawFont &font, int renderTypeQuality);
+
+ QFontEngine::FaceId faceId;
+ QFont::Style style;
+ int weight;
+ int renderTypeQuality;
+ QString familyName;
+ QString styleName;
+ };
+ friend bool operator==(const QSGRenderContext::FontKey &f1, const QSGRenderContext::FontKey &f2);
+ friend size_t qHash(const QSGRenderContext::FontKey &f, size_t seed);
+
// Hold m_sg with QPointer in the rare case it gets deleted before us.
QPointer<QSGContext> m_sg;
QMutex m_mutex;
QHash<QObject *, QSGTexture *> m_textures;
QSet<QSGTexture *> m_texturesToDelete;
- QHash<QString, QSGDistanceFieldGlyphCache *> m_glyphCaches;
+ QHash<FontKey, QSGDistanceFieldGlyphCache *> m_glyphCaches;
- QSet<QFontEngine *> m_fontEnginesToClean;
+ // References to font engines that are currently in use by native rendering glyph nodes
+ // and which must be kept alive as long as they are used in the render thread.
+ QHash<QFontEngine *, int> m_fontEnginesToClean;
};
+inline bool operator ==(const QSGRenderContext::FontKey &f1, const QSGRenderContext::FontKey &f2)
+{
+ return f1.faceId == f2.faceId
+ && f1.style == f2.style
+ && f1.weight == f2.weight
+ && f1.renderTypeQuality == f2.renderTypeQuality
+ && f1.familyName == f2.familyName
+ && f1.styleName == f2.styleName;
+}
+
+inline size_t qHash(const QSGRenderContext::FontKey &f, size_t seed = 0)
+{
+ return qHashMulti(seed, f.faceId, f.renderTypeQuality, f.familyName, f.styleName, f.style, f.weight);
+}
+
+
QT_END_NAMESPACE
#endif // QSGCONTEXT_H
diff --git a/src/quick/scenegraph/qsgcontextplugin.cpp b/src/quick/scenegraph/qsgcontextplugin.cpp
index e21f2fbb6f..8026f68eb4 100644
--- a/src/quick/scenegraph/qsgcontextplugin.cpp
+++ b/src/quick/scenegraph/qsgcontextplugin.cpp
@@ -103,7 +103,7 @@ QSGAdaptationBackendData *contextFactory()
// caused by run time hocus pocus. If one wants to use the software backend
// in a GL or Vulkan capable Qt build (or on Windows or Apple platforms), it
// has to be requested explicitly.
-#if !QT_CONFIG(opengl) && !QT_CONFIG(vulkan) && !defined(Q_OS_WIN) && !defined(Q_OS_MACOS) && !defined(Q_OS_IOS)
+#if !QT_CONFIG(opengl) && !QT_CONFIG(vulkan) && !QT_CONFIG(metal) && !defined(Q_OS_WIN)
if (requestedBackend.isEmpty())
requestedBackend = QLatin1String("software");
#endif
diff --git a/src/quick/scenegraph/qsgcontextplugin_p.h b/src/quick/scenegraph/qsgcontextplugin_p.h
index 853641b509..966cf0b978 100644
--- a/src/quick/scenegraph/qsgcontextplugin_p.h
+++ b/src/quick/scenegraph/qsgcontextplugin_p.h
@@ -26,7 +26,7 @@ class QSGContext;
class QSGRenderLoop;
-struct Q_QUICK_PRIVATE_EXPORT QSGContextFactoryInterface : public QFactoryInterface
+struct Q_QUICK_EXPORT QSGContextFactoryInterface : public QFactoryInterface
{
enum Flag {
SupportsShaderEffectNode = 0x01
@@ -46,7 +46,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QSGContextFactoryInterface::Flags)
"org.qt-project.Qt.QSGContextFactoryInterface"
Q_DECLARE_INTERFACE(QSGContextFactoryInterface, QSGContextFactoryInterface_iid)
-class Q_QUICK_PRIVATE_EXPORT QSGContextPlugin : public QObject, public QSGContextFactoryInterface
+class Q_QUICK_EXPORT QSGContextPlugin : public QObject, public QSGContextFactoryInterface
{
Q_OBJECT
Q_INTERFACES(QSGContextFactoryInterface:QFactoryInterface)
diff --git a/src/quick/scenegraph/qsgcurveabstractnode_p.h b/src/quick/scenegraph/qsgcurveabstractnode_p.h
new file mode 100644
index 0000000000..aadc17d3f0
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveabstractnode_p.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEABSTRACTNODE_P_H
+#define QSGCURVEABSTRACTNODE_P_H
+
+#include <QtGui/qcolor.h>
+#include <QtQuick/qsgnode.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveAbstractNode : public QSGGeometryNode
+{
+public:
+ virtual void setColor(QColor col) = 0;
+ virtual void cookGeometry() = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEABSTRACTNODE_P_H
diff --git a/src/quick/scenegraph/qsgcurvefillnode.cpp b/src/quick/scenegraph/qsgcurvefillnode.cpp
new file mode 100644
index 0000000000..0a4a42341e
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode.cpp
@@ -0,0 +1,61 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvefillnode_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveFillNode::QSGCurveFillNode()
+{
+ setFlag(OwnsGeometry, true);
+ setFlag(UsePreprocess, true);
+ setGeometry(new QSGGeometry(attributes(), 0, 0));
+
+ updateMaterial();
+}
+
+void QSGCurveFillNode::updateMaterial()
+{
+ m_material.reset(new QSGCurveFillMaterial(this));
+ setMaterial(m_material.data());
+}
+
+void QSGCurveFillNode::cookGeometry()
+{
+ QSGGeometry *g = geometry();
+ if (g->indexType() != QSGGeometry::UnsignedIntType) {
+ g = new QSGGeometry(attributes(),
+ m_uncookedVertexes.size(),
+ m_uncookedIndexes.size(),
+ QSGGeometry::UnsignedIntType);
+ setGeometry(g);
+ } else {
+ g->allocate(m_uncookedVertexes.size(), m_uncookedIndexes.size());
+ }
+
+ g->setDrawingMode(QSGGeometry::DrawTriangles);
+ memcpy(g->vertexData(),
+ m_uncookedVertexes.constData(),
+ g->vertexCount() * g->sizeOfVertex());
+ memcpy(g->indexData(),
+ m_uncookedIndexes.constData(),
+ g->indexCount() * g->sizeOfIndex());
+
+ m_uncookedIndexes.clear();
+ m_uncookedVertexes.clear();
+}
+
+const QSGGeometry::AttributeSet &QSGCurveFillNode::attributes()
+{
+ static QSGGeometry::Attribute data[] = {
+ QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(1, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(2, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute),
+ };
+ static QSGGeometry::AttributeSet attrs = { 4, sizeof(CurveNodeVertex), data };
+ return attrs;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p.cpp b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
new file mode 100644
index 0000000000..331620cf94
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
@@ -0,0 +1,396 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvefillnode_p_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "util/qsggradientcache_p.h"
+
+#include <private/qsgtexture_p.h>
+#include <private/qsgplaintexture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+ class QSGCurveFillMaterialShader : public QSGMaterialShader
+ {
+ public:
+ QSGCurveFillMaterialShader(QGradient::Type gradientType,
+ bool useTextureFill,
+ bool useDerivatives,
+ int viewCount);
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
+ void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+ };
+
+ QSGCurveFillMaterialShader::QSGCurveFillMaterialShader(QGradient::Type gradientType,
+ bool useTextureFill,
+ bool useDerivatives,
+ int viewCount)
+ {
+ QString baseName = QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapecurve");
+
+ if (gradientType == QGradient::LinearGradient) {
+ baseName += QStringLiteral("_lg");
+ } else if (gradientType == QGradient::RadialGradient) {
+ baseName += QStringLiteral("_rg");
+ } else if (gradientType == QGradient::ConicalGradient) {
+ baseName += QStringLiteral("_cg");
+ } else if (useTextureFill) {
+ baseName += QStringLiteral("_tf");
+ }
+
+ if (useDerivatives)
+ baseName += QStringLiteral("_derivatives");
+
+ setShaderFileName(VertexStage, baseName + QStringLiteral(".vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, baseName + QStringLiteral(".frag.qsb"), viewCount);
+ }
+
+ void QSGCurveFillMaterialShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+ {
+ Q_UNUSED(oldMaterial);
+ QSGCurveFillMaterial *m = static_cast<QSGCurveFillMaterial *>(newMaterial);
+ const QSGCurveFillNode *node = m->node();
+ if (binding != 1
+ || (node->gradientType() == QGradient::NoGradient && node->fillTextureProvider() == nullptr)) {
+ return;
+ }
+
+ QSGTexture *t = nullptr;
+ if (node->gradientType() != QGradient::NoGradient) {
+ const QSGGradientCacheKey cacheKey(node->fillGradient()->stops,
+ node->fillGradient()->spread);
+ t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
+ } else if (node->fillTextureProvider() != nullptr) {
+ t = node->fillTextureProvider()->texture();
+ if (t != nullptr && t->isAtlasTexture()) {
+ // Create a non-atlas copy to make texture coordinate wrapping work. This
+ // texture copy is owned by the QSGTexture so memory is managed with the original
+ // texture provider.
+ QSGTexture *newTexture = t->removedFromAtlas(state.resourceUpdateBatch());
+ if (newTexture != nullptr)
+ t = newTexture;
+ }
+
+ }
+
+ if (t != nullptr) {
+ t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+ } else {
+ if (m->dummyTexture() == nullptr) {
+ QSGPlainTexture *dummyTexture = new QSGPlainTexture;
+ dummyTexture->setFiltering(QSGTexture::Nearest);
+ dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
+ dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
+ QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
+ img.fill(0);
+ dummyTexture->setImage(img);
+ dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+
+ m->setDummyTexture(dummyTexture);
+ }
+
+ t = m->dummyTexture();
+ }
+
+ *texture = t;
+ }
+
+ bool QSGCurveFillMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
+ {
+ bool changed = false;
+ QByteArray *buf = state.uniformData();
+ Q_ASSERT(buf->size() >= 80);
+ const int matrixCount = qMin(state.projectionMatrixCount(), newEffect->viewCount());
+
+ int offset = 0;
+ float matrixScale = 0.0f;
+ if (state.isMatrixDirty()) {
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + offset + viewIndex * 64, m.constData(), 64);
+ }
+
+ matrixScale = qSqrt(qAbs(state.determinant()));
+ memcpy(buf->data() + offset + newEffect->viewCount() * 64, &matrixScale, 4);
+
+ changed = true;
+ }
+ offset += newEffect->viewCount() * 64 + 4;
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(buf->data() + offset, &opacity, 4);
+ changed = true;
+ }
+ offset += 4;
+
+ QSGCurveFillMaterial *newMaterial = static_cast<QSGCurveFillMaterial *>(newEffect);
+ QSGCurveFillMaterial *oldMaterial = static_cast<QSGCurveFillMaterial *>(oldEffect);
+
+ QSGCurveFillNode *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
+ QSGCurveFillNode *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
+
+ if (newNode == nullptr)
+ return changed;
+
+ if (oldNode == nullptr || oldNode->debug() != newNode->debug()) {
+ float debug = newNode->debug();
+ memcpy(buf->data() + offset, &debug, 4);
+ changed = true;
+ }
+ offset += 8;
+
+ if (newNode->gradientType() == QGradient::NoGradient
+ && newNode->fillTextureProvider() == nullptr) {
+ Q_ASSERT(buf->size() >= offset + 16);
+
+ QVector4D newColor = QVector4D(newNode->color().redF(),
+ newNode->color().greenF(),
+ newNode->color().blueF(),
+ newNode->color().alphaF());
+ QVector4D oldColor = oldNode != nullptr
+ ? QVector4D(oldNode->color().redF(),
+ oldNode->color().greenF(),
+ oldNode->color().blueF(),
+ oldNode->color().alphaF())
+ : QVector4D{};
+
+ if (oldNode == nullptr || oldColor != newColor) {
+ memcpy(buf->data() + offset, &newColor, 16);
+ changed = true;
+ }
+
+ offset += 16;
+ } else {
+ Q_ASSERT(buf->size() >= offset + 64);
+
+ if (!oldNode || *oldNode->fillTransform() != *newNode->fillTransform()) {
+ memcpy(buf->data() + offset, newNode->fillTransform()->invertedData(), 64);
+ changed = true;
+ }
+
+ offset += 64;
+ }
+
+ if (newNode->gradientType() == QGradient::NoGradient
+ && newNode->fillTextureProvider() != nullptr) {
+ Q_ASSERT(buf->size() >= offset + 8);
+ const QSizeF newTextureSize = newNode->fillTextureProvider()->texture() != nullptr
+ ? newNode->fillTextureProvider()->texture()->textureSize()
+ : QSizeF(0, 0);
+ const QVector2D newBoundsSize(newTextureSize.width() / state.devicePixelRatio(),
+ newTextureSize.height() / state.devicePixelRatio());
+ const QVector2D oldBoundsSize = oldNode != nullptr
+ ? oldNode->boundsSize()
+ : QVector2D{};
+
+ if (oldEffect == nullptr || newBoundsSize != oldBoundsSize) {
+ newNode->setBoundsSize(newBoundsSize);
+ memcpy(buf->data() + offset, &newBoundsSize, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ } else if (newNode->gradientType() == QGradient::LinearGradient) {
+ Q_ASSERT(buf->size() >= offset + 8 + 8);
+
+ QVector2D newGradientStart = QVector2D(newNode->fillGradient()->a);
+ QVector2D oldGradientStart = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->a)
+ : QVector2D{};
+
+ if (newGradientStart != oldGradientStart || oldEffect == nullptr) {
+ memcpy(buf->data() + offset, &newGradientStart, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ QVector2D newGradientEnd = QVector2D(newNode->fillGradient()->b);
+ QVector2D oldGradientEnd = oldNode!= nullptr
+ ? QVector2D(oldNode->fillGradient()->b)
+ : QVector2D{};
+
+ if (newGradientEnd != oldGradientEnd || oldEffect == nullptr) {
+ memcpy(buf->data() + offset, &newGradientEnd, 8);
+ changed = true;
+ }
+
+ offset += 8;
+ } else if (newNode->gradientType() == QGradient::RadialGradient) {
+ Q_ASSERT(buf->size() >= offset + 8 + 8 + 4 + 4);
+
+ QVector2D newFocalPoint = QVector2D(newNode->fillGradient()->b);
+ QVector2D oldFocalPoint = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->b)
+ : QVector2D{};
+ if (oldNode == nullptr || newFocalPoint != oldFocalPoint) {
+ memcpy(buf->data() + offset, &newFocalPoint, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ QVector2D newCenterPoint = QVector2D(newNode->fillGradient()->a);
+ QVector2D oldCenterPoint = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->a)
+ : QVector2D{};
+
+ QVector2D newCenterToFocal = newCenterPoint - newFocalPoint;
+ QVector2D oldCenterToFocal = oldCenterPoint - oldFocalPoint;
+ if (oldNode == nullptr || newCenterToFocal != oldCenterToFocal) {
+ memcpy(buf->data() + offset, &newCenterToFocal, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ float newCenterRadius = newNode->fillGradient()->v0;
+ float oldCenterRadius = oldNode != nullptr
+ ? oldNode->fillGradient()->v0
+ : 0.0f;
+ if (oldNode == nullptr || !qFuzzyCompare(newCenterRadius, oldCenterRadius)) {
+ memcpy(buf->data() + offset, &newCenterRadius, 4);
+ changed = true;
+ }
+ offset += 4;
+
+ float newFocalRadius = newNode->fillGradient()->v1;
+ float oldFocalRadius = oldNode != nullptr
+ ? oldNode->fillGradient()->v1
+ : 0.0f;
+ if (oldNode == nullptr || !qFuzzyCompare(newFocalRadius, oldFocalRadius)) {
+ memcpy(buf->data() + offset, &newFocalRadius, 4);
+ changed = true;
+ }
+ offset += 4;
+
+ } else if (newNode->gradientType() == QGradient::ConicalGradient) {
+ Q_ASSERT(buf->size() >= offset + 8 + 4);
+
+ QVector2D newFocalPoint = QVector2D(newNode->fillGradient()->a);
+ QVector2D oldFocalPoint = oldNode != nullptr
+ ? QVector2D(oldNode->fillGradient()->a)
+ : QVector2D{};
+ if (oldNode == nullptr || newFocalPoint != oldFocalPoint) {
+ memcpy(buf->data() + offset, &newFocalPoint, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ float newAngle = newNode->fillGradient()->v0;
+ float oldAngle = oldNode != nullptr
+ ? oldNode->fillGradient()->v0
+ : 0.0f;
+ if (oldNode == nullptr || !qFuzzyCompare(newAngle, oldAngle)) {
+ newAngle = -qDegreesToRadians(newAngle);
+ memcpy(buf->data() + offset, &newAngle, 4);
+ changed = true;
+ }
+ offset += 4;
+ }
+
+ return changed;
+ }
+
+}
+
+QSGCurveFillMaterial::QSGCurveFillMaterial(QSGCurveFillNode *node)
+ : m_node(node)
+{
+ setFlag(Blending, true);
+ setFlag(RequiresDeterminant, true);
+}
+
+QSGCurveFillMaterial::~QSGCurveFillMaterial()
+{
+ delete m_dummyTexture;
+}
+
+int QSGCurveFillMaterial::compare(const QSGMaterial *other) const
+{
+ if (other->type() != type())
+ return (type() - other->type());
+
+ const QSGCurveFillMaterial *otherMaterial =
+ static_cast<const QSGCurveFillMaterial *>(other);
+
+ QSGCurveFillNode *a = node();
+ QSGCurveFillNode *b = otherMaterial->node();
+ if (a == b)
+ return 0;
+
+ if (a->gradientType() == QGradient::NoGradient && a->fillTextureProvider() == nullptr) {
+ if (int d = a->color().red() - b->color().red())
+ return d;
+ if (int d = a->color().green() - b->color().green())
+ return d;
+ if (int d = a->color().blue() - b->color().blue())
+ return d;
+ if (int d = a->color().alpha() - b->color().alpha())
+ return d;
+ } else {
+ if (a->gradientType() != QGradient::NoGradient) {
+ const QSGGradientCache::GradientDesc &ga = *a->fillGradient();
+ const QSGGradientCache::GradientDesc &gb = *b->fillGradient();
+
+ if (int d = ga.a.x() - gb.a.x())
+ return d;
+ if (int d = ga.a.y() - gb.a.y())
+ return d;
+ if (int d = ga.b.x() - gb.b.x())
+ return d;
+ if (int d = ga.b.y() - gb.b.y())
+ return d;
+
+ if (int d = ga.v0 - gb.v0)
+ return d;
+ if (int d = ga.v1 - gb.v1)
+ return d;
+
+ if (int d = ga.spread - gb.spread)
+ return d;
+
+ if (int d = ga.stops.size() - gb.stops.size())
+ return d;
+
+ for (int i = 0; i < ga.stops.size(); ++i) {
+ if (int d = ga.stops[i].first - gb.stops[i].first)
+ return d;
+ if (int d = ga.stops[i].second.rgba() - gb.stops[i].second.rgba())
+ return d;
+ }
+ }
+
+ if (int d = a->fillTransform()->compareTo(*b->fillTransform()))
+ return d;
+ }
+
+ const qintptr diff = qintptr(a->fillTextureProvider()) - qintptr(b->fillTextureProvider());
+ return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
+}
+
+QSGMaterialType *QSGCurveFillMaterial::type() const
+{
+ static QSGMaterialType type[5];
+ uint index = node()->gradientType();
+ Q_ASSERT((index & ~3) == 0); // Only two first bits for gradient type
+
+ if (node()->gradientType() == QGradient::NoGradient && node()->fillTextureProvider() != nullptr)
+ index = 5;
+
+ return &type[index];
+}
+
+QSGMaterialShader *QSGCurveFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
+{
+ return new QSGCurveFillMaterialShader(node()->gradientType(),
+ node()->gradientType() == QGradient::NoGradient
+ && node()->fillTextureProvider() != nullptr,
+ renderMode == QSGRendererInterface::RenderMode3D,
+ viewCount());
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p.h b/src/quick/scenegraph/qsgcurvefillnode_p.h
new file mode 100644
index 0000000000..441576a814
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.h
@@ -0,0 +1,273 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEFILLNODE_P_H
+#define QSGCURVEFILLNODE_P_H
+
+#include <QtGui/qbrush.h>
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/private/qsggradientcache_p.h>
+#include <QtQuick/private/qsgtransform_p.h>
+#include <QtQuick/qsgnode.h>
+#include <QtQuick/qsgtextureprovider.h>
+
+#include "qsgcurveabstractnode_p.h"
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGTextureProvider;
+
+class Q_QUICK_EXPORT QSGCurveFillNode : public QObject, public QSGCurveAbstractNode
+{
+ Q_OBJECT
+public:
+ QSGCurveFillNode();
+
+ void setColor(QColor col) override
+ {
+ m_color = col;
+ markDirty(DirtyMaterial);
+ }
+
+ QColor color() const
+ {
+ return m_color;
+ }
+
+ void setFillTextureProvider(QSGTextureProvider *provider)
+ {
+ if (provider == m_textureProvider)
+ return;
+
+ if (m_textureProvider != nullptr) {
+ disconnect(m_textureProvider, &QSGTextureProvider::textureChanged,
+ this, &QSGCurveFillNode::handleTextureChanged);
+ disconnect(m_textureProvider, &QSGTextureProvider::destroyed,
+ this, &QSGCurveFillNode::handleTextureProviderDestroyed);
+ }
+
+ m_textureProvider = provider;
+ markDirty(DirtyMaterial);
+
+ if (m_textureProvider != nullptr) {
+ connect(m_textureProvider, &QSGTextureProvider::textureChanged,
+ this, &QSGCurveFillNode::handleTextureChanged);
+ connect(m_textureProvider, &QSGTextureProvider::destroyed,
+ this, &QSGCurveFillNode::handleTextureProviderDestroyed);
+ }
+ }
+
+
+ QSGTextureProvider *fillTextureProvider() const
+ {
+ return m_textureProvider;
+ }
+
+ void setFillGradient(const QSGGradientCache::GradientDesc &fillGradient)
+ {
+ m_fillGradient = fillGradient;
+ markDirty(DirtyMaterial);
+ }
+
+ const QSGGradientCache::GradientDesc *fillGradient() const
+ {
+ return &m_fillGradient;
+ }
+
+ void setGradientType(QGradient::Type type)
+ {
+ m_gradientType = type;
+ markDirty(DirtyMaterial);
+ }
+
+ QGradient::Type gradientType() const
+ {
+ return m_gradientType;
+ }
+
+ void setFillTransform(const QSGTransform &transform)
+ {
+ m_fillTransform = transform;
+ markDirty(DirtyMaterial);
+ }
+
+ const QSGTransform *fillTransform() const
+ {
+ return &m_fillTransform;
+ }
+
+ float debug() const
+ {
+ return m_debug;
+ }
+
+ void setDebug(float newDebug)
+ {
+ m_debug = newDebug;
+ }
+
+ void appendTriangle(const std::array<QVector2D, 3> &v, // triangle vertices
+ const std::array<QVector2D, 3> &n, // vertex normals
+ std::function<QVector3D(QVector2D)> uvForPoint
+ )
+ {
+ QVector3D uv1 = uvForPoint(v[0]);
+ QVector3D uv2 = uvForPoint(v[1]);
+ QVector3D uv3 = uvForPoint(v[2]);
+
+ QVector2D duvdx = QVector2D(uvForPoint(v[0] + QVector2D(1, 0))) - QVector2D(uv1);
+ QVector2D duvdy = QVector2D(uvForPoint(v[0] + QVector2D(0, 1))) - QVector2D(uv1);
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v[0].x(), v[0].y(),
+ uv1.x(), uv1.y(), uv1.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n[0].x(), n[0].y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v[1].x(), v[1].y(),
+ uv2.x(), uv2.y(), uv2.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n[1].x(), n[1].y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v[2].x(), v[2].y(),
+ uv3.x(), uv3.y(), uv3.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n[2].x(), n[2].y()
+ });
+ }
+
+ void appendTriangle(const QVector2D &v1,
+ const QVector2D &v2,
+ const QVector2D &v3,
+ const QVector3D &uv1,
+ const QVector3D &uv2,
+ const QVector3D &uv3,
+ const QVector2D &n1,
+ const QVector2D &n2,
+ const QVector2D &n3,
+ const QVector2D &duvdx,
+ const QVector2D &duvdy)
+ {
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v1.x(), v1.y(),
+ uv1.x(), uv1.y(), uv1.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n1.x(), n1.y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v2.x(), v2.y(),
+ uv2.x(), uv2.y(), uv2.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n2.x(), n2.y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v3.x(), v3.y(),
+ uv3.x(), uv3.y(), uv3.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n3.x(), n3.y()
+ });
+ }
+
+ void appendTriangle(const QVector2D &v1,
+ const QVector2D &v2,
+ const QVector2D &v3,
+ std::function<QVector3D(QVector2D)> uvForPoint)
+ {
+ appendTriangle({v1, v2, v3}, {}, uvForPoint);
+ }
+
+ QVector<quint32> uncookedIndexes() const
+ {
+ return m_uncookedIndexes;
+ }
+
+ void cookGeometry() override;
+
+ void reserve(qsizetype size)
+ {
+ m_uncookedIndexes.reserve(size);
+ m_uncookedVertexes.reserve(size);
+ }
+
+ void preprocess() override
+ {
+ if (m_textureProvider != nullptr) {
+ if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(m_textureProvider->texture()))
+ texture->updateTexture();
+ }
+ }
+
+ QVector2D boundsSize() const
+ {
+ return m_boundsSize;
+ }
+
+ void setBoundsSize(const QVector2D &boundsSize)
+ {
+ m_boundsSize = boundsSize;
+ }
+
+private Q_SLOTS:
+ void handleTextureChanged()
+ {
+ markDirty(DirtyMaterial);
+ }
+
+ void handleTextureProviderDestroyed()
+ {
+ m_textureProvider = nullptr;
+ markDirty(DirtyMaterial);
+ }
+
+private:
+ struct CurveNodeVertex
+ {
+ float x, y, u, v, w;
+ float dudx, dvdx, dudy, dvdy; // Size of pixel in curve space (must be same for all vertices in triangle)
+ float nx, ny; // normal vector describing the direction to shift the vertex for AA
+ };
+
+ void updateMaterial();
+ static const QSGGeometry::AttributeSet &attributes();
+
+ QScopedPointer<QSGMaterial> m_material;
+
+ QVector<CurveNodeVertex> m_uncookedVertexes;
+ QVector<quint32> m_uncookedIndexes;
+
+ QSGGradientCache::GradientDesc m_fillGradient;
+ QSGTextureProvider *m_textureProvider = nullptr;
+ QVector2D m_boundsSize;
+ QSGTransform m_fillTransform;
+ QColor m_color = Qt::white;
+ QGradient::Type m_gradientType = QGradient::NoGradient;
+ float m_debug = 0.0f;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEFILLNODE_P_H
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p_p.h b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
new file mode 100644
index 0000000000..9cc80f3dca
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEFILLNODE_P_P_H
+#define QSGCURVEFILLNODE_P_P_H
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/qsgmaterial.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveFillNode;
+class QSGPlainTexture;
+class Q_QUICK_EXPORT QSGCurveFillMaterial : public QSGMaterial
+{
+public:
+ QSGCurveFillMaterial(QSGCurveFillNode *node);
+ ~QSGCurveFillMaterial() override;
+ int compare(const QSGMaterial *other) const override;
+
+ QSGCurveFillNode *node() const
+ {
+ return m_node;
+ }
+
+ QSGPlainTexture *dummyTexture() const
+ {
+ return m_dummyTexture;
+ }
+
+ void setDummyTexture(QSGPlainTexture *dummyTexture)
+ {
+ m_dummyTexture = dummyTexture;
+ }
+
+private:
+ QSGMaterialType *type() const override;
+ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
+
+ QSGCurveFillNode *m_node;
+ QSGPlainTexture *m_dummyTexture = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEFILLNODE_P_P_H
diff --git a/src/quick/scenegraph/qsgcurveglyphatlas.cpp b/src/quick/scenegraph/qsgcurveglyphatlas.cpp
new file mode 100644
index 0000000000..410ce2dd26
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphatlas.cpp
@@ -0,0 +1,142 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurveglyphatlas_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvestrokenode_p.h"
+#include "qsgcurveprocessor_p.h"
+#include "util/qquadpath_p.h"
+
+#include <QtGui/qrawfont.h>
+#include <QtGui/qpainterpath.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveGlyphAtlas::QSGCurveGlyphAtlas(const QRawFont &font)
+ : m_font(font)
+{
+ // The font size used for the curve atlas currently affects the outlines, since we don't
+ // really support cosmetic outlines. Therefore we need to pick one which gives large enough
+ // triangles relative to glyph size that we can reuse the same triangles for any font size.
+ // When experimenting, 10 works for all font sizes we tested, so we currently default to this
+ // but allow overriding it.
+ static int curveGlyphAtlasFontSize = qEnvironmentVariableIntValue("QSGCURVEGLYPHATLAS_FONT_SIZE");
+ m_font.setPixelSize(curveGlyphAtlasFontSize > 0 ? qreal(curveGlyphAtlasFontSize) : 10.0);
+}
+
+QSGCurveGlyphAtlas::~QSGCurveGlyphAtlas()
+{
+}
+
+void QSGCurveGlyphAtlas::populate(const QList<glyph_t> &glyphs)
+{
+ for (glyph_t glyphIndex : glyphs) {
+ if (!m_glyphs.contains(glyphIndex)) {
+ QPainterPath path = m_font.pathForGlyph(glyphIndex);
+ QQuadPath quadPath = QQuadPath::fromPainterPath(path);
+ quadPath.setFillRule(Qt::WindingFill);
+
+ Glyph glyph;
+
+ QSGCurveProcessor::processStroke(quadPath, 2, 2, Qt::MiterJoin, Qt::FlatCap,
+ [&glyph](const std::array<QVector2D, 3> &s,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n,
+ bool isLine) {
+ glyph.strokeVertices.append(s.at(0));
+ glyph.strokeVertices.append(s.at(1));
+ glyph.strokeVertices.append(s.at(2));
+
+ glyph.strokeUvs.append(p.at(0));
+ glyph.strokeUvs.append(p.at(1));
+ glyph.strokeUvs.append(p.at(2));
+
+ glyph.strokeNormals.append(n.at(0));
+ glyph.strokeNormals.append(n.at(1));
+ glyph.strokeNormals.append(n.at(2));
+
+ glyph.strokeElementIsLine.append(isLine);
+ });
+
+ quadPath = quadPath.subPathsClosed();
+ quadPath.addCurvatureData(); // ### Since the inside of glyphs is defined by order of
+ // vertices, this step could be simplified
+ QSGCurveProcessor::solveOverlaps(quadPath);
+
+ QSGCurveProcessor::processFill(quadPath,
+ Qt::WindingFill,
+ [&glyph](const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n,
+ QSGCurveProcessor::uvForPointCallback uvForPoint)
+ {
+ glyph.vertices.append(v.at(0));
+ glyph.vertices.append(v.at(1));
+ glyph.vertices.append(v.at(2));
+
+ QVector3D uv1 = uvForPoint(v.at(0));
+ glyph.uvs.append(uv1);
+ glyph.uvs.append(uvForPoint(v.at(1)));
+ glyph.uvs.append(uvForPoint(v.at(2)));
+
+ glyph.normals.append(n.at(0));
+ glyph.normals.append(n.at(1));
+ glyph.normals.append(n.at(2));
+
+ glyph.duvdx.append(QVector2D(uvForPoint(v.at(0) + QVector2D(1, 0))) - QVector2D(uv1));
+ glyph.duvdy.append(QVector2D(uvForPoint(v.at(0) + QVector2D(0, 1))) - QVector2D(uv1));
+ });
+
+ m_glyphs.insert(glyphIndex, glyph);
+ }
+ }
+}
+
+void QSGCurveGlyphAtlas::addStroke(QSGCurveStrokeNode *node,
+ glyph_t glyphIndex,
+ const QPointF &position) const
+{
+ const Glyph &glyph = m_glyphs[glyphIndex];
+
+ const QVector2D v(position);
+ for (qsizetype i = glyph.strokeElementIsLine.size() - 1; i >= 0; --i) {
+ QVector2D v1 = glyph.strokeVertices.at(i * 3 + 0) + v;
+ QVector2D v2 = glyph.strokeVertices.at(i * 3 + 1) + v;
+ QVector2D v3 = glyph.strokeVertices.at(i * 3 + 2) + v;
+ if (glyph.strokeElementIsLine.at(i)) {
+ node->appendTriangle({ v1, v2, v3 },
+ std::array<QVector2D, 2>({ glyph.strokeUvs.at(i * 3 + 0) + v, glyph.strokeUvs.at(i * 3 + 2) + v }),
+ { glyph.strokeNormals.at(i * 3 + 0), glyph.strokeNormals.at(i * 3 + 1), glyph.strokeNormals.at(i * 3 + 2) });
+ } else {
+ node->appendTriangle({ v1, v2, v3 },
+ { glyph.strokeUvs.at(i * 3 + 0) + v, glyph.strokeUvs.at(i * 3 + 1) + v, glyph.strokeUvs.at(i * 3 + 2) + v },
+ { glyph.strokeNormals.at(i * 3 + 0), glyph.strokeNormals.at(i * 3 + 1), glyph.strokeNormals.at(i * 3 + 2) });
+
+ }
+ }
+}
+
+void QSGCurveGlyphAtlas::addGlyph(QSGCurveFillNode *node,
+ glyph_t glyphIndex,
+ const QPointF &position,
+ qreal pixelSize) const
+{
+ const Glyph &glyph = m_glyphs[glyphIndex];
+
+ const float scaleFactor = pixelSize / m_font.pixelSize();
+ const QVector2D v(position);
+ for (qsizetype i = 0; i < glyph.vertices.size() / 3; ++i) {
+ node->appendTriangle(scaleFactor * glyph.vertices.at(i * 3 + 0) + v,
+ scaleFactor * glyph.vertices.at(i * 3 + 1) + v,
+ scaleFactor * glyph.vertices.at(i * 3 + 2) + v,
+ glyph.uvs.at(i * 3 + 0),
+ glyph.uvs.at(i * 3 + 1),
+ glyph.uvs.at(i * 3 + 2),
+ glyph.normals.at(i * 3 + 0),
+ glyph.normals.at(i * 3 + 1),
+ glyph.normals.at(i * 3 + 2),
+ glyph.duvdx.at(i) / scaleFactor,
+ glyph.duvdy.at(i) / scaleFactor);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveglyphatlas_p.h b/src/quick/scenegraph/qsgcurveglyphatlas_p.h
new file mode 100644
index 0000000000..c82f1921f9
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphatlas_p.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEGLYPHATLAS_P_H
+#define QSGCURVEGLYPHATLAS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qrawfont.h>
+#include <QtGui/private/qtextengine_p.h>
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveFillNode;
+class QSGCurveStrokeNode;
+
+class Q_QUICK_EXPORT QSGCurveGlyphAtlas
+{
+public:
+ QSGCurveGlyphAtlas(const QRawFont &font);
+ virtual ~QSGCurveGlyphAtlas();
+
+ void populate(const QList<glyph_t> &glyphs);
+ void addGlyph(QSGCurveFillNode *node,
+ glyph_t glyph,
+ const QPointF &position,
+ qreal pixelSize) const;
+ void addStroke(QSGCurveStrokeNode *node,
+ glyph_t glyph,
+ const QPointF &position) const;
+
+ qreal fontSize() const
+ {
+ return m_font.pixelSize();
+ }
+
+private:
+ struct Glyph
+ {
+ QList<QVector2D> vertices;
+ QList<QVector3D> uvs;
+ QList<QVector2D> normals;
+ QList<QVector2D> duvdx;
+ QList<QVector2D> duvdy;
+
+ QList<QVector2D> strokeVertices;
+ QList<QVector2D> strokeUvs;
+ QList<QVector2D> strokeNormals;
+ QList<bool> strokeElementIsLine;
+ };
+
+ QHash<glyph_t, Glyph> m_glyphs;
+ QRawFont m_font;
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QSGCURVEGLYPHATLAS_P_H
diff --git a/src/quick/scenegraph/qsgcurveglyphnode.cpp b/src/quick/scenegraph/qsgcurveglyphnode.cpp
new file mode 100644
index 0000000000..c5252083d3
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphnode.cpp
@@ -0,0 +1,164 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurveglyphnode_p.h"
+#include "qsgcurveglyphatlas_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvestrokenode_p.h"
+
+#include <private/qsgcurveabstractnode_p.h>
+#include <private/qsgcontext_p.h>
+#include <private/qsgtexturematerial_p.h>
+
+#include <private/qrawfont_p.h>
+#include <QtGui/qcolor.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveGlyphNode::QSGCurveGlyphNode(QSGRenderContext *context)
+ : m_context(context)
+ , m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0)
+ , m_dirtyGeometry(false)
+{
+ setFlag(UsePreprocess);
+ setFlag(OwnsMaterial);
+
+ // #### To avoid asserts: we should probably merge this with QSGCurveFillNode
+ setGeometry(&m_geometry);
+ setMaterial(new QSGTextureMaterial);
+}
+
+QSGCurveGlyphNode::~QSGCurveGlyphNode()
+{
+}
+
+void QSGCurveGlyphNode::setPreferredAntialiasingMode(AntialiasingMode mode)
+{
+ Q_UNUSED(mode);
+}
+
+void QSGCurveGlyphNode::setColor(const QColor &color)
+{
+ m_color = color;
+ if (m_glyphNode != nullptr)
+ m_glyphNode->setColor(color);
+}
+
+void QSGCurveGlyphNode::setStyleColor(const QColor &styleColor)
+{
+ m_styleColor = styleColor;
+ if (m_styleNode != nullptr)
+ m_styleNode->setColor(styleColor);
+}
+
+void QSGCurveGlyphNode::setStyle(QQuickText::TextStyle style)
+{
+ if (m_style != style) {
+ m_style = style;
+ m_dirtyGeometry = true;
+ update();
+ }
+}
+
+void QSGCurveGlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &glyphs)
+{
+ m_glyphs = glyphs;
+
+ QRawFont font = glyphs.rawFont();
+ m_fontSize = font.pixelSize();
+ m_position = QPointF(position.x(), position.y() - font.ascent());
+
+
+ m_dirtyGeometry = true;
+
+#ifdef QSG_RUNTIME_DESCRIPTION
+ qsgnode_set_description(this, QString::number(glyphs.glyphIndexes().count())
+ + QStringLiteral(" curve glyphs: ")
+ + m_glyphs.rawFont().familyName()
+ + QStringLiteral(" ")
+ + QString::number(m_glyphs.rawFont().pixelSize()));
+#endif
+}
+
+void QSGCurveGlyphNode::update()
+{
+ markDirty(DirtyGeometry);
+}
+
+void QSGCurveGlyphNode::preprocess()
+{
+ if (m_dirtyGeometry)
+ updateGeometry();
+}
+
+void QSGCurveGlyphNode::updateGeometry()
+{
+ delete m_glyphNode;
+ m_glyphNode = nullptr;
+
+ delete m_styleNode;
+ m_styleNode = nullptr;
+
+ QSGCurveGlyphAtlas *curveGlyphAtlas = m_context->curveGlyphAtlas(m_glyphs.rawFont());
+ curveGlyphAtlas->populate(m_glyphs.glyphIndexes());
+
+ m_glyphNode = new QSGCurveFillNode;
+ m_glyphNode->setColor(m_color);
+
+ QPointF offset;
+
+ float fontScale = float(m_fontSize / curveGlyphAtlas->fontSize());
+ QSGCurveFillNode *raisedSunkenStyleNode = nullptr;
+ QSGCurveStrokeNode *outlineNode = nullptr;
+ if (m_style == QQuickText::Raised || m_style == QQuickText::Sunken) {
+ raisedSunkenStyleNode = new QSGCurveFillNode;
+ raisedSunkenStyleNode ->setColor(m_styleColor);
+
+ offset = m_style == QQuickText::Raised ? QPointF(0.0f, 1.0f) : QPointF(0.0f, -1.0f);
+ m_styleNode = raisedSunkenStyleNode;
+ } else if (m_style == QQuickText::Outline) {
+ outlineNode = new QSGCurveStrokeNode;
+ outlineNode->setColor(m_styleColor);
+ outlineNode->setStrokeWidth(2 / fontScale);
+ outlineNode->setLocalScale(fontScale);
+
+ m_styleNode = outlineNode;
+ }
+
+ const QVector<quint32> indexes = m_glyphs.glyphIndexes();
+ const QVector<QPointF> positions = m_glyphs.positions();
+ for (qsizetype i = 0; i < indexes.size(); ++i) {
+ if (i == 0)
+ m_baseLine = positions.at(i);
+ curveGlyphAtlas->addGlyph(m_glyphNode,
+ indexes.at(i),
+ m_position + positions.at(i),
+ m_fontSize);
+ if (raisedSunkenStyleNode != nullptr) {
+ curveGlyphAtlas->addGlyph(raisedSunkenStyleNode,
+ indexes.at(i),
+ m_position + positions.at(i) + offset,
+ m_fontSize);
+ }
+ if (outlineNode != nullptr) {
+ // Since the stroke node will scale everything by fontScale internally (the
+ // shader does not support pre-transforming the vertices), we have to also first
+ // do the inverse scale on the glyph position to get the correct position.
+ curveGlyphAtlas->addStroke(outlineNode,
+ indexes.at(i),
+ (m_position + positions.at(i)) / fontScale);
+ }
+ }
+
+ if (m_styleNode != nullptr) {
+ m_styleNode->cookGeometry();
+ appendChildNode(m_styleNode);
+ }
+
+ m_glyphNode->cookGeometry();
+ appendChildNode(m_glyphNode);
+
+ m_dirtyGeometry = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveglyphnode_p.h b/src/quick/scenegraph/qsgcurveglyphnode_p.h
new file mode 100644
index 0000000000..cb634112d0
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphnode_p.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEGLYPHNODE_P_H
+#define QSGCURVEGLYPHNODE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qtquickexports.h>
+#include <private/qsgadaptationlayer_p.h>
+#include <private/qsgbasicglyphnode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveGlyphAtlas;
+class QSGCurveFillNode;
+class QSGCurveAbstractNode;
+
+class Q_QUICK_EXPORT QSGCurveGlyphNode : public QSGGlyphNode
+{
+public:
+ QSGCurveGlyphNode(QSGRenderContext *context);
+ ~QSGCurveGlyphNode();
+ void setGlyphs(const QPointF &position, const QGlyphRun &glyphs) override;
+ void update() override;
+ void preprocess() override;
+ void setPreferredAntialiasingMode(AntialiasingMode) override;
+ void updateGeometry();
+ void setColor(const QColor &color) override;
+ void setStyle(QQuickText::TextStyle style) override;
+
+ void setStyleColor(const QColor &color) override;
+ QPointF baseLine() const override { return m_baseLine; }
+
+private:
+ QSGRenderContext *m_context;
+ QSGGeometry m_geometry;
+ QColor m_color = Qt::black;
+
+ struct GlyphInfo {
+ QVector<quint32> indexes;
+ QVector<QPointF> positions;
+ };
+
+ uint m_dirtyGeometry: 1;
+ qreal m_fontSize = 0.0f;
+ QGlyphRun m_glyphs;
+ QQuickText::TextStyle m_style;
+ QColor m_styleColor;
+ QPointF m_baseLine;
+ QPointF m_position;
+
+ QSGCurveFillNode *m_glyphNode = nullptr;
+ QSGCurveAbstractNode *m_styleNode = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/quick/scenegraph/qsgcurveprocessor.cpp b/src/quick/scenegraph/qsgcurveprocessor.cpp
new file mode 100644
index 0000000000..771b2d7cf3
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveprocessor.cpp
@@ -0,0 +1,1887 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurveprocessor_p.h"
+
+#include <QtGui/private/qtriangulator_p.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcSGCurveProcessor, "qt.quick.curveprocessor");
+Q_LOGGING_CATEGORY(lcSGCurveIntersectionSolver, "qt.quick.curveprocessor.intersections");
+
+namespace {
+// Input coordinate space is pre-mapped so that (0, 0) maps to [0, 0] in uv space.
+// v1 maps to [1,0], v2 maps to [0,1]. p is the point to be mapped to uv in this space (i.e. vector from p0)
+static inline QVector2D uvForPoint(QVector2D v1, QVector2D v2, QVector2D p)
+{
+ double divisor = v1.x() * v2.y() - v2.x() * v1.y();
+
+ float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
+ float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
+
+ return {u, v};
+}
+
+// Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1
+// also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line
+static inline QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
+{
+ QVector2D v1 = 2 * (p1 - p0);
+ QVector2D v2 = p2 - v1 - p0;
+ return uvForPoint(v1, v2, p - p0);
+}
+
+static QVector3D elementUvForPoint(const QQuadPath::Element& e, QVector2D p)
+{
+ auto uv = curveUv(e.startPoint(), e.referencePoint(), e.endPoint(), p);
+ if (e.isLine())
+ return { uv.x(), uv.y(), 0.0f };
+ else
+ return { uv.x(), uv.y(), e.isConvex() ? -1.0f : 1.0f };
+}
+
+static inline QVector2D calcNormalVector(QVector2D a, QVector2D b)
+{
+ auto v = b - a;
+ return {v.y(), -v.x()};
+}
+
+// The sign of the return value indicates which side of the line defined by a and n the point p falls
+static inline float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
+{
+ float dot = QVector2D::dotProduct(p - a, n);
+ return dot;
+};
+
+static inline float determinant(const QVector2D &p1, const QVector2D &p2, const QVector2D &p3)
+{
+ return p1.x() * (p2.y() - p3.y())
+ + p2.x() * (p3.y() - p1.y())
+ + p3.x() * (p1.y() - p2.y());
+}
+
+/*
+ Clever triangle overlap algorithm. Stack Overflow says:
+
+ You can prove that the two triangles do not collide by finding an edge (out of the total 6
+ edges that make up the two triangles) that acts as a separating line where all the vertices
+ of one triangle lie on one side and the vertices of the other triangle lie on the other side.
+ If you can find such an edge then it means that the triangles do not intersect otherwise the
+ triangles are colliding.
+*/
+using TrianglePoints = std::array<QVector2D, 3>;
+using LinePoints = std::array<QVector2D, 2>;
+
+// The sign of the determinant tells the winding order: positive means counter-clockwise
+
+static inline double determinant(const TrianglePoints &p)
+{
+ return determinant(p[0], p[1], p[2]);
+}
+
+// Fix the triangle so that the determinant is positive
+static void fixWinding(TrianglePoints &p)
+{
+ double det = determinant(p);
+ if (det < 0.0) {
+ qSwap(p[0], p[1]);
+ }
+}
+
+// Return true if the determinant is negative, i.e. if the winding order is opposite of the triangle p1,p2,p3.
+// This means that p is strictly on the other side of p1-p2 relative to p3 [where p1,p2,p3 is a triangle with
+// a positive determinant].
+bool checkEdge(QVector2D &p1, QVector2D &p2, QVector2D &p, float epsilon)
+{
+ return determinant(p1, p2, p) <= epsilon;
+}
+
+// Check if lines l1 and l2 are intersecting and return the respective value. Solutions are stored to
+// the optional pointer solution.
+bool lineIntersection(const LinePoints &l1, const LinePoints &l2, QList<QPair<float, float>> *solution = nullptr)
+{
+ constexpr double eps2 = 1e-5; // Epsilon for parameter space t1-t2
+
+ // see https://www.wolframalpha.com/input?i=solve%28A+%2B+t+*+B+%3D+C+%2B+s*D%3B+E+%2B+t+*+F+%3D+G+%2B+s+*+H+for+s+and+t%29
+ const float A = l1[0].x();
+ const float B = l1[1].x() - l1[0].x();
+ const float C = l2[0].x();
+ const float D = l2[1].x() - l2[0].x();
+ const float E = l1[0].y();
+ const float F = l1[1].y() - l1[0].y();
+ const float G = l2[0].y();
+ const float H = l2[1].y() - l2[0].y();
+
+ float det = D * F - B * H;
+
+ if (det == 0)
+ return false;
+
+ float s = (F * (A - C) - B * (E - G)) / det;
+ float t = (H * (A - C) - D * (E - G)) / det;
+
+ // Intersections at 0 count. Intersections at 1 do not.
+ bool intersecting = (s >= 0 && s <= 1. - eps2 && t >= 0 && t <= 1. - eps2);
+
+ if (solution && intersecting)
+ solution->append(QPair<float, float>(t, s));
+
+ return intersecting;
+}
+
+
+bool checkTriangleOverlap(TrianglePoints &triangle1, TrianglePoints &triangle2, float epsilon = 1.0/32)
+{
+ // See if there is an edge of triangle1 such that all vertices in triangle2 are on the opposite side
+ fixWinding(triangle1);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+ if (checkEdge(triangle1[i], triangle1[ni], triangle2[0], epsilon) &&
+ checkEdge(triangle1[i], triangle1[ni], triangle2[1], epsilon) &&
+ checkEdge(triangle1[i], triangle1[ni], triangle2[2], epsilon))
+ return false;
+ }
+
+ // See if there is an edge of triangle2 such that all vertices in triangle1 are on the opposite side
+ fixWinding(triangle2);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+
+ if (checkEdge(triangle2[i], triangle2[ni], triangle1[0], epsilon) &&
+ checkEdge(triangle2[i], triangle2[ni], triangle1[1], epsilon) &&
+ checkEdge(triangle2[i], triangle2[ni], triangle1[2], epsilon))
+ return false;
+ }
+
+ return true;
+}
+
+bool checkLineTriangleOverlap(TrianglePoints &triangle, LinePoints &line, float epsilon = 1.0/32)
+{
+ // See if all vertices of the triangle are on the same side of the line
+ bool s1 = determinant(line[0], line[1], triangle[0]) < 0;
+ auto s2 = determinant(line[0], line[1], triangle[1]) < 0;
+ auto s3 = determinant(line[0], line[1], triangle[2]) < 0;
+ // If all determinants have the same sign, then there is no overlap
+ if (s1 == s2 && s2 == s3) {
+ return false;
+ }
+ // See if there is an edge of triangle1 such that both vertices in line are on the opposite side
+ fixWinding(triangle);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+ if (checkEdge(triangle[i], triangle[ni], line[0], epsilon) &&
+ checkEdge(triangle[i], triangle[ni], line[1], epsilon))
+ return false;
+ }
+
+ return true;
+}
+
+static bool isOverlap(const QQuadPath &path, int e1, int e2)
+{
+ const QQuadPath::Element &element1 = path.elementAt(e1);
+ const QQuadPath::Element &element2 = path.elementAt(e2);
+
+ if (element1.isLine()) {
+ LinePoints line1{ element1.startPoint(), element1.endPoint() };
+ if (element2.isLine()) {
+ LinePoints line2{ element2.startPoint(), element2.endPoint() };
+ return lineIntersection(line1, line2);
+ } else {
+ TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
+ return checkLineTriangleOverlap(t2, line1);
+ }
+ } else {
+ TrianglePoints t1{ element1.startPoint(), element1.controlPoint(), element1.endPoint() };
+ if (element2.isLine()) {
+ LinePoints line{ element2.startPoint(), element2.endPoint() };
+ return checkLineTriangleOverlap(t1, line);
+ } else {
+ TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
+ return checkTriangleOverlap(t1, t2);
+ }
+ }
+
+ return false;
+}
+
+static float angleBetween(const QVector2D v1, const QVector2D v2)
+{
+ float dot = v1.x() * v2.x() + v1.y() * v2.y();
+ float cross = v1.x() * v2.y() - v1.y() * v2.x();
+ //TODO: Optimization: Maybe we don't need the atan2 here.
+ return atan2(cross, dot);
+}
+
+static bool isIntersecting(const TrianglePoints &t1, const TrianglePoints &t2, QList<QPair<float, float>> *solutions = nullptr)
+{
+ constexpr double eps = 1e-5; // Epsilon for coordinate space x-y
+ constexpr double eps2 = 1e-5; // Epsilon for parameter space t1-t2
+ constexpr int maxIterations = 7; // Maximum iterations allowed for Newton
+
+ // Convert to double to get better accuracy.
+ QPointF td1[3] = { t1[0].toPointF(), t1[1].toPointF(), t1[2].toPointF() };
+ QPointF td2[3] = { t2[0].toPointF(), t2[1].toPointF(), t2[2].toPointF() };
+
+ // F = P1(t1) - P2(t2) where P1 and P2 are bezier curve functions.
+ // F = (0, 0) at the intersection.
+ // t is the vector of bezier curve parameters for curves P1 and P2
+ auto F = [=](QPointF t) { return
+ td1[0] * (1 - t.x()) * (1. - t.x()) + 2 * td1[1] * (1. - t.x()) * t.x() + td1[2] * t.x() * t.x() -
+ td2[0] * (1 - t.y()) * (1. - t.y()) - 2 * td2[1] * (1. - t.y()) * t.y() - td2[2] * t.y() * t.y();};
+
+ // J is the Jacobi Matrix dF/dt where F and t are both vectors of dimension 2.
+ // Storing in a QLineF for simplicity.
+ auto J = [=](QPointF t) { return QLineF(
+ td1[0].x() * (-2 * (1-t.x())) + 2 * td1[1].x() * (1 - 2 * t.x()) + td1[2].x() * 2 * t.x(),
+ -td2[0].x() * (-2 * (1-t.y())) - 2 * td2[1].x() * (1 - 2 * t.y()) - td2[2].x() * 2 * t.y(),
+ td1[0].y() * (-2 * (1-t.x())) + 2 * td1[1].y() * (1 - 2 * t.x()) + td1[2].y() * 2 * t.x(),
+ -td2[0].y() * (-2 * (1-t.y())) - 2 * td2[1].y() * (1 - 2 * t.y()) - td2[2].y() * 2 * t.y());};
+
+ // solve the equation A(as 2x2 matrix)*x = b. Returns x.
+ auto solve = [](QLineF A, QPointF b) {
+ // invert A
+ const double det = A.x1() * A.y2() - A.y1() * A.x2();
+ QLineF Ainv(A.y2() / det, -A.y1() / det, -A.x2() / det, A.x1() / det);
+ // return A^-1 * b
+ return QPointF(Ainv.x1() * b.x() + Ainv.y1() * b.y(),
+ Ainv.x2() * b.x() + Ainv.y2() * b.y());
+ };
+
+#ifdef INTERSECTION_EXTRA_DEBUG
+ qCDebug(lcSGCurveIntersectionSolver) << "Checking" << t1[0] << t1[1] << t1[2];
+ qCDebug(lcSGCurveIntersectionSolver) << " vs" << t2[0] << t2[1] << t2[2];
+#endif
+
+ // TODO: Try to figure out reasonable starting points to reach all 4 possible intersections.
+ // This works but is kinda brute forcing it.
+ constexpr std::array tref = { QPointF{0.0, 0.0}, QPointF{0.5, 0.0}, QPointF{1.0, 0.0},
+ QPointF{0.0, 0.5}, QPointF{0.5, 0.5}, QPointF{1.0, 0.5},
+ QPointF{0.0, 1.0}, QPointF{0.5, 1.0}, QPointF{1.0, 1.0} };
+
+ for (auto t : tref) {
+ double err = 1;
+ QPointF fval = F(t);
+ int i = 0;
+
+ // TODO: Try to abort sooner, e.g. when falling out of the interval [0-1]?
+ while (err > eps && i < maxIterations) { // && t.x() >= 0 && t.x() <= 1 && t.y() >= 0 && t.y() <= 1) {
+ t = t - solve(J(t), fval);
+ fval = F(t);
+ err = qAbs(fval.x()) + qAbs(fval.y()); // Using the Manhatten length as an error indicator.
+ i++;
+#ifdef INTERSECTION_EXTRA_DEBUG
+ qCDebug(lcSGCurveIntersectionSolver) << " Newton iteration" << i << "t =" << t << "F =" << fval << "Error =" << err;
+#endif
+ }
+ // Intersections at 0 count. Intersections at 1 do not.
+ if (err < eps && t.x() >=0 && t.x() <= 1. - 10 * eps2 && t.y() >= 0 && t.y() <= 1. - 10 * eps2) {
+#ifdef INTERSECTION_EXTRA_DEBUG
+ qCDebug(lcSGCurveIntersectionSolver) << " Newton solution (after" << i << ")=" << t << "(" << F(t) << ")";
+#endif
+ if (solutions) {
+ bool append = true;
+ for (auto solution : *solutions) {
+ if (qAbs(solution.first - t.x()) < 10 * eps2 && qAbs(solution.second - t.y()) < 10 * eps2) {
+ append = false;
+ break;
+ }
+ }
+ if (append)
+ solutions->append({t.x(), t.y()});
+ }
+ else
+ return true;
+ }
+ }
+ if (solutions)
+ return solutions->size() > 0;
+ else
+ return false;
+}
+
+static bool isIntersecting(const QQuadPath &path, int e1, int e2, QList<QPair<float, float>> *solutions = nullptr)
+{
+
+ const QQuadPath::Element &elem1 = path.elementAt(e1);
+ const QQuadPath::Element &elem2 = path.elementAt(e2);
+
+ if (elem1.isLine() && elem2.isLine()) {
+ return lineIntersection(LinePoints {elem1.startPoint(), elem1.endPoint() },
+ LinePoints {elem2.startPoint(), elem2.endPoint() },
+ solutions);
+ } else {
+ return isIntersecting(TrianglePoints { elem1.startPoint(), elem1.controlPoint(), elem1.endPoint() },
+ TrianglePoints { elem2.startPoint(), elem2.controlPoint(), elem2.endPoint() },
+ solutions);
+ }
+}
+
+struct TriangleData
+{
+ TrianglePoints points;
+ int pathElementIndex;
+ TrianglePoints normals;
+};
+
+// Returns a normalized vector that is perpendicular to baseLine, pointing to the right
+inline QVector2D normalVector(QVector2D baseLine)
+{
+ QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
+ return normal;
+}
+
+// Returns a vector that is normal to the path and pointing to the right. If endSide is false
+// the vector is normal to the start point, otherwise to the end point
+QVector2D normalVector(const QQuadPath::Element &element, bool endSide = false)
+{
+ if (element.isLine())
+ return normalVector(element.endPoint() - element.startPoint());
+ else if (!endSide)
+ return normalVector(element.controlPoint() - element.startPoint());
+ else
+ return normalVector(element.endPoint() - element.controlPoint());
+}
+
+// Returns a vector that is parallel to the path. If endSide is false
+// the vector starts at the start point and points forward,
+// otherwise it starts at the end point and points backward
+QVector2D tangentVector(const QQuadPath::Element &element, bool endSide = false)
+{
+ if (element.isLine()) {
+ if (!endSide)
+ return element.endPoint() - element.startPoint();
+ else
+ return element.startPoint() - element.endPoint();
+ } else {
+ if (!endSide)
+ return element.controlPoint() - element.startPoint();
+ else
+ return element.controlPoint() - element.endPoint();
+ }
+}
+
+// Really simplistic O(n^2) triangulator - only intended for five points
+QList<TriangleData> simplePointTriangulator(const QList<QVector2D> &pts, const QList<QVector2D> &normals, int elementIndex)
+{
+ int count = pts.size();
+ Q_ASSERT(count >= 3);
+ Q_ASSERT(normals.size() == count);
+
+ // First we find the convex hull: it's always in positive determinant winding order
+ QList<int> hull;
+ float det1 = determinant(pts[0], pts[1], pts[2]);
+ if (det1 > 0)
+ hull << 0 << 1 << 2;
+ else
+ hull << 2 << 1 << 0;
+ auto connectableInHull = [&](int idx) -> QList<int> {
+ QList<int> r;
+ const int n = hull.size();
+ const auto &pt = pts[idx];
+ for (int i = 0; i < n; ++i) {
+ const auto &i1 = hull.at(i);
+ const auto &i2 = hull.at((i+1) % n);
+ if (determinant(pts[i1], pts[i2], pt) < 0.0f)
+ r << i;
+ }
+ return r;
+ };
+ for (int i = 3; i < count; ++i) {
+ auto visible = connectableInHull(i);
+ if (visible.isEmpty())
+ continue;
+ int visCount = visible.count();
+ int hullCount = hull.count();
+ // Find where the visible part of the hull starts. (This is the part we need to triangulate to,
+ // and the part we're going to replace. "visible" contains the start point of the line segments that are visible from p.
+ int boundaryStart = visible[0];
+ for (int j = 0; j < visCount - 1; ++j) {
+ if ((visible[j] + 1) % hullCount != visible[j+1]) {
+ boundaryStart = visible[j + 1];
+ break;
+ }
+ }
+ // Finally replace the points that are now inside the hull
+ // We insert the new point after boundaryStart, and before boundaryStart + visCount (modulo...)
+ // and remove the points in between
+ int pointsToKeep = hullCount - visCount + 1;
+ QList<int> newHull;
+ newHull << i;
+ for (int j = 0; j < pointsToKeep; ++j) {
+ newHull << hull.at((j + boundaryStart + visCount) % hullCount);
+ }
+ hull = newHull;
+ }
+
+ // Now that we have a convex hull, we can trivially triangulate it
+ QList<TriangleData> ret;
+ for (int i = 1; i < hull.size() - 1; ++i) {
+ int i0 = hull[0];
+ int i1 = hull[i];
+ int i2 = hull[i+1];
+ ret.append({{pts[i0], pts[i1], pts[i2]}, elementIndex, {normals[i0], normals[i1], normals[i2]}});
+ }
+ return ret;
+}
+
+
+inline bool needsSplit(const QQuadPath::Element &el)
+{
+ Q_ASSERT(!el.isLine());
+ const auto v1 = el.controlPoint() - el.startPoint();
+ const auto v2 = el.endPoint() - el.controlPoint();
+ float cos = QVector2D::dotProduct(v1, v2) / (v1.length() * v2.length());
+ return cos < 0.9;
+}
+
+
+inline void splitElementIfNecessary(QQuadPath *path, int index, int level) {
+ if (level > 0 && needsSplit(path->elementAt(index))) {
+ path->splitElementAt(index);
+ splitElementIfNecessary(path, path->indexOfChildAt(index, 0), level - 1);
+ splitElementIfNecessary(path, path->indexOfChildAt(index, 1), level - 1);
+ }
+}
+
+static QQuadPath subdivide(const QQuadPath &path, int subdivisions)
+{
+ QQuadPath newPath = path;
+ newPath.iterateElements([&](QQuadPath::Element &e, int index) {
+ if (!e.isLine())
+ splitElementIfNecessary(&newPath, index, subdivisions);
+ });
+
+ return newPath;
+}
+
+static QList<TriangleData> customTriangulator2(const QQuadPath &path, float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle, float miterLimit)
+{
+ const bool bevelJoin = joinStyle == Qt::BevelJoin;
+ const bool roundJoin = joinStyle == Qt::RoundJoin;
+ const bool miterJoin = !bevelJoin && !roundJoin;
+
+ const bool roundCap = capStyle == Qt::RoundCap;
+ const bool squareCap = capStyle == Qt::SquareCap;
+ // We can't use the simple miter for miter joins, since the shader currently only supports round joins
+ const bool simpleMiter = joinStyle == Qt::RoundJoin;
+
+ Q_ASSERT(miterLimit > 0 || !miterJoin);
+ float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
+
+ const float penFactor = penWidth / 2;
+
+ // Returns {inner1, inner2, outer1, outer2, outerMiter}
+ // where foo1 is for the end of element1 and foo2 is for the start of element2
+ // and inner1 == inner2 unless we had to give up finding a decent point
+ auto calculateJoin = [&](const QQuadPath::Element *element1, const QQuadPath::Element *element2,
+ bool &outerBisectorWithinMiterLimit, bool &innerIsRight, bool &giveUp) -> std::array<QVector2D, 5>
+ {
+ outerBisectorWithinMiterLimit = true;
+ innerIsRight = true;
+ giveUp = false;
+ if (!element1) {
+ Q_ASSERT(element2);
+ QVector2D n = normalVector(*element2);
+ return {n, n, -n, -n, -n};
+ }
+ if (!element2) {
+ Q_ASSERT(element1);
+ QVector2D n = normalVector(*element1, true);
+ return {n, n, -n, -n, -n};
+ }
+
+ Q_ASSERT(element1->endPoint() == element2->startPoint());
+
+ const auto p1 = element1->isLine() ? element1->startPoint() : element1->controlPoint();
+ const auto p2 = element1->endPoint();
+ const auto p3 = element2->isLine() ? element2->endPoint() : element2->controlPoint();
+
+ const auto v1 = (p1 - p2).normalized();
+ const auto v2 = (p3 - p2).normalized();
+ const auto b = (v1 + v2);
+
+ constexpr float epsilon = 1.0f / 32.0f;
+ bool smoothJoin = qAbs(b.x()) < epsilon && qAbs(b.y()) < epsilon;
+
+ if (smoothJoin) {
+ // v1 and v2 are almost parallel and pointing in opposite directions
+ // angle bisector formula will give an almost null vector: use normal of bisector of normals instead
+ QVector2D n1(-v1.y(), v1.x());
+ QVector2D n2(-v2.y(), v2.x());
+ QVector2D n = (n2 - n1).normalized();
+ return {n, n, -n, -n, -n};
+ }
+ // Calculate the length of the bisector, so it will cover the entire miter.
+ // Using the identity sin(x/2) == sqrt((1 - cos(x)) / 2), and the fact that the
+ // dot product of two unit vectors is the cosine of the angle between them
+ // The length of the miter is w/sin(x/2) where x is the angle between the two elements
+
+ const auto bisector = b.normalized();
+ float cos2x = QVector2D::dotProduct(v1, v2);
+ cos2x = qMin(1.0f, cos2x); // Allow for float inaccuracy
+ float sine = sqrt((1.0f - cos2x) / 2);
+ innerIsRight = determinant(p1, p2, p3) > 0;
+ sine = qMax(sine, 0.01f); // Avoid divide by zero
+ float length = penFactor / sine;
+
+ // Check if bisector is longer than one of the lines it's trying to bisect
+
+ auto tooLong = [](QVector2D p1, QVector2D p2, QVector2D n, float length, float margin) -> bool {
+ auto v = p2 - p1;
+ // It's too long if the projection onto the bisector is longer than the bisector
+ // and the projection onto the normal to the bisector is shorter
+ // than the pen margin (that projection is just v - proj)
+ // (we're adding a 10% safety margin to make room for AA -- not exact)
+ auto projLen = QVector2D::dotProduct(v, n);
+ return projLen * 0.9f < length && (v - n * projLen).length() * 0.9 < margin;
+ };
+
+
+ // The angle bisector of the tangent lines is not correct for curved lines. We could fix this by calculating
+ // the exact intersection point, but for now just give up and use the normals.
+
+ giveUp = !element1->isLine() || !element2->isLine()
+ || tooLong(p1, p2, bisector, length, penFactor)
+ || tooLong(p3, p2, bisector, length, penFactor);
+ outerBisectorWithinMiterLimit = sine >= inverseMiterLimit / 2.0f;
+ bool simpleJoin = simpleMiter && outerBisectorWithinMiterLimit && !giveUp;
+ const QVector2D bn = bisector / sine;
+
+ if (simpleJoin)
+ return {bn, bn, -bn, -bn, -bn}; // We only have one inner and one outer point TODO: change inner point when conflict/curve
+ const QVector2D n1 = normalVector(*element1, true);
+ const QVector2D n2 = normalVector(*element2);
+ if (giveUp) {
+ if (innerIsRight)
+ return {n1, n2, -n1, -n2, -bn};
+ else
+ return {-n1, -n2, n1, n2, -bn};
+
+ } else {
+ if (innerIsRight)
+ return {bn, bn, -n1, -n2, -bn};
+ else
+ return {bn, bn, n1, n2, -bn};
+ }
+ };
+
+ QList<TriangleData> ret;
+
+ auto triangulateCurve = [&](int idx, const QVector2D &p1, const QVector2D &p2, const QVector2D &p3, const QVector2D &p4,
+ const QVector2D &n1, const QVector2D &n2, const QVector2D &n3, const QVector2D &n4)
+ {
+ const auto &element = path.elementAt(idx);
+ Q_ASSERT(!element.isLine());
+ const auto &s = element.startPoint();
+ const auto &c = element.controlPoint();
+ const auto &e = element.endPoint();
+ // TODO: Don't flatten the path in addCurveStrokeNodes, but iterate over the children here instead
+ bool controlPointOnRight = determinant(s, c, e) > 0;
+ QVector2D startNormal = normalVector(element);
+ QVector2D endNormal = normalVector(element, true);
+ QVector2D controlPointNormal = (startNormal + endNormal).normalized();
+ if (controlPointOnRight)
+ controlPointNormal = -controlPointNormal;
+ QVector2D p5 = c + controlPointNormal * penFactor; // This is too simplistic
+ TrianglePoints t1{p1, p2, p5};
+ TrianglePoints t2{p3, p4, p5};
+ bool simpleCase = !checkTriangleOverlap(t1, t2);
+
+ if (simpleCase) {
+ ret.append({{p1, p2, p5}, idx, {n1, n2, controlPointNormal}});
+ ret.append({{p3, p4, p5}, idx, {n3, n4, controlPointNormal}});
+ if (controlPointOnRight) {
+ ret.append({{p1, p3, p5}, idx, {n1, n3, controlPointNormal}});
+ } else {
+ ret.append({{p2, p4, p5}, idx, {n2, n4, controlPointNormal}});
+ }
+ } else {
+ ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx));
+ }
+ };
+
+ // Each element is calculated independently, so we don't have to special-case closed sub-paths.
+ // Take care so the end points of one element are precisely equal to the start points of the next.
+ // Any additional triangles needed for joining are added at the end of the current element.
+
+ int count = path.elementCount();
+ int subStart = 0;
+ while (subStart < count) {
+ int subEnd = subStart;
+ for (int i = subStart + 1; i < count; ++i) {
+ const auto &e = path.elementAt(i);
+ if (e.isSubpathStart()) {
+ subEnd = i - 1;
+ break;
+ }
+ if (i == count - 1) {
+ subEnd = i;
+ break;
+ }
+ }
+ bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
+ const int subCount = subEnd - subStart + 1;
+
+ auto addIdx = [&](int idx, int delta) -> int {
+ int subIdx = idx - subStart;
+ if (closed)
+ subIdx = (subIdx + subCount + delta) % subCount;
+ else
+ subIdx += delta;
+ return subStart + subIdx;
+ };
+ auto elementAt = [&](int idx, int delta) -> const QQuadPath::Element * {
+ int subIdx = idx - subStart;
+ if (closed) {
+ subIdx = (subIdx + subCount + delta) % subCount;
+ return &path.elementAt(subStart + subIdx);
+ }
+ subIdx += delta;
+ if (subIdx >= 0 && subIdx < subCount)
+ return &path.elementAt(subStart + subIdx);
+ return nullptr;
+ };
+
+ for (int i = subStart; i <= subEnd; ++i) {
+ const auto &element = path.elementAt(i);
+ const auto *nextElement = elementAt(i, +1);
+ const auto *prevElement = elementAt(i, -1);
+
+ const auto &s = element.startPoint();
+ const auto &e = element.endPoint();
+
+ bool startInnerIsRight;
+ bool startBisectorWithinMiterLimit; // Not used
+ bool giveUpOnStartJoin; // Not used
+ auto startJoin = calculateJoin(prevElement, &element,
+ startBisectorWithinMiterLimit, startInnerIsRight,
+ giveUpOnStartJoin);
+ const QVector2D &startInner = startJoin[1];
+ const QVector2D &startOuter = startJoin[3];
+
+ bool endInnerIsRight;
+ bool endBisectorWithinMiterLimit;
+ bool giveUpOnEndJoin;
+ auto endJoin = calculateJoin(&element, nextElement,
+ endBisectorWithinMiterLimit, endInnerIsRight,
+ giveUpOnEndJoin);
+ QVector2D endInner = endJoin[0];
+ QVector2D endOuter = endJoin[2];
+ QVector2D nextOuter = endJoin[3];
+ QVector2D outerB = endJoin[4];
+
+ QVector2D p1, p2, p3, p4;
+ QVector2D n1, n2, n3, n4;
+
+ if (startInnerIsRight) {
+ n1 = startInner;
+ n2 = startOuter;
+ } else {
+ n1 = startOuter;
+ n2 = startInner;
+ }
+
+ p1 = s + n1 * penFactor;
+ p2 = s + n2 * penFactor;
+
+ // repeat logic above for the other end:
+ if (endInnerIsRight) {
+ n3 = endInner;
+ n4 = endOuter;
+ } else {
+ n3 = endOuter;
+ n4 = endInner;
+ }
+
+ p3 = e + n3 * penFactor;
+ p4 = e + n4 * penFactor;
+
+ // End caps
+
+ if (!prevElement) {
+ QVector2D capSpace = tangentVector(element).normalized() * -penFactor;
+ if (roundCap) {
+ p1 += capSpace;
+ p2 += capSpace;
+ } else if (squareCap) {
+ QVector2D c1 = p1 + capSpace;
+ QVector2D c2 = p2 + capSpace;
+ ret.append({{p1, s, c1}, -1, {}});
+ ret.append({{c1, s, c2}, -1, {}});
+ ret.append({{p2, s, c2}, -1, {}});
+ }
+ }
+ if (!nextElement) {
+ QVector2D capSpace = tangentVector(element, true).normalized() * -penFactor;
+ if (roundCap) {
+ p3 += capSpace;
+ p4 += capSpace;
+ } else if (squareCap) {
+ QVector2D c3 = p3 + capSpace;
+ QVector2D c4 = p4 + capSpace;
+ ret.append({{p3, e, c3}, -1, {}});
+ ret.append({{c3, e, c4}, -1, {}});
+ ret.append({{p4, e, c4}, -1, {}});
+ }
+ }
+
+ if (element.isLine()) {
+ ret.append({{p1, p2, p3}, i, {n1, n2, n3}});
+ ret.append({{p2, p3, p4}, i, {n2, n3, n4}});
+ } else {
+ triangulateCurve(i, p1, p2, p3, p4, n1, n2, n3, n4);
+ }
+
+ bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
+ if (!trivialJoin && nextElement) {
+ // inside of join (opposite of bevel) is defined by
+ // triangle s, e, next.e
+ bool innerOnRight = endInnerIsRight;
+
+ const auto outer1 = e + endOuter * penFactor;
+ const auto outer2 = e + nextOuter * penFactor;
+ //const auto inner = e + endInner * penFactor;
+
+ if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
+ ret.append({{outer1, e, outer2}, -1, {}});
+ } else if (roundJoin) {
+ ret.append({{outer1, e, outer2}, i, {}});
+ QVector2D nn = calcNormalVector(outer1, outer2).normalized() * penFactor;
+ if (!innerOnRight)
+ nn = -nn;
+ ret.append({{outer1, outer1 + nn, outer2}, i, {}});
+ ret.append({{outer1 + nn, outer2, outer2 + nn}, i, {}});
+
+ } else if (miterJoin) {
+ QVector2D outer = e + outerB * penFactor;
+ ret.append({{outer1, e, outer}, -2, {}});
+ ret.append({{outer, e, outer2}, -2, {}});
+ }
+
+ if (!giveUpOnEndJoin) {
+ QVector2D inner = e + endInner * penFactor;
+ ret.append({{inner, e, outer1}, i, {endInner, {}, endOuter}});
+ // The remaining triangle ought to be done by nextElement, but we don't have start join logic there (yet)
+ int nextIdx = addIdx(i, +1);
+ ret.append({{inner, e, outer2}, nextIdx, {endInner, {}, nextOuter}});
+ }
+ }
+ }
+ subStart = subEnd + 1;
+ }
+ return ret;
+}
+
+// TODO: we could optimize by preprocessing e1, since we call this function multiple times on the same
+// elements
+// Returns true if a change was made
+static bool handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel = 0)
+{
+ // Splitting lines is not going to help with overlap, since we assume that lines don't intersect
+ if (path.elementAt(e1).isLine() && path.elementAt(e1).isLine())
+ return false;
+
+ if (!isOverlap(path, e1, e2)) {
+ return false;
+ }
+
+ if (recursionLevel > 8) {
+ qCDebug(lcSGCurveProcessor) << "Triangle overlap: recursion level" << recursionLevel << "aborting!";
+ return false;
+ }
+
+ if (path.elementAt(e1).childCount() > 1) {
+ auto e11 = path.indexOfChildAt(e1, 0);
+ auto e12 = path.indexOfChildAt(e1, 1);
+ handleOverlap(path, e11, e2, recursionLevel + 1);
+ handleOverlap(path, e12, e2, recursionLevel + 1);
+ } else if (path.elementAt(e2).childCount() > 1) {
+ auto e21 = path.indexOfChildAt(e2, 0);
+ auto e22 = path.indexOfChildAt(e2, 1);
+ handleOverlap(path, e1, e21, recursionLevel + 1);
+ handleOverlap(path, e1, e22, recursionLevel + 1);
+ } else {
+ path.splitElementAt(e1);
+ auto e11 = path.indexOfChildAt(e1, 0);
+ auto e12 = path.indexOfChildAt(e1, 1);
+ bool overlap1 = isOverlap(path, e11, e2);
+ bool overlap2 = isOverlap(path, e12, e2);
+ if (!overlap1 && !overlap2)
+ return true; // no more overlap: success!
+
+ // We need to split more:
+ if (path.elementAt(e2).isLine()) {
+ // Splitting a line won't help, so we just split e1 further
+ if (overlap1)
+ handleOverlap(path, e11, e2, recursionLevel + 1);
+ if (overlap2)
+ handleOverlap(path, e12, e2, recursionLevel + 1);
+ } else {
+ // See if splitting e2 works:
+ path.splitElementAt(e2);
+ auto e21 = path.indexOfChildAt(e2, 0);
+ auto e22 = path.indexOfChildAt(e2, 1);
+ if (overlap1) {
+ handleOverlap(path, e11, e21, recursionLevel + 1);
+ handleOverlap(path, e11, e22, recursionLevel + 1);
+ }
+ if (overlap2) {
+ handleOverlap(path, e12, e21, recursionLevel + 1);
+ handleOverlap(path, e12, e22, recursionLevel + 1);
+ }
+ }
+ }
+ return true;
+}
+}
+
+// Returns true if the path was changed
+bool QSGCurveProcessor::solveOverlaps(QQuadPath &path)
+{
+ bool changed = false;
+ if (path.testHint(QQuadPath::PathNonOverlappingControlPointTriangles))
+ return false;
+
+ const auto candidates = findOverlappingCandidates(path);
+ for (auto candidate : candidates)
+ changed = handleOverlap(path, candidate.first, candidate.second) || changed;
+
+ path.setHint(QQuadPath::PathNonOverlappingControlPointTriangles);
+ return changed;
+}
+
+// A fast algorithm to find path elements that might overlap. We will only check the overlap of the
+// triangles that define the elements.
+// We will order the elements first and then pool them depending on their x-values. This should
+// reduce the complexity to O(n log n), where n is the number of elements in the path.
+QList<QPair<int, int>> QSGCurveProcessor::findOverlappingCandidates(const QQuadPath &path)
+{
+ struct BRect { float xmin; float xmax; float ymin; float ymax; };
+
+ // Calculate all bounding rectangles
+ QVarLengthArray<int, 64> elementStarts, elementEnds;
+ QVarLengthArray<BRect, 64> boundingRects;
+ elementStarts.reserve(path.elementCount());
+ boundingRects.reserve(path.elementCount());
+ for (int i = 0; i < path.elementCount(); i++) {
+ QQuadPath::Element e = path.elementAt(i);
+
+ BRect bR{qMin(qMin(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
+ qMax(qMax(e.startPoint().x(), e.controlPoint().x()), e.endPoint().x()),
+ qMin(qMin(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y()),
+ qMax(qMax(e.startPoint().y(), e.controlPoint().y()), e.endPoint().y())};
+ boundingRects.append(bR);
+ elementStarts.append(i);
+ }
+
+ // Sort the bounding rectangles by x-startpoint and x-endpoint
+ auto compareXmin = [&](int i, int j){return boundingRects.at(i).xmin < boundingRects.at(j).xmin;};
+ auto compareXmax = [&](int i, int j){return boundingRects.at(i).xmax < boundingRects.at(j).xmax;};
+ std::sort(elementStarts.begin(), elementStarts.end(), compareXmin);
+ elementEnds = elementStarts;
+ std::sort(elementEnds.begin(), elementEnds.end(), compareXmax);
+
+ QList<int> bRpool;
+ QList<QPair<int, int>> overlappingBB;
+
+ // Start from x = xmin and move towards xmax. Add a rectangle to the pool and check for
+ // intersections with all other rectangles in the pool. If a rectangles xmax is smaller
+ // than the new xmin, it can be removed from the pool.
+ int firstElementEnd = 0;
+ for (const int addIndex : std::as_const(elementStarts)) {
+ const BRect &newR = boundingRects.at(addIndex);
+ // First remove elements from the pool that cannot touch the new one
+ // because xmax is too small
+ while (bRpool.size() && firstElementEnd < elementEnds.size()) {
+ int removeIndex = elementEnds.at(firstElementEnd);
+ if (bRpool.contains(removeIndex) && newR.xmin > boundingRects.at(removeIndex).xmax) {
+ bRpool.removeOne(removeIndex);
+ firstElementEnd++;
+ } else {
+ break;
+ }
+ }
+
+ // Now compare the new element with all elements in the pool.
+ for (int j = 0; j < bRpool.size(); j++) {
+ int i = bRpool.at(j);
+ const BRect &r1 = boundingRects.at(i);
+ // We don't have to check for x because the pooling takes care of it.
+ //if (r1.xmax <= newR.xmin || newR.xmax <= r1.xmin)
+ // continue;
+
+ bool isNeighbor = false;
+ if (i - addIndex == 1) {
+ if (!path.elementAt(addIndex).isSubpathEnd())
+ isNeighbor = true;
+ } else if (addIndex - i == 1) {
+ if (!path.elementAt(i).isSubpathEnd())
+ isNeighbor = true;
+ }
+ // Neighbors need to be completely different (otherwise they just share a point)
+ if (isNeighbor && (r1.ymax <= newR.ymin || newR.ymax <= r1.ymin))
+ continue;
+ // Non-neighbors can also just touch
+ if (!isNeighbor && (r1.ymax < newR.ymin || newR.ymax < r1.ymin))
+ continue;
+ // If the bounding boxes are overlapping it is a candidate for an intersection.
+ overlappingBB.append(QPair<int, int>(i, addIndex));
+ }
+ bRpool.append(addIndex); //Add the new element to the pool.
+ }
+ return overlappingBB;
+}
+
+// Remove paths that are nested inside another path and not required to fill the path correctly
+bool QSGCurveProcessor::removeNestedSubpaths(QQuadPath &path)
+{
+ // Ensure that the path is not intersecting first
+ Q_ASSERT(path.testHint(QQuadPath::PathNonIntersecting));
+
+ if (path.fillRule() != Qt::WindingFill) {
+ // If the fillingRule is odd-even, all internal subpaths matter
+ return false;
+ }
+
+ // Store the starting and end elements of the subpaths to be able
+ // to jump quickly between them.
+ QList<int> subPathStartPoints;
+ QList<int> subPathEndPoints;
+ for (int i = 0; i < path.elementCount(); i++) {
+ if (path.elementAt(i).isSubpathStart())
+ subPathStartPoints.append(i);
+ if (path.elementAt(i).isSubpathEnd()) {
+ subPathEndPoints.append(i);
+ }
+ }
+ const int subPathCount = subPathStartPoints.size();
+
+ // If there is only one subpath, none have to be removed
+ if (subPathStartPoints.size() < 2)
+ return false;
+
+ // We set up a matrix that tells us which path is nested in which other path.
+ QList<bool> isInside;
+ bool isAnyInside = false;
+ isInside.reserve(subPathStartPoints.size() * subPathStartPoints.size());
+ for (int i = 0; i < subPathCount; i++) {
+ for (int j = 0; j < subPathCount; j++) {
+ if (i == j) {
+ isInside.append(false);
+ } else {
+ isInside.append(path.contains(path.elementAt(subPathStartPoints.at(i)).startPoint(),
+ subPathStartPoints.at(j), subPathEndPoints.at(j)));
+ if (isInside.last())
+ isAnyInside = true;
+ }
+ }
+ }
+
+ // If no nested subpaths are present we can return early.
+ if (!isAnyInside)
+ return false;
+
+ // To find out which paths are filled and which not, we first calculate the
+ // rotation direction (clockwise - counterclockwise).
+ QList<bool> clockwise;
+ clockwise.reserve(subPathCount);
+ for (int i = 0; i < subPathCount; i++) {
+ float sumProduct = 0;
+ for (int j = subPathStartPoints.at(i); j <= subPathEndPoints.at(i); j++) {
+ const QVector2D startPoint = path.elementAt(j).startPoint();
+ const QVector2D endPoint = path.elementAt(j).endPoint();
+ sumProduct += (endPoint.x() - startPoint.x()) * (endPoint.y() + startPoint.y());
+ }
+ clockwise.append(sumProduct > 0);
+ }
+
+ // Set up a list that tells us which paths create filling and which path create holes.
+ // Holes in Holes and fillings in fillings can then be removed.
+ QList<bool> isFilled;
+ isFilled.reserve(subPathStartPoints.size() );
+ for (int i = 0; i < subPathCount; i++) {
+ int crossings = clockwise.at(i) ? 1 : -1;
+ for (int j = 0; j < subPathStartPoints.size(); j++) {
+ if (isInside.at(i * subPathCount + j))
+ crossings += clockwise.at(j) ? 1 : -1;
+ }
+ isFilled.append(crossings != 0);
+ }
+
+ // A helper function to find the most inner subpath that is around a subpath.
+ // Returns -1 if the subpath is a toplevel subpath.
+ auto findClosestOuterSubpath = [&](int subPath) {
+ // All paths that contain the current subPath are candidates.
+ QList<int> candidates;
+ for (int i = 0; i < subPathStartPoints.size(); i++) {
+ if (isInside.at(subPath * subPathCount + i))
+ candidates.append(i);
+ }
+ int maxNestingLevel = -1;
+ int maxNestingLevelIndex = -1;
+ for (int i = 0; i < candidates.size(); i++) {
+ int nestingLevel = 0;
+ for (int j = 0; j < candidates.size(); j++) {
+ if (isInside.at(candidates.at(i) * subPathCount + candidates.at(j))) {
+ nestingLevel++;
+ }
+ }
+ if (nestingLevel > maxNestingLevel) {
+ maxNestingLevel = nestingLevel;
+ maxNestingLevelIndex = candidates.at(i);
+ }
+ }
+ return maxNestingLevelIndex;
+ };
+
+ bool pathChanged = false;
+ QQuadPath fixedPath;
+ fixedPath.setPathHints(path.pathHints());
+
+ // Go through all subpaths and find the closest surrounding subpath.
+ // If it is doing the same (create filling or create hole) we can remove it.
+ for (int i = 0; i < subPathCount; i++) {
+ int j = findClosestOuterSubpath(i);
+ if (j >= 0 && isFilled.at(i) == isFilled.at(j)) {
+ pathChanged = true;
+ } else {
+ for (int k = subPathStartPoints.at(i); k <= subPathEndPoints.at(i); k++)
+ fixedPath.addElement(path.elementAt(k));
+ }
+ }
+
+ if (pathChanged)
+ path = fixedPath;
+ return pathChanged;
+}
+
+// Returns true if the path was changed
+bool QSGCurveProcessor::solveIntersections(QQuadPath &path, bool removeNestedPaths)
+{
+ if (path.testHint(QQuadPath::PathNonIntersecting)) {
+ if (removeNestedPaths)
+ return removeNestedSubpaths(path);
+ else
+ return false;
+ }
+
+ if (path.elementCount() < 2) {
+ path.setHint(QQuadPath::PathNonIntersecting);
+ return false;
+ }
+
+ struct IntersectionData { int e1; int e2; float t1; float t2; bool in1 = false, in2 = false, out1 = false, out2 = false; };
+ QList<IntersectionData> intersections;
+
+ // Helper function to mark an intersection as handled when the
+ // path i is processed moving forward/backward
+ auto markIntersectionAsHandled = [=](IntersectionData *data, int i, bool forward) {
+ if (data->e1 == i) {
+ if (forward)
+ data->out1 = true;
+ else
+ data->in1 = true;
+ } else if (data->e2 == i){
+ if (forward)
+ data->out2 = true;
+ else
+ data->in2 = true;
+ } else {
+ Q_UNREACHABLE();
+ }
+ };
+
+ // First make a O(n log n) search for candidates.
+ const QList<QPair<int, int>> candidates = findOverlappingCandidates(path);
+ // Then check the candidates for actual intersections.
+ for (const auto &candidate : candidates) {
+ QList<QPair<float,float>> res;
+ int e1 = candidate.first;
+ int e2 = candidate.second;
+ if (isIntersecting(path, e1, e2, &res)) {
+ for (const auto &r : res)
+ intersections.append({e1, e2, r.first, r.second});
+ }
+ }
+
+ qCDebug(lcSGCurveIntersectionSolver) << "----- Checking for Intersections -----";
+ qCDebug(lcSGCurveIntersectionSolver) << "Found" << intersections.length() << "intersections";
+ if (lcSGCurveIntersectionSolver().isDebugEnabled()) {
+ for (const auto &i : intersections) {
+ auto p1 = path.elementAt(i.e1).pointAtFraction(i.t1);
+ auto p2 = path.elementAt(i.e2).pointAtFraction(i.t2);
+ qCDebug(lcSGCurveIntersectionSolver) << " between" << i.e1 << "and" << i.e2 << "at" << i.t1 << "/" << i.t2 << "->" << p1 << "/" << p2;
+ }
+ }
+
+ if (intersections.isEmpty()) {
+ path.setHint(QQuadPath::PathNonIntersecting);
+ if (removeNestedPaths) {
+ qCDebug(lcSGCurveIntersectionSolver) << "No Intersections found. Looking for enclosed subpaths.";
+ return removeNestedSubpaths(path);
+ } else {
+ qCDebug(lcSGCurveIntersectionSolver) << "Returning the path unchanged.";
+ return false;
+ }
+ }
+
+
+ // Store the starting and end elements of the subpaths to be able
+ // to jump quickly between them. Also keep a list of handled paths,
+ // so we know if we need to come back to a subpath or if it
+ // was already united with another subpath due to an intersection.
+ QList<int> subPathStartPoints;
+ QList<int> subPathEndPoints;
+ QList<bool> subPathHandled;
+ for (int i = 0; i < path.elementCount(); i++) {
+ if (path.elementAt(i).isSubpathStart())
+ subPathStartPoints.append(i);
+ if (path.elementAt(i).isSubpathEnd()) {
+ subPathEndPoints.append(i);
+ subPathHandled.append(false);
+ }
+ }
+
+ // A small helper to find the subPath of an element with index
+ auto subPathIndex = [&](int index) {
+ for (int i = 0; i < subPathStartPoints.size(); i++) {
+ if (index >= subPathStartPoints.at(i) && index <= subPathEndPoints.at(i))
+ return i;
+ }
+ return -1;
+ };
+
+ // Helper to ensure that index i and position t are valid:
+ auto ensureInBounds = [&](int *i, float *t, float deltaT) {
+ if (*t <= 0.f) {
+ if (path.elementAt(*i).isSubpathStart())
+ *i = subPathEndPoints.at(subPathIndex(*i));
+ else
+ *i = *i - 1;
+ *t = 1.f - deltaT;
+ } else if (*t >= 1.f) {
+ if (path.elementAt(*i).isSubpathEnd())
+ *i = subPathStartPoints.at(subPathIndex(*i));
+ else
+ *i = *i + 1;
+ *t = deltaT;
+ }
+ };
+
+ // Helper function to find a siutable starting point between start and end.
+ // A suitable starting point is where right is inside and left is outside
+ // If left is inside and right is outside it works too, just move in the
+ // other direction (forward = false).
+ auto findStart = [=](QQuadPath &path, int start, int end, int *result, bool *forward) {
+ for (int i = start; i < end; i++) {
+ int adjecent;
+ if (subPathStartPoints.contains(i))
+ adjecent = subPathEndPoints.at(subPathStartPoints.indexOf(i));
+ else
+ adjecent = i - 1;
+
+ QQuadPath::Element::FillSide fillSide = path.fillSideOf(i, 1e-4f);
+ const bool leftInside = fillSide == QQuadPath::Element::FillSideLeft;
+ const bool rightInside = fillSide == QQuadPath::Element::FillSideRight;
+ qCDebug(lcSGCurveIntersectionSolver) << "Element" << i << "/" << adjecent << "meeting point is left/right inside:" << leftInside << "/" << rightInside;
+ if (rightInside) {
+ *result = i;
+ *forward = true;
+ return true;
+ } else if (leftInside) {
+ *result = adjecent;
+ *forward = false;
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Also store handledElements (handled is when we touch the start point).
+ // This is used to identify and abort on errors.
+ QVarLengthArray<bool> handledElements(path.elementCount(), false);
+ // Only store handledElements when it is not touched due to an intersection.
+ bool regularVisit = true;
+
+ QQuadPath fixedPath;
+ fixedPath.setFillRule(path.fillRule());
+
+ int i1 = 0;
+ float t1 = 0;
+
+ int i2 = 0;
+ float t2 = 0;
+
+ float t = 0;
+ bool forward = true;
+
+ int startedAtIndex = -1;
+ float startedAtT = -1;
+
+ if (!findStart(path, 0, path.elementCount(), &i1, &forward)) {
+ qCDebug(lcSGCurveIntersectionSolver) << "No suitable start found. This should not happen. Returning the path unchanged.";
+ return false;
+ }
+
+ // Helper function to start a new subpath and update temporary variables.
+ auto startNewSubPath = [&](int i, bool forward) {
+ if (forward) {
+ fixedPath.moveTo(path.elementAt(i).startPoint());
+ t = startedAtT = 0;
+ } else {
+ fixedPath.moveTo(path.elementAt(i).endPoint());
+ t = startedAtT = 1;
+ }
+ startedAtIndex = i;
+ subPathHandled[subPathIndex(i)] = true;
+ };
+ startNewSubPath(i1, forward);
+
+ // If not all interactions where found correctly, we might end up in an infinite loop.
+ // Therefore we count the total number of iterations and bail out at some point.
+ int totalIterations = 0;
+
+ // We need to store the last intersection so we don't jump back and forward immediately.
+ int prevIntersection = -1;
+
+ do {
+ // Sanity check: Make sure that we do not process the same corner point more than once.
+ if (regularVisit && (t == 0 || t == 1)) {
+ int nextIndex = i1;
+ if (t == 1 && path.elementAt(i1).isSubpathEnd()) {
+ nextIndex = subPathStartPoints.at(subPathIndex(i1));
+ } else if (t == 1) {
+ nextIndex = nextIndex + 1;
+ }
+ if (handledElements[nextIndex]) {
+ qCDebug(lcSGCurveIntersectionSolver) << "Revisiting an element when trying to solve intersections. This should not happen. Returning the path unchanged.";
+ return false;
+ }
+ handledElements[nextIndex] = true;
+ }
+ // Sanity check: Keep an eye on total iterations
+ totalIterations++;
+
+ qCDebug(lcSGCurveIntersectionSolver) << "Checking section" << i1 << "from" << t << "going" << (forward ? "forward" : "backward");
+
+ // Find the next intersection that is as close as possible to t but in direction of processing (forward or !forward).
+ int iC = -1; //intersection candidate
+ t1 = forward? 1 : -1; //intersection candidate t-value
+ for (int j = 0; j < intersections.size(); j++) {
+ if (j == prevIntersection)
+ continue;
+ if (i1 == intersections[j].e1 &&
+ intersections[j].t1 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
+ intersections[j].t1 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
+ iC = j;
+ t1 = intersections[j].t1;
+ i2 = intersections[j].e2;
+ t2 = intersections[j].t2;
+ }
+ if (i1 == intersections[j].e2 &&
+ intersections[j].t2 * (forward ? 1 : -1) >= t * (forward ? 1 : -1) &&
+ intersections[j].t2 * (forward ? 1 : -1) < t1 * (forward ? 1 : -1)) {
+ iC = j;
+ t1 = intersections[j].t2;
+ i2 = intersections[j].e1;
+ t2 = intersections[j].t1;
+ }
+ }
+ prevIntersection = iC;
+
+ if (iC < 0) {
+ qCDebug(lcSGCurveIntersectionSolver) << " No intersection found on my way. Adding the rest of the segment " << i1;
+ regularVisit = true;
+ // If no intersection with the current element was found, just add the rest of the element
+ // to the fixed path and go on.
+ // If we reached the end (going forward) or start (going backward) of a subpath, we have
+ // to wrap aroud. Abort condition for the loop comes separately later.
+ if (forward) {
+ if (path.elementAt(i1).isLine()) {
+ fixedPath.lineTo(path.elementAt(i1).endPoint());
+ } else {
+ const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(t, 1);
+ fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
+ }
+ if (path.elementAt(i1).isSubpathEnd()) {
+ int index = subPathEndPoints.indexOf(i1);
+ qCDebug(lcSGCurveIntersectionSolver) << " Going back to the start of subPath" << index;
+ i1 = subPathStartPoints.at(index);
+ } else {
+ i1++;
+ }
+ t = 0;
+ } else {
+ if (path.elementAt(i1).isLine()) {
+ fixedPath.lineTo(path.elementAt(i1).startPoint());
+ } else {
+ const QQuadPath::Element rest = path.elementAt(i1).segmentFromTo(0, t).reversed();
+ fixedPath.quadTo(rest.controlPoint(), rest.endPoint());
+ }
+ if (path.elementAt(i1).isSubpathStart()) {
+ int index = subPathStartPoints.indexOf(i1);
+ qCDebug(lcSGCurveIntersectionSolver) << " Going back to the end of subPath" << index;
+ i1 = subPathEndPoints.at(index);
+ } else {
+ i1--;
+ }
+ t = 1;
+ }
+ } else { // Here comes the part where we actually handle intersections.
+ qCDebug(lcSGCurveIntersectionSolver) << " Found an intersection at" << t1 << "with" << i2 << "at" << t2;
+
+ // Mark the subpath we intersected with as visisted. We do not have to come here explicitly again.
+ subPathHandled[subPathIndex(i2)] = true;
+
+ // Mark the path that lead us to this intersection as handled on the intersection level.
+ // Note the ! in front of forward. This is required because we move towards the intersection.
+ markIntersectionAsHandled(&intersections[iC], i1, !forward);
+
+ // Split the path from the last point to the newly found intersection.
+ // Add the part of the current segment to the fixedPath.
+ const QQuadPath::Element &elem1 = path.elementAt(i1);
+ if (elem1.isLine()) {
+ fixedPath.lineTo(elem1.pointAtFraction(t1));
+ } else {
+ QQuadPath::Element partUntilIntersection;
+ if (forward) {
+ partUntilIntersection = elem1.segmentFromTo(t, t1);
+ } else {
+ partUntilIntersection = elem1.segmentFromTo(t1, t).reversed();
+ }
+ fixedPath.quadTo(partUntilIntersection.controlPoint(), partUntilIntersection.endPoint());
+ }
+
+ // If only one unhandled path is left the decision how to proceed is trivial
+ if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && !intersections[iC].out2) {
+ i1 = intersections[iC].e2;
+ t = intersections[iC].t2;
+ forward = true;
+ } else if (intersections[iC].in1 && intersections[iC].in2 && !intersections[iC].out1 && intersections[iC].out2) {
+ i1 = intersections[iC].e1;
+ t = intersections[iC].t1;
+ forward = true;
+ } else if (intersections[iC].in1 && !intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
+ i1 = intersections[iC].e2;
+ t = intersections[iC].t2;
+ forward = false;
+ } else if (!intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
+ i1 = intersections[iC].e1;
+ t = intersections[iC].t1;
+ forward = false;
+ } else {
+ // If no trivial path is left, calculate the intersection angle to decide which path to move forward.
+ // For winding fill we take the left most path forward, so the inside stays on the right side
+ // For odd even fill we take the right most path forward so we cut of the smallest area.
+ // We come back at the intersection and add the missing pieces as subpaths later on.
+ if (t1 !=0 && t1 != 1 && t2 != 0 && t2 != 1) {
+ QVector2D tangent1 = elem1.tangentAtFraction(t1);
+ if (!forward)
+ tangent1 = -tangent1;
+ const QQuadPath::Element &elem2 = path.elementAt(i2);
+ const QVector2D tangent2 = elem2.tangentAtFraction(t2);
+ const float angle = angleBetween(-tangent1, tangent2);
+ qCDebug(lcSGCurveIntersectionSolver) << " Angle at intersection is" << angle;
+ // A small angle. Everything smaller is interpreted as tangent
+ constexpr float deltaAngle = 1e-3f;
+ if ((angle > deltaAngle && path.fillRule() == Qt::WindingFill) || (angle < -deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
+ forward = true;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going forward from" << t << "on" << i1;
+ } else if ((angle < -deltaAngle && path.fillRule() == Qt::WindingFill) || (angle > deltaAngle && path.fillRule() == Qt::OddEvenFill)) {
+ forward = false;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going backward from" << t << "on" << i1;
+ } else { // this is basically a tangential touch and and no crossing. So stay on the current path, keep direction
+ qCDebug(lcSGCurveIntersectionSolver) << " Found tangent. Staying on element";
+ }
+ } else {
+ // If we are intersecting exactly at a corner, the trick with the angle does not help.
+ // Therefore we have to rely on finding the next path by looking forward and see if the
+ // path there is valid. This is more expensive than the method above and is therefore
+ // just used as a fallback for corner cases.
+ constexpr float deltaT = 1e-4f;
+ int i2after = i2;
+ float t2after = t2 + deltaT;
+ ensureInBounds(&i2after, &t2after, deltaT);
+ QQuadPath::Element::FillSide fillSideForwardNew = path.fillSideOf(i2after, t2after);
+ if (fillSideForwardNew == QQuadPath::Element::FillSideRight) {
+ forward = true;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going forward from" << t << "on" << i1;
+ } else {
+ int i2before = i2;
+ float t2before = t2 - deltaT;
+ ensureInBounds(&i2before, &t2before, deltaT);
+ QQuadPath::Element::FillSide fillSideBackwardNew = path.fillSideOf(i2before, t2before);
+ if (fillSideBackwardNew == QQuadPath::Element::FillSideLeft) {
+ forward = false;
+ i1 = i2;
+ t = t2;
+ qCDebug(lcSGCurveIntersectionSolver) << " Next going backward from" << t << "on" << i1;
+ } else {
+ qCDebug(lcSGCurveIntersectionSolver) << " Staying on element.";
+ }
+ }
+ }
+ }
+
+ // Mark the path that takes us away from this intersection as handled on the intersection level.
+ if (!(i1 == startedAtIndex && t == startedAtT))
+ markIntersectionAsHandled(&intersections[iC], i1, forward);
+
+ // If we took all paths from an intersection it can be deleted.
+ if (intersections[iC].in1 && intersections[iC].in2 && intersections[iC].out1 && intersections[iC].out2) {
+ qCDebug(lcSGCurveIntersectionSolver) << " This intersection was processed completely and will be removed";
+ intersections.removeAt(iC);
+ prevIntersection = -1;
+ }
+ regularVisit = false;
+ }
+
+ if (i1 == startedAtIndex && t == startedAtT) {
+ // We reached the point on the subpath where we started. This subpath is done.
+ // We have to find an unhandled subpath or a new subpath that was generated by cuts/intersections.
+ qCDebug(lcSGCurveIntersectionSolver) << "Reached my starting point and try to find a new subpath.";
+
+ // Search for the next subpath to handle.
+ int nextUnhandled = -1;
+ for (int i = 0; i < subPathHandled.size(); i++) {
+ if (!subPathHandled.at(i)) {
+
+ // Not nesesarrily handled (if findStart return false) but if we find no starting
+ // point, we cannot/don't need to handle it anyway. So just mark it as handled.
+ subPathHandled[i] = true;
+
+ if (findStart(path, subPathStartPoints.at(i), subPathEndPoints.at(i), &i1, &forward)) {
+ nextUnhandled = i;
+ qCDebug(lcSGCurveIntersectionSolver) << "Found a new subpath" << i << "to be processed.";
+ startNewSubPath(i1, forward);
+ regularVisit = true;
+ break;
+ }
+ }
+ }
+
+ // If no valid subpath is left, we have to go back to the unhandled intersections.
+ while (nextUnhandled < 0) {
+ qCDebug(lcSGCurveIntersectionSolver) << "All subpaths handled. Looking for unhandled intersections.";
+ if (intersections.isEmpty()) {
+ qCDebug(lcSGCurveIntersectionSolver) << "All intersections handled. I am done.";
+ fixedPath.setHint(QQuadPath::PathNonIntersecting);
+ path = fixedPath;
+ return true;
+ }
+
+ IntersectionData &unhandledIntersec = intersections[0];
+ prevIntersection = 0;
+ regularVisit = false;
+ qCDebug(lcSGCurveIntersectionSolver) << "Revisiting intersection of" << unhandledIntersec.e1 << "with" << unhandledIntersec.e2;
+ qCDebug(lcSGCurveIntersectionSolver) << "Handled are" << unhandledIntersec.e1 << "in:" << unhandledIntersec.in1 << "out:" << unhandledIntersec.out1
+ << "/" << unhandledIntersec.e2 << "in:" << unhandledIntersec.in2 << "out:" << unhandledIntersec.out2;
+
+ // Searching for the correct direction to go forward.
+ // That requires that the intersection + small delta (here 1e-4)
+ // is a valid starting point (filling only on one side)
+ auto lookForwardOnIntersection = [&](bool *handledPath, int nextE, float nextT, bool nextForward) {
+ if (*handledPath)
+ return false;
+ constexpr float deltaT = 1e-4f;
+ int eForward = nextE;
+ float tForward = nextT + (nextForward ? deltaT : -deltaT);
+ ensureInBounds(&eForward, &tForward, deltaT);
+ QQuadPath::Element::FillSide fillSide = path.fillSideOf(eForward, tForward);
+ if ((nextForward && fillSide == QQuadPath::Element::FillSideRight) ||
+ (!nextForward && fillSide == QQuadPath::Element::FillSideLeft)) {
+ fixedPath.moveTo(path.elementAt(nextE).pointAtFraction(nextT));
+ i1 = startedAtIndex = nextE;
+ t = startedAtT = nextT;
+ forward = nextForward;
+ *handledPath = true;
+ return true;
+ }
+ return false;
+ };
+
+ if (lookForwardOnIntersection(&unhandledIntersec.in1, unhandledIntersec.e1, unhandledIntersec.t1, false))
+ break;
+ if (lookForwardOnIntersection(&unhandledIntersec.in2, unhandledIntersec.e2, unhandledIntersec.t2, false))
+ break;
+ if (lookForwardOnIntersection(&unhandledIntersec.out1, unhandledIntersec.e1, unhandledIntersec.t1, true))
+ break;
+ if (lookForwardOnIntersection(&unhandledIntersec.out2, unhandledIntersec.e2, unhandledIntersec.t2, true))
+ break;
+
+ intersections.removeFirst();
+ qCDebug(lcSGCurveIntersectionSolver) << "Found no way to move forward at this intersection and removed it.";
+ }
+ }
+
+ } while (totalIterations < path.elementCount() * 50);
+ // Check the totalIterations as a sanity check. Should never be triggered.
+
+ qCDebug(lcSGCurveIntersectionSolver) << "Could not solve intersections of path. This should not happen. Returning the path unchanged.";
+
+ return false;
+}
+
+
+void QSGCurveProcessor::processStroke(const QQuadPath &strokePath,
+ float miterLimit,
+ float penWidth,
+ Qt::PenJoinStyle joinStyle,
+ Qt::PenCapStyle capStyle,
+ addStrokeTriangleCallback addTriangle,
+ int subdivisions)
+{
+ auto thePath = subdivide(strokePath, subdivisions).flattened(); // TODO: don't flatten, but handle it in the triangulator
+ auto triangles = customTriangulator2(thePath, penWidth, joinStyle, capStyle, miterLimit);
+
+ auto addCurveTriangle = [&](const QQuadPath::Element &element, const TriangleData &t) {
+ addTriangle(t.points,
+ { element.startPoint(), element.controlPoint(), element.endPoint() },
+ t.normals,
+ element.isLine());
+ };
+
+ auto addBevelTriangle = [&](const TrianglePoints &p)
+ {
+ QVector2D fp1 = p[0];
+ QVector2D fp2 = p[2];
+
+ // That describes a path that passes through those points. We want the stroke
+ // edge, so we need to shift everything down by the stroke offset
+
+ QVector2D nn = calcNormalVector(p[0], p[2]);
+ if (determinant(p) < 0)
+ nn = -nn;
+ float delta = penWidth / 2;
+
+ QVector2D offset = nn.normalized() * delta;
+ fp1 += offset;
+ fp2 += offset;
+
+ TrianglePoints n;
+ // p1 is inside, so n[1] is {0,0}
+ n[0] = (p[0] - p[1]).normalized();
+ n[2] = (p[2] - p[1]).normalized();
+
+ addTriangle(p, { fp1, QVector2D(0.0f, 0.0f), fp2 }, n, true);
+ };
+
+ for (const auto &triangle : triangles) {
+ if (triangle.pathElementIndex < 0) {
+ addBevelTriangle(triangle.points);
+ continue;
+ }
+ const auto &element = thePath.elementAt(triangle.pathElementIndex);
+ addCurveTriangle(element, triangle);
+ }
+}
+
+// 2x variant of qHash(float)
+inline size_t qHash(QVector2D key, size_t seed = 0) noexcept
+{
+ Q_STATIC_ASSERT(sizeof(QVector2D) == sizeof(quint64));
+ // ensure -0 gets mapped to 0
+ key[0] += 0.0f;
+ key[1] += 0.0f;
+ quint64 k;
+ memcpy(&k, &key, sizeof(QVector2D));
+ return QHashPrivate::hash(k, seed);
+}
+
+void QSGCurveProcessor::processFill(const QQuadPath &fillPath,
+ Qt::FillRule fillRule,
+ addTriangleCallback addTriangle)
+{
+ QPainterPath internalHull;
+ internalHull.setFillRule(fillRule);
+
+ QMultiHash<QVector2D, int> pointHash;
+
+ auto roundVec2D = [](const QVector2D &p) -> QVector2D {
+ return { qRound64(p.x() * 32.0f) / 32.0f, qRound64(p.y() * 32.0f) / 32.0f };
+ };
+
+ auto addCurveTriangle = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addTriangle({ sp, cp, ep },
+ {},
+ [&element](QVector2D v) { return elementUvForPoint(element, v); });
+ };
+
+ auto addCurveTriangleWithNormals = [&](const QQuadPath::Element &element,
+ const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n) {
+ addTriangle(v, n, [&element](QVector2D v) { return elementUvForPoint(element, v); });
+ };
+
+ auto outsideNormal = [](const QVector2D &startPoint,
+ const QVector2D &endPoint,
+ const QVector2D &insidePoint) {
+
+ QVector2D baseLine = endPoint - startPoint;
+ QVector2D insideVector = insidePoint - startPoint;
+ QVector2D normal = normalVector(baseLine);
+
+ bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
+
+ return swap ? normal : -normal;
+ };
+
+ auto addTriangleForLine = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addCurveTriangle(element, sp, ep, cp);
+
+ // Add triangles on the outer side to make room for AA
+ const QVector2D normal = outsideNormal(sp, ep, cp);
+ constexpr QVector2D null;
+ addCurveTriangleWithNormals(element, {sp, sp, ep}, {null, normal, null});
+ addCurveTriangleWithNormals(element, {sp, ep, ep}, {normal, normal, null});
+ };
+
+ auto addTriangleForConcave = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addTriangleForLine(element, sp, ep, cp);
+ };
+
+ auto addTriangleForConvex = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addCurveTriangle(element, sp, ep, cp);
+ // Add two triangles on the outer side to get some more AA
+
+ constexpr QVector2D null;
+ // First triangle on the line sp-cp, replacing ep
+ {
+ const QVector2D normal = outsideNormal(sp, cp, ep);
+ addCurveTriangleWithNormals(element, {sp, sp, cp}, {null, normal, null});
+ }
+
+ // Second triangle on the line ep-cp, replacing sp
+ {
+ const QVector2D normal = outsideNormal(ep, cp, sp);
+ addCurveTriangleWithNormals(element, {ep, ep, cp}, {null, normal, null});
+ }
+ };
+
+ auto addFillTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3) {
+ constexpr QVector3D uv(0.0, 1.0, -1.0);
+ addTriangle({ p1, p2, p3 },
+ {},
+ [&uv](QVector2D) { return uv; });
+ };
+
+ fillPath.iterateElements([&](const QQuadPath::Element &element, int index) {
+ QVector2D sp(element.startPoint());
+ QVector2D cp(element.controlPoint());
+ QVector2D ep(element.endPoint());
+ QVector2D rsp = roundVec2D(sp);
+
+ if (element.isSubpathStart())
+ internalHull.moveTo(sp.toPointF());
+ if (element.isLine()) {
+ internalHull.lineTo(ep.toPointF());
+ pointHash.insert(rsp, index);
+ } else {
+ QVector2D rep = roundVec2D(ep);
+ QVector2D rcp = roundVec2D(cp);
+ if (element.isConvex()) {
+ internalHull.lineTo(ep.toPointF());
+ addTriangleForConvex(element, rsp, rep, rcp);
+ pointHash.insert(rsp, index);
+ } else {
+ internalHull.lineTo(cp.toPointF());
+ internalHull.lineTo(ep.toPointF());
+ addTriangleForConcave(element, rsp, rep, rcp);
+ pointHash.insert(rcp, index);
+ }
+ }
+ });
+
+ // Points in p are already rounded do 1/32
+ // Returns false if the triangle needs to be split. Adds the triangle to the graphics buffers and returns true otherwise.
+ // (Does not handle ambiguous vertices that are on multiple unrelated lines/curves)
+ auto onSameSideOfLine = [](const QVector2D &p1,
+ const QVector2D &p2,
+ const QVector2D &linePoint,
+ const QVector2D &lineNormal) {
+ float side1 = testSideOfLineByNormal(linePoint, lineNormal, p1);
+ float side2 = testSideOfLineByNormal(linePoint, lineNormal, p2);
+ return side1 * side2 >= 0;
+ };
+
+ auto pointInSafeSpace = [&](const QVector2D &p, const QQuadPath::Element &element) -> bool {
+ const QVector2D a = element.startPoint();
+ const QVector2D b = element.endPoint();
+ const QVector2D c = element.controlPoint();
+ // There are "safe" areas of the curve also across the baseline: the curve can never cross:
+ // line1: the line through A and B'
+ // line2: the line through B and A'
+ // Where A' = A "mirrored" through C and B' = B "mirrored" through C
+ const QVector2D n1 = calcNormalVector(a, c + (c - b));
+ const QVector2D n2 = calcNormalVector(b, c + (c - a));
+ bool safeSideOf1 = onSameSideOfLine(p, c, a, n1);
+ bool safeSideOf2 = onSameSideOfLine(p, c, b, n2);
+ return safeSideOf1 && safeSideOf2;
+ };
+
+ // Returns false if the triangle belongs to multiple elements and need to be split.
+ // Otherwise adds the triangle, optionally splitting it to avoid "unsafe space"
+ auto handleTriangle = [&](const QVector2D (&p)[3]) -> bool {
+ bool isLine = false;
+ bool isConcave = false;
+ bool isConvex = false;
+ int elementIndex = -1;
+
+ bool foundElement = false;
+ int si = -1;
+ int ei = -1;
+
+ for (int i = 0; i < 3; ++i) {
+ auto pointFoundRange = std::as_const(pointHash).equal_range(roundVec2D(p[i]));
+
+ if (pointFoundRange.first == pointHash.constEnd())
+ continue;
+
+ // This point is on some element, now find the element
+ int testIndex = *pointFoundRange.first;
+ bool ambiguous = std::next(pointFoundRange.first) != pointFoundRange.second;
+ if (ambiguous) {
+ // The triangle should be on the inside of exactly one of the elements
+ // We're doing the test for each of the points, which maybe duplicates some effort,
+ // but optimize for simplicity for now.
+ for (auto it = pointFoundRange.first; it != pointFoundRange.second; ++it) {
+ auto &el = fillPath.elementAt(*it);
+ bool fillOnLeft = !el.isFillOnRight();
+ auto sp = roundVec2D(el.startPoint());
+ auto ep = roundVec2D(el.endPoint());
+ // Check if the triangle is on the inside of el; i.e. each point is either sp, ep, or on the inside.
+ auto pointInside = [&](const QVector2D &p) {
+ return p == sp || p == ep
+ || QQuadPath::isPointOnLeft(p, el.startPoint(), el.endPoint()) == fillOnLeft;
+ };
+ if (pointInside(p[0]) && pointInside(p[1]) && pointInside(p[2])) {
+ testIndex = *it;
+ break;
+ }
+ }
+ }
+
+ const auto &element = fillPath.elementAt(testIndex);
+ // Now we check if p[i] -> p[j] is on the element for some j
+ // For a line, the relevant line is sp-ep
+ // For concave it's cp-sp/ep
+ // For convex it's sp-ep again
+ bool onElement = false;
+ for (int j = 0; j < 3; ++j) {
+ if (i == j)
+ continue;
+ if (element.isConvex() || element.isLine())
+ onElement = roundVec2D(element.endPoint()) == p[j];
+ else // concave
+ onElement = roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j];
+ if (onElement) {
+ if (foundElement)
+ return false; // Triangle already on some other element: must split
+ si = i;
+ ei = j;
+ foundElement = true;
+ elementIndex = testIndex;
+ isConvex = element.isConvex();
+ isLine = element.isLine();
+ isConcave = !isLine && !isConvex;
+ break;
+ }
+ }
+ }
+
+ if (isLine) {
+ int ci = (6 - si - ei) % 3; // 1+2+3 is 6, so missing number is 6-n1-n2
+ addTriangleForLine(fillPath.elementAt(elementIndex), p[si], p[ei], p[ci]);
+ } else if (isConcave) {
+ addCurveTriangle(fillPath.elementAt(elementIndex), p[0], p[1], p[2]);
+ } else if (isConvex) {
+ int oi = (6 - si - ei) % 3;
+ const auto &otherPoint = p[oi];
+ const auto &element = fillPath.elementAt(elementIndex);
+ // We have to test whether the triangle can cross the line
+ // TODO: use the toplevel element's safe space
+ bool safeSpace = pointInSafeSpace(otherPoint, element);
+ if (safeSpace) {
+ addCurveTriangle(element, p[0], p[1], p[2]);
+ } else {
+ // Find a point inside the triangle that's also in the safe space
+ QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
+ // We should calculate the point directly, but just do a lazy implementation for now:
+ for (int i = 0; i < 7; ++i) {
+ safeSpace = pointInSafeSpace(newPoint, element);
+ if (safeSpace)
+ break;
+ newPoint = (p[si] + p[ei] + newPoint) / 3;
+ }
+ if (safeSpace) {
+ // Split triangle. We know the original triangle is only on one path element, so the other triangles are both fill.
+ // Curve triangle is (sp, ep, np)
+ addCurveTriangle(element, p[si], p[ei], newPoint);
+ // The other two are (sp, op, np) and (ep, op, np)
+ addFillTriangle(p[si], p[oi], newPoint);
+ addFillTriangle(p[ei], p[oi], newPoint);
+ } else {
+ // fallback to fill if we can't find a point in safe space
+ addFillTriangle(p[0], p[1], p[2]);
+ }
+ }
+
+ } else {
+ addFillTriangle(p[0], p[1], p[2]);
+ }
+ return true;
+ };
+
+ QTriangleSet triangles = qTriangulate(internalHull);
+ // Workaround issue in qTriangulate() for single-triangle path
+ if (triangles.indices.size() == 3)
+ triangles.indices.setDataUint({ 0, 1, 2 });
+
+ const quint32 *idxTable = static_cast<const quint32 *>(triangles.indices.data());
+ for (int triangle = 0; triangle < triangles.indices.size() / 3; ++triangle) {
+ const quint32 *idx = &idxTable[triangle * 3];
+
+ QVector2D p[3];
+ for (int i = 0; i < 3; ++i) {
+ p[i] = roundVec2D(QVector2D(float(triangles.vertices.at(idx[i] * 2)),
+ float(triangles.vertices.at(idx[i] * 2 + 1))));
+ }
+ if (qFuzzyIsNull(determinant(p[0], p[1], p[2])))
+ continue; // Skip degenerate triangles
+ bool needsSplit = !handleTriangle(p);
+ if (needsSplit) {
+ QVector2D c = (p[0] + p[1] + p[2]) / 3;
+ for (int i = 0; i < 3; ++i) {
+ qSwap(c, p[i]);
+ handleTriangle(p);
+ qSwap(c, p[i]);
+ }
+ }
+ }
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveprocessor_p.h b/src/quick/scenegraph/qsgcurveprocessor_p.h
new file mode 100644
index 0000000000..cdd93bc58e
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveprocessor_p.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVEPROCESSOR_P_H
+#define QSGCURVEPROCESSOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/qtquickexports.h>
+#include "util/qquadpath_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QSGCurveProcessor
+{
+public:
+ typedef std::function<QVector3D(QVector2D)> uvForPointCallback;
+ typedef std::function<void(const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ uvForPointCallback)> addTriangleCallback;
+ typedef std::function<void(const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ bool)> addStrokeTriangleCallback;
+
+ static void processFill(const QQuadPath &path,
+ Qt::FillRule fillRule,
+ addTriangleCallback addTriangle);
+ static void processStroke(const QQuadPath &strokePath,
+ float miterLimit,
+ float penWidth,
+ Qt::PenJoinStyle joinStyle,
+ Qt::PenCapStyle capStyle,
+ addStrokeTriangleCallback addTriangle,
+ int subdivisions = 3);
+ static bool solveOverlaps(QQuadPath &path);
+ static QList<QPair<int, int>> findOverlappingCandidates(const QQuadPath &path);
+ static bool removeNestedSubpaths(QQuadPath &path);
+ static bool solveIntersections(QQuadPath &path, bool removeNestedPaths = true);
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEPROCESSOR_P_H
diff --git a/src/quick/scenegraph/qsgcurvestrokenode.cpp b/src/quick/scenegraph/qsgcurvestrokenode.cpp
new file mode 100644
index 0000000000..2bfaff1b4e
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode.cpp
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvestrokenode_p.h"
+#include "qsgcurvestrokenode_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveStrokeNode::QSGCurveStrokeNode()
+{
+ setFlag(OwnsGeometry, true);
+ setGeometry(new QSGGeometry(attributes(), 0, 0));
+ updateMaterial();
+}
+
+void QSGCurveStrokeNode::QSGCurveStrokeNode::updateMaterial()
+{
+ m_material.reset(new QSGCurveStrokeMaterial(this));
+ setMaterial(m_material.data());
+}
+
+// Take the start, control and end point of a curve and return the points A, B, C
+// representing the curve as Q(s) = A*s*s + B*s + C
+std::array<QVector2D, 3> QSGCurveStrokeNode::curveABC(const std::array<QVector2D, 3> &p)
+{
+ QVector2D a = p[0] - 2*p[1] + p[2];
+ QVector2D b = 2*p[1] - 2*p[0];
+ QVector2D c = p[0];
+
+ return {a, b, c};
+}
+
+// Curve from p[0] to p[2] with control point p[1]
+void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n)
+{
+ auto abc = curveABC(p);
+
+ int currentVertex = m_uncookedVertexes.count();
+
+ for (int i = 0; i < 3; ++i) {
+ m_uncookedVertexes.append( { v[i].x(), v[i].y(),
+ abc[0].x(), abc[0].y(), abc[1].x(), abc[1].y(), abc[2].x(), abc[2].y(),
+ n[i].x(), n[i].y() } );
+ }
+ m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
+}
+
+// Straight line from p0 to p1
+void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 2> &p,
+ const std::array<QVector2D, 3> &n)
+{
+ // We could reduce this to a linear equation by setting A to (0,0).
+ // However, then we cannot use the cubic solution and need an additional
+ // code path in the shader. The following formulation looks more complicated
+ // but allows to always use the cubic solution.
+ auto A = p[1] - p[0];
+ auto B = QVector2D(0., 0.);
+ auto C = p[0];
+
+ int currentVertex = m_uncookedVertexes.count();
+
+// for (auto v : QList<QPair<QVector2D, QVector2D>>({{v0, n0}, {v1, n1}, {v2, n2}})) {
+ for (int i = 0; i < 3; ++i) {
+ m_uncookedVertexes.append( { v[i].x(), v[i].y(),
+ A.x(), A.y(), B.x(), B.y(), C.x(), C.y(),
+ n[i].x(), n[i].y() } );
+ }
+ m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
+}
+
+void QSGCurveStrokeNode::cookGeometry()
+{
+ QSGGeometry *g = geometry();
+ if (g->indexType() != QSGGeometry::UnsignedIntType) {
+ g = new QSGGeometry(attributes(),
+ m_uncookedVertexes.size(),
+ m_uncookedIndexes.size(),
+ QSGGeometry::UnsignedIntType);
+ setGeometry(g);
+ } else {
+ g->allocate(m_uncookedVertexes.size(), m_uncookedIndexes.size());
+ }
+
+ g->setDrawingMode(QSGGeometry::DrawTriangles);
+ memcpy(g->vertexData(),
+ m_uncookedVertexes.constData(),
+ g->vertexCount() * g->sizeOfVertex());
+ memcpy(g->indexData(),
+ m_uncookedIndexes.constData(),
+ g->indexCount() * g->sizeOfIndex());
+
+ m_uncookedIndexes.clear();
+ m_uncookedVertexes.clear();
+}
+
+const QSGGeometry::AttributeSet &QSGCurveStrokeNode::attributes()
+{
+ static QSGGeometry::Attribute data[] = {
+ QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute), //vertexCoord
+ QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //A
+ QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //B
+ QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //C
+ QSGGeometry::Attribute::createWithAttributeType(4, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //normalVector
+ };
+ static QSGGeometry::AttributeSet attrs = { 5, sizeof(StrokeVertex), data };
+ return attrs;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvestrokenode_p.cpp b/src/quick/scenegraph/qsgcurvestrokenode_p.cpp
new file mode 100644
index 0000000000..b86493b169
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p.cpp
@@ -0,0 +1,90 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgcurvestrokenode_p_p.h"
+#include "qsgcurvestrokenode_p.h"
+
+QT_BEGIN_NAMESPACE
+
+bool QSGCurveStrokeMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
+{
+ bool changed = false;
+ QByteArray *buf = state.uniformData();
+ Q_ASSERT(buf->size() >= 64);
+ const int matrixCount = qMin(state.projectionMatrixCount(), newEffect->viewCount());
+
+ auto *newMaterial = static_cast<QSGCurveStrokeMaterial *>(newEffect);
+ auto *oldMaterial = static_cast<QSGCurveStrokeMaterial *>(oldEffect);
+
+ auto *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
+ auto *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
+
+ if (state.isMatrixDirty()) {
+ float localScale = newNode != nullptr ? newNode->localScale() : 1.0f;
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ m.scale(localScale);
+ memcpy(buf->data() + viewIndex * 64, m.constData(), 64);
+ }
+ float matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio() * localScale;
+ memcpy(buf->data() + matrixCount * 64, &matrixScale, 4);
+ changed = true;
+ }
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(buf->data() + matrixCount * 64 + 4, &opacity, 4);
+ changed = true;
+ }
+
+ int offset = matrixCount * 64 + 16;
+ if (newNode == nullptr)
+ return changed;
+
+ QVector4D newStrokeColor(newNode->color().redF(),
+ newNode->color().greenF(),
+ newNode->color().blueF(),
+ newNode->color().alphaF());
+ QVector4D oldStrokeColor = oldNode != nullptr
+ ? QVector4D(oldNode->color().redF(),
+ oldNode->color().greenF(),
+ oldNode->color().blueF(),
+ oldNode->color().alphaF())
+ : QVector4D{};
+
+ if (oldNode == nullptr || oldStrokeColor != newStrokeColor) {
+ memcpy(buf->data() + offset, &newStrokeColor, 16);
+ changed = true;
+ }
+ offset += 16;
+
+ if (oldNode == nullptr || newNode->strokeWidth() != oldNode->strokeWidth()) {
+ float w = newNode->strokeWidth();
+ memcpy(buf->data() + offset, &w, 4);
+ changed = true;
+ }
+ offset += 4;
+ if (oldNode == nullptr || newNode->debug() != oldNode->debug()) {
+ float w = newNode->debug();
+ memcpy(buf->data() + offset, &w, 4);
+ changed = true;
+ }
+// offset += 4;
+
+ return changed;
+}
+
+int QSGCurveStrokeMaterial::compare(const QSGMaterial *other) const
+{
+ int typeDif = type() - other->type();
+ if (!typeDif) {
+ auto *othernode = static_cast<const QSGCurveStrokeMaterial*>(other)->node();
+ if (node()->color() != othernode->color())
+ return node()->color().rgb() < othernode->color().rgb() ? -1 : 1;
+ if (node()->strokeWidth() != othernode->strokeWidth())
+ return node()->strokeWidth() < othernode->strokeWidth() ? -1 : 1;
+ }
+ return typeDif;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvestrokenode_p.h b/src/quick/scenegraph/qsgcurvestrokenode_p.h
new file mode 100644
index 0000000000..079414bc16
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p.h
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVESTROKENODE_P_H
+#define QSGCURVESTROKENODE_P_H
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/qsgnode.h>
+
+#include "qsgcurveabstractnode_p.h"
+#include "qsgcurvestrokenode_p_p.h"
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QSGCurveStrokeNode : public QSGCurveAbstractNode
+{
+public:
+ QSGCurveStrokeNode();
+
+ void setColor(QColor col) override
+ {
+ m_color = col;
+ }
+
+ QColor color() const
+ {
+ return m_color;
+ }
+
+ void setStrokeWidth(float width)
+ {
+ m_strokeWidth = width;
+ }
+
+ float strokeWidth() const
+ {
+ return m_strokeWidth;
+ }
+
+ void appendTriangle(const std::array<QVector2D, 3> &v, // triangle vertices
+ const std::array<QVector2D, 3> &p, // curve points
+ const std::array<QVector2D, 3> &n); // vertex normals
+ void appendTriangle(const std::array<QVector2D, 3> &v, // triangle vertices
+ const std::array<QVector2D, 2> &p, // line points
+ const std::array<QVector2D, 3> &n); // vertex normals
+
+ void cookGeometry() override;
+
+ static const QSGGeometry::AttributeSet &attributes();
+
+ QVector<quint32> uncookedIndexes() const
+ {
+ return m_uncookedIndexes;
+ }
+
+ float debug() const
+ {
+ return m_debug;
+ }
+
+ void setDebug(float newDebug)
+ {
+ m_debug = newDebug;
+ }
+
+ void setLocalScale(float scale)
+ {
+ m_localScale = scale;
+ }
+
+ float localScale() const
+ {
+ return m_localScale;
+ }
+
+private:
+
+ struct StrokeVertex
+ {
+ float x, y;
+ float ax, ay;
+ float bx, by;
+ float cx, cy;
+ float nx, ny; //normal vector: direction to move vertext to account for AA
+ };
+
+ void updateMaterial();
+
+ static std::array<QVector2D, 3> curveABC(const std::array<QVector2D, 3> &p);
+
+ QColor m_color;
+ float m_strokeWidth = 0.0f;
+ float m_debug = 0.0f;
+ float m_localScale = 1.0f;
+
+protected:
+ QScopedPointer<QSGCurveStrokeMaterial> m_material;
+
+ QVector<StrokeVertex> m_uncookedVertexes;
+ QVector<quint32> m_uncookedIndexes;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVESTROKENODE_P_H
diff --git a/src/quick/scenegraph/qsgcurvestrokenode_p_p.h b/src/quick/scenegraph/qsgcurvestrokenode_p_p.h
new file mode 100644
index 0000000000..847098f8cc
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p_p.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGCURVESTROKENODE_P_P_H
+#define QSGCURVESTROKENODE_P_P_H
+
+#include <QtQuick/qtquickexports.h>
+#include <QtQuick/qsgmaterial.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveStrokeNode;
+class QSGCurveStrokeMaterial;
+
+class Q_QUICK_EXPORT QSGCurveStrokeMaterialShader : public QSGMaterialShader
+{
+public:
+ QSGCurveStrokeMaterialShader(int viewCount)
+ {
+ setShaderFileName(VertexStage,
+ QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapestroke.vert.qsb"),
+ viewCount);
+ setShaderFileName(FragmentStage,
+ QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapestroke.frag.qsb"),
+ viewCount);
+ }
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
+};
+
+
+class Q_QUICK_EXPORT QSGCurveStrokeMaterial : public QSGMaterial
+{
+public:
+ QSGCurveStrokeMaterial(QSGCurveStrokeNode *node)
+ : m_node(node)
+ {
+ setFlag(Blending, true);
+ }
+
+ int compare(const QSGMaterial *other) const override;
+
+ QSGCurveStrokeNode *node() const
+ {
+ return m_node;
+ }
+
+protected:
+ QSGMaterialType *type() const override
+ {
+ static QSGMaterialType t;
+ return &t;
+ }
+ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override
+ {
+ return new QSGCurveStrokeMaterialShader(viewCount());
+ }
+
+ QSGCurveStrokeNode *m_node;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVESTROKENODE_P_P_H
diff --git a/src/quick/scenegraph/qsgdefaultcontext.cpp b/src/quick/scenegraph/qsgdefaultcontext.cpp
index f3f85445e5..7d1daa1716 100644
--- a/src/quick/scenegraph/qsgdefaultcontext.cpp
+++ b/src/quick/scenegraph/qsgdefaultcontext.cpp
@@ -7,6 +7,7 @@
#include <QtQuick/private/qsgdefaultinternalimagenode_p.h>
#include <QtQuick/private/qsgdefaultpainternode_p.h>
#include <QtQuick/private/qsgdefaultglyphnode_p.h>
+#include <QtQuick/private/qsgcurveglyphnode_p.h>
#include <QtQuick/private/qsgdistancefieldglyphnode_p.h>
#include <QtQuick/private/qsgdistancefieldglyphnode_p_p.h>
#include <QtQuick/private/qsgrhisupport_p.h>
@@ -19,6 +20,8 @@
#include <QtQuick/private/qsgdefaultspritenode_p.h>
#endif
#include <QtQuick/private/qsgrhishadereffectnode_p.h>
+#include <QtQuick/private/qsginternaltextnode_p.h>
+#include <QtQuick/private/qsgrhiinternaltextnode_p.h>
#include <QOpenGLContext>
@@ -29,10 +32,7 @@
#include <algorithm>
-#include <QtGui/private/qrhi_p.h>
-#if QT_CONFIG(opengl)
-# include <QtGui/private/qrhigles2_p.h>
-#endif
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -147,11 +147,18 @@ QSGPainterNode *QSGDefaultContext::createPainterNode(QQuickPaintedItem *item)
return new QSGDefaultPainterNode(item);
}
+QSGInternalTextNode *QSGDefaultContext::createInternalTextNode(QSGRenderContext *renderContext)
+{
+ return new QSGRhiInternalTextNode(renderContext);
+}
+
QSGGlyphNode *QSGDefaultContext::createGlyphNode(QSGRenderContext *rc,
- bool preferNativeGlyphNode,
+ QSGTextNode::RenderType renderType,
int renderTypeQuality)
{
- if (m_distanceFieldDisabled || preferNativeGlyphNode) {
+ if (renderType == QSGTextNode::CurveRendering) {
+ return new QSGCurveGlyphNode(rc);
+ } else if (m_distanceFieldDisabled || renderType == QSGTextNode::NativeRendering) {
return new QSGDefaultGlyphNode(rc);
} else {
QSGDistanceFieldGlyphNode *node = new QSGDistanceFieldGlyphNode(rc);
diff --git a/src/quick/scenegraph/qsgdefaultcontext_p.h b/src/quick/scenegraph/qsgdefaultcontext_p.h
index 8dd507bd9a..5f35e7b451 100644
--- a/src/quick/scenegraph/qsgdefaultcontext_p.h
+++ b/src/quick/scenegraph/qsgdefaultcontext_p.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultContext : public QSGContext, public QSGRendererInterface
+class Q_QUICK_EXPORT QSGDefaultContext : public QSGContext, public QSGRendererInterface
{
public:
QSGDefaultContext(QObject *parent = nullptr);
@@ -33,7 +33,8 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode() override;
QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) override;
QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override;
- QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) override;
+ QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) override;
+ QSGInternalTextNode *createInternalTextNode(QSGRenderContext *renderContext) override;
QSGLayer *createLayer(QSGRenderContext *renderContext) override;
QSurfaceFormat defaultSurfaceFormat() const override;
QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext) override;
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode.cpp b/src/quick/scenegraph/qsgdefaultglyphnode.cpp
index f9133455c8..445f338bbd 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode.cpp
+++ b/src/quick/scenegraph/qsgdefaultglyphnode.cpp
@@ -47,11 +47,14 @@ void QSGDefaultGlyphNode::update()
QRawFont font = m_glyphs.rawFont();
QMargins margins(0, 0, 0, 0);
- if (m_style == QQuickText::Normal) {
+ const auto *fontEngine = QRawFontPrivate::get(font)->fontEngine;
+ const bool isColorFont = fontEngine->glyphFormat == QFontEngine::Format_ARGB;
+
+ if (m_style == QQuickText::Normal || isColorFont) {
QFontEngine::GlyphFormat glyphFormat;
// Don't try to override glyph format of color fonts
- if (QRawFontPrivate::get(font)->fontEngine->glyphFormat == QFontEngine::Format_ARGB) {
+ if (isColorFont) {
glyphFormat = QFontEngine::Format_None;
} else {
switch (m_preferredAntialiasingMode) {
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
index 43a7e919bc..3d6cabe6fc 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
+++ b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp
@@ -17,45 +17,16 @@
QT_BEGIN_NAMESPACE
-#ifndef GL_FRAMEBUFFER_SRGB
-#define GL_FRAMEBUFFER_SRGB 0x8DB9
-#endif
-
-#ifndef GL_FRAMEBUFFER_SRGB_CAPABLE
-#define GL_FRAMEBUFFER_SRGB_CAPABLE 0x8DBA
-#endif
-
static inline QVector4D qsg_premultiply(const QVector4D &c, float globalOpacity)
{
float o = c.w() * globalOpacity;
return QVector4D(c.x() * o, c.y() * o, c.z() * o, o);
}
-#if 0
-static inline qreal qt_sRGB_to_linear_RGB(qreal f)
-{
- return f > 0.04045 ? qPow((f + 0.055) / 1.055, 2.4) : f / 12.92;
-}
-
-static inline QVector4D qt_sRGB_to_linear_RGB(const QVector4D &color)
-{
- return QVector4D(qt_sRGB_to_linear_RGB(color.x()),
- qt_sRGB_to_linear_RGB(color.y()),
- qt_sRGB_to_linear_RGB(color.z()),
- color.w());
-}
-
-static inline qreal fontSmoothingGamma()
-{
- static qreal fontSmoothingGamma = QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FontSmoothingGamma).toReal();
- return fontSmoothingGamma;
-}
-#endif
-
class QSGTextMaskRhiShader : public QSGMaterialShader
{
public:
- QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat);
+ QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount);
bool updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -64,28 +35,25 @@ public:
protected:
QFontEngine::GlyphFormat m_glyphFormat;
+ quint32 m_currentUbufOffset;
};
-QSGTextMaskRhiShader::QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat)
+QSGTextMaskRhiShader::QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount)
: m_glyphFormat(glyphFormat)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.vert.qsb"));
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.frag.qsb"), viewCount);
}
-enum UbufOffset {
- ModelViewMatrixOffset = 0,
- ProjectionMatrixOffset = ModelViewMatrixOffset + 64,
- ColorOffset = ProjectionMatrixOffset + 64,
- TextureScaleOffset = ColorOffset + 16,
- DprOffset = TextureScaleOffset + 8,
-
- // + 1 float padding (vec4 must be aligned to 16)
- StyleColorOffset = DprOffset + 4 + 4,
- ShiftOffset = StyleColorOffset + 16
-};
+// uniform block layout:
+// mat4 modelViewMatrix
+// mat4 projectionMatrix or mat4 projectionMatrix[QSHADER_VIEW_COUNT]
+// vec2 textureScale
+// float dpr
+// vec4 color
+// [styled/outline only]
+// vec4 styleColor
+// vec2 shift
bool QSGTextMaskRhiShader::updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
@@ -102,34 +70,46 @@ bool QSGTextMaskRhiShader::updateUniformData(RenderState &state,
bool changed = false;
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= DprOffset + 4);
+ const int projectionMatrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
+ quint32 offset = 0; // ModelViewMatrix
if (state.isMatrixDirty()) {
const QMatrix4x4 mv = state.modelViewMatrix();
- memcpy(buf->data() + ModelViewMatrixOffset, mv.constData(), 64);
- const QMatrix4x4 p = state.projectionMatrix();
- memcpy(buf->data() + ProjectionMatrixOffset, p.constData(), 64);
-
+ memcpy(buf->data() + offset, mv.constData(), 64);
changed = true;
}
+ offset += 64; // now at ProjectionMatrix or ProjectionMatrix[0]
+
+ for (int viewIndex = 0; viewIndex < projectionMatrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 p = state.projectionMatrix(viewIndex);
+ memcpy(buf->data() + offset, p.constData(), 64);
+ changed = true;
+ }
+ offset += 64;
+ }
+ // offset is now at TextureScale
QRhiTexture *oldRtex = oldMat ? oldMat->texture()->rhiTexture() : nullptr;
QRhiTexture *newRtex = mat->texture()->rhiTexture();
if (updated || !oldMat || oldRtex != newRtex) {
const QVector2D textureScale = QVector2D(1.0f / mat->rhiGlyphCache()->width(),
1.0f / mat->rhiGlyphCache()->height());
- memcpy(buf->data() + TextureScaleOffset, &textureScale, 8);
+ memcpy(buf->data() + offset, &textureScale, 8);
changed = true;
}
+ offset += 8; // now at Dpr
if (!oldMat) {
float dpr = state.devicePixelRatio();
- memcpy(buf->data() + DprOffset, &dpr, 4);
+ memcpy(buf->data() + offset, &dpr, 4);
}
+ offset += 4 + 4; // now at Color (with padding to ensure vec4 alignment)
// move texture uploads/copies onto the renderer's soon-to-be-committed list
mat->rhiGlyphCache()->commitResourceUpdates(state.resourceUpdateBatch());
+ m_currentUbufOffset = offset;
return changed;
}
@@ -149,15 +129,13 @@ void QSGTextMaskRhiShader::updateSampledImage(RenderState &state, int binding, Q
class QSG8BitTextMaskRhiShader : public QSGTextMaskRhiShader
{
public:
- QSG8BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
- : QSGTextMaskRhiShader(glyphFormat)
+ QSG8BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount, bool alphaTexture)
+ : QSGTextMaskRhiShader(glyphFormat, viewCount)
{
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -172,13 +150,13 @@ bool QSG8BitTextMaskRhiShader::updateUniformData(RenderState &state,
QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ColorOffset + 16);
if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
const QVector4D color = qsg_premultiply(mat->color(), state.opacity());
- memcpy(buf->data() + ColorOffset, &color, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &color, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleColor
return changed;
}
@@ -186,12 +164,11 @@ bool QSG8BitTextMaskRhiShader::updateUniformData(RenderState &state,
class QSG24BitTextMaskRhiShader : public QSGTextMaskRhiShader
{
public:
- QSG24BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat)
- : QSGTextMaskRhiShader(glyphFormat)
+ QSG24BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount)
+ : QSGTextMaskRhiShader(glyphFormat, viewCount)
{
setFlag(UpdatesGraphicsPipelineState, true);
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/24bittextmask.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/24bittextmask.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -199,13 +176,6 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-// ### gamma correction (sRGB) Unsurprisingly, the GL approach is not portable
-// to anything else - it just does not work that way, there is no opt-in/out
-// switch and magic winsys-provided maybe-sRGB buffers. When requesting an sRGB
-// QRhiSwapChain (which we do not do), it is full sRGB, with the sRGB
-// framebuffer update and blending always on... Could we do gamma correction in
-// the shader for text? (but that's bad for blending?)
-
bool QSG24BitTextMaskRhiShader::updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
@@ -215,14 +185,14 @@ bool QSG24BitTextMaskRhiShader::updateUniformData(RenderState &state,
QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ColorOffset + 16);
if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
// shader takes vec4 but uses alpha only; coloring happens via the blend constant
const QVector4D color = qsg_premultiply(mat->color(), state.opacity());
- memcpy(buf->data() + ColorOffset, &color, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &color, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleColor
return changed;
}
@@ -240,11 +210,8 @@ bool QSG24BitTextMaskRhiShader::updateGraphicsPipelineState(RenderState &state,
QVector4D color = mat->color();
- // if (useSRGB())
- // color = qt_sRGB_to_linear_RGB(color);
-
// this is dynamic state but it's - magic! - taken care of by the renderer
- ps->blendConstant = QColor::fromRgbF(color.x(), color.y(), color.z(), color.w());
+ ps->blendConstant = QColor::fromRgbF(color.x(), color.y(), color.z());
return true;
}
@@ -252,11 +219,10 @@ bool QSG24BitTextMaskRhiShader::updateGraphicsPipelineState(RenderState &state,
class QSG32BitColorTextRhiShader : public QSGTextMaskRhiShader
{
public:
- QSG32BitColorTextRhiShader(QFontEngine::GlyphFormat glyphFormat)
- : QSGTextMaskRhiShader(glyphFormat)
+ QSG32BitColorTextRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount)
+ : QSGTextMaskRhiShader(glyphFormat, viewCount)
{
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/32bitcolortext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/32bitcolortext.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -271,14 +237,14 @@ bool QSG32BitColorTextRhiShader::updateUniformData(RenderState &state,
QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ColorOffset + 16);
if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
// shader takes vec4 but uses alpha only
const QVector4D color(0, 0, 0, mat->color().w() * state.opacity());
- memcpy(buf->data() + ColorOffset, &color, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &color, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleColor
return changed;
}
@@ -286,17 +252,14 @@ bool QSG32BitColorTextRhiShader::updateUniformData(RenderState &state,
class QSGStyledTextRhiShader : public QSG8BitTextMaskRhiShader
{
public:
- QSGStyledTextRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
- : QSG8BitTextMaskRhiShader(glyphFormat, alphaTexture)
+ QSGStyledTextRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount, bool alphaTexture)
+ : QSG8BitTextMaskRhiShader(glyphFormat, viewCount, alphaTexture)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.frag.qsb"), viewCount);
}
bool updateUniformData(RenderState &state,
@@ -312,17 +275,17 @@ bool QSGStyledTextRhiShader::updateUniformData(RenderState &state,
QSGStyledTextMaterial *oldMat = static_cast<QSGStyledTextMaterial *>(oldMaterial);
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= ShiftOffset + 8);
if (oldMat == nullptr || mat->styleColor() != oldMat->styleColor() || state.isOpacityDirty()) {
const QVector4D styleColor = qsg_premultiply(mat->styleColor(), state.opacity());
- memcpy(buf->data() + StyleColorOffset, &styleColor, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &styleColor, 16);
changed = true;
}
+ m_currentUbufOffset += 16; // now at StyleShift
if (oldMat == nullptr || oldMat->styleShift() != mat->styleShift()) {
const QVector2D v = mat->styleShift();
- memcpy(buf->data() + ShiftOffset, &v, 8);
+ memcpy(buf->data() + m_currentUbufOffset, &v, 8);
changed = true;
}
@@ -332,17 +295,14 @@ bool QSGStyledTextRhiShader::updateUniformData(RenderState &state,
class QSGOutlinedTextRhiShader : public QSGStyledTextRhiShader
{
public:
- QSGOutlinedTextRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
- : QSGStyledTextRhiShader(glyphFormat, alphaTexture)
+ QSGOutlinedTextRhiShader(QFontEngine::GlyphFormat glyphFormat, int viewCount, bool alphaTexture)
+ : QSGStyledTextRhiShader(glyphFormat, viewCount, alphaTexture)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.frag.qsb"), viewCount);
}
};
@@ -361,6 +321,8 @@ QSGTextMaskMaterial::QSGTextMaskMaterial(QSGRenderContext *rc, const QVector4D &
QSGTextMaskMaterial::~QSGTextMaskMaterial()
{
+ if (m_retainedFontEngine != nullptr)
+ m_rc->unregisterFontengineForCleanup(m_retainedFontEngine);
delete m_texture;
}
@@ -408,7 +370,8 @@ void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat)
qreal devicePixelRatio;
void *cacheKey;
Q_ASSERT(m_rhi);
- cacheKey = m_rhi;
+ Q_ASSERT(m_rc);
+ cacheKey = m_rc;
// Get the dpr the modern way. This value retrieved via the
// rendercontext matches what RenderState::devicePixelRatio()
// exposes to the material shaders later on.
@@ -423,6 +386,12 @@ void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat)
if (!m_glyphCache || int(m_glyphCache->glyphFormat()) != glyphFormat) {
m_glyphCache = new QSGRhiTextureGlyphCache(m_rc, glyphFormat, glyphCacheTransform, color);
fontEngine->setGlyphCache(cacheKey, m_glyphCache.data());
+ if (m_retainedFontEngine != nullptr)
+ m_rc->unregisterFontengineForCleanup(m_retainedFontEngine);
+
+ // Note: This is reference counted by the render context, so it will stay alive until
+ // we release that reference
+ m_retainedFontEngine = fontEngine;
m_rc->registerFontengineForCleanup(fontEngine);
}
}
@@ -557,12 +526,12 @@ QSGMaterialShader *QSGTextMaskMaterial::createShader(QSGRendererInterface::Rende
const QFontEngine::GlyphFormat glyphFormat = gc->glyphFormat();
switch (glyphFormat) {
case QFontEngine::Format_ARGB:
- return new QSG32BitColorTextRhiShader(glyphFormat);
+ return new QSG32BitColorTextRhiShader(glyphFormat, viewCount());
case QFontEngine::Format_A32:
- return new QSG24BitTextMaskRhiShader(glyphFormat);
+ return new QSG24BitTextMaskRhiShader(glyphFormat, viewCount());
case QFontEngine::Format_A8:
default:
- return new QSG8BitTextMaskRhiShader(glyphFormat, gc->eightBitFormatIsAlphaSwizzled());
+ return new QSG8BitTextMaskRhiShader(glyphFormat, viewCount(), gc->eightBitFormatIsAlphaSwizzled());
}
}
@@ -621,7 +590,7 @@ QSGMaterialShader *QSGStyledTextMaterial::createShader(QSGRendererInterface::Ren
{
Q_UNUSED(renderMode);
QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
- return new QSGStyledTextRhiShader(gc->glyphFormat(), gc->eightBitFormatIsAlphaSwizzled());
+ return new QSGStyledTextRhiShader(gc->glyphFormat(), viewCount(), gc->eightBitFormatIsAlphaSwizzled());
}
int QSGStyledTextMaterial::compare(const QSGMaterial *o) const
@@ -653,7 +622,7 @@ QSGMaterialShader *QSGOutlinedTextMaterial::createShader(QSGRendererInterface::R
{
Q_UNUSED(renderMode);
QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
- return new QSGOutlinedTextRhiShader(gc->glyphFormat(), gc->eightBitFormatIsAlphaSwizzled());
+ return new QSGOutlinedTextRhiShader(gc->glyphFormat(), viewCount(), gc->eightBitFormatIsAlphaSwizzled());
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
index e77e2715cd..d3b919c275 100644
--- a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
+++ b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h
@@ -69,6 +69,7 @@ private:
QSGPlainTexture *m_texture;
QExplicitlySharedDataPointer<QFontEngineGlyphCache> m_glyphCache;
QRawFont m_font;
+ QFontEngine *m_retainedFontEngine = nullptr;
QRhi *m_rhi;
QVector4D m_color;
QSize m_size;
diff --git a/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp b/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp
index 9179382f89..d99f19bbe5 100644
--- a/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp
+++ b/src/quick/scenegraph/qsgdefaultinternalimagenode.cpp
@@ -7,14 +7,14 @@
#include <private/qsgtexturematerial_p.h>
#include <qopenglfunctions.h>
#include <QtCore/qmath.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
class SmoothTextureMaterialRhiShader : public QSGTextureMaterialRhiShader
{
public:
- SmoothTextureMaterialRhiShader();
+ SmoothTextureMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
@@ -40,25 +40,28 @@ QSGMaterialType *QSGSmoothTextureMaterial::type() const
QSGMaterialShader *QSGSmoothTextureMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new SmoothTextureMaterialRhiShader;
+ return new SmoothTextureMaterialRhiShader(viewCount());
}
-SmoothTextureMaterialRhiShader::SmoothTextureMaterialRhiShader()
+SmoothTextureMaterialRhiShader::SmoothTextureMaterialRhiShader(int viewCount)
+ : QSGTextureMaterialRhiShader(viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothtexture.frag.qsb"), viewCount);
}
bool SmoothTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
bool changed = false;
QByteArray *buf = state.uniformData();
+ const int shaderMatrixCount = newMaterial->viewCount();
if (!oldMaterial) {
// The viewport is constant, so set the pixel size uniform only once (per batches with the same material).
const QRect r = state.viewportRect();
const QVector2D v(2.0f / r.width(), 2.0f / r.height());
- memcpy(buf->data() + 64 + 8, &v, 8);
+ // mat4 matrix, float opacity, vec2 pixelSize, and the vec2 must be 2*4 aligned
+ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &v, 8);
changed = true;
}
diff --git a/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h b/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h
index 2fc75367a3..1a64bdaace 100644
--- a/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h
+++ b/src/quick/scenegraph/qsgdefaultinternalimagenode_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGSmoothTextureMaterial : public QSGTextureMaterial
+class Q_QUICK_EXPORT QSGSmoothTextureMaterial : public QSGTextureMaterial
{
public:
QSGSmoothTextureMaterial();
@@ -36,7 +36,7 @@ protected:
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultInternalImageNode : public QSGBasicInternalImageNode
+class Q_QUICK_EXPORT QSGDefaultInternalImageNode : public QSGBasicInternalImageNode
{
public:
QSGDefaultInternalImageNode(QSGDefaultRenderContext *rc);
diff --git a/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp b/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp
index 19ee18c0de..57fe15d6bf 100644
--- a/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp
+++ b/src/quick/scenegraph/qsgdefaultinternalrectanglenode.cpp
@@ -16,26 +16,30 @@ QT_BEGIN_NAMESPACE
class SmoothColorMaterialRhiShader : public QSGMaterialShader
{
public:
- SmoothColorMaterialRhiShader();
+ SmoothColorMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-SmoothColorMaterialRhiShader::SmoothColorMaterialRhiShader()
+SmoothColorMaterialRhiShader::SmoothColorMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/smoothcolor.frag.qsb"), viewCount);
}
-bool SmoothColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *oldMaterial)
+bool SmoothColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
bool changed = false;
QByteArray *buf = state.uniformData();
-
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
if (oldMaterial == nullptr) {
@@ -43,13 +47,13 @@ bool SmoothColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMate
const QRect r = state.viewportRect();
const QVector2D v(2.0f / r.width(), 2.0f / r.height());
Q_ASSERT(sizeof(v) == 8);
- memcpy(buf->data() + 64, &v, 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &v, 8);
changed = true;
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 72, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &opacity, 4);
changed = true;
}
@@ -78,7 +82,7 @@ QSGMaterialType *QSGSmoothColorMaterial::type() const
QSGMaterialShader *QSGSmoothColorMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new SmoothColorMaterialRhiShader;
+ return new SmoothColorMaterialRhiShader(viewCount());
}
QSGDefaultInternalRectangleNode::QSGDefaultInternalRectangleNode()
diff --git a/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h b/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h
index 48236d9022..e4ded20888 100644
--- a/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h
+++ b/src/quick/scenegraph/qsgdefaultinternalrectanglenode_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QSGContext;
-class Q_QUICK_PRIVATE_EXPORT QSGSmoothColorMaterial : public QSGMaterial
+class Q_QUICK_EXPORT QSGSmoothColorMaterial : public QSGMaterial
{
public:
QSGSmoothColorMaterial();
@@ -36,7 +36,7 @@ protected:
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultInternalRectangleNode : public QSGBasicInternalRectangleNode
+class Q_QUICK_EXPORT QSGDefaultInternalRectangleNode : public QSGBasicInternalRectangleNode
{
public:
QSGDefaultInternalRectangleNode();
diff --git a/src/quick/scenegraph/qsgdefaultrendercontext.cpp b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
index 3fee9d9faf..1b0753e9ae 100644
--- a/src/quick/scenegraph/qsgdefaultrendercontext.cpp
+++ b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qsgdefaultrendercontext_p.h"
+#include "qsgcurveglyphatlas_p.h"
#include <QtGui/QGuiApplication>
@@ -59,13 +60,32 @@ void QSGDefaultRenderContext::initialize(const QSGRenderContext::InitParams *par
void QSGDefaultRenderContext::invalidateGlyphCaches()
{
- auto it = m_glyphCaches.begin();
- while (it != m_glyphCaches.end()) {
- if (!(*it)->isActive()) {
- delete *it;
- it = m_glyphCaches.erase(it);
- } else {
- ++it;
+ {
+ auto it = m_glyphCaches.begin();
+ while (it != m_glyphCaches.end()) {
+ if (!(*it)->isActive()) {
+ delete *it;
+ it = m_glyphCaches.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ qDeleteAll(m_curveGlyphAtlases);
+ m_curveGlyphAtlases.clear();
+
+ {
+ auto it = m_fontEnginesToClean.begin();
+ while (it != m_fontEnginesToClean.end()) {
+ if (it.value() == 0) {
+ it.key()->clearGlyphCache(this);
+ if (!it.key()->ref.deref())
+ delete it.key();
+ it = m_fontEnginesToClean.erase(it);
+ } else {
+ ++it;
+ }
}
}
}
@@ -108,18 +128,20 @@ void QSGDefaultRenderContext::invalidate()
// code is only called from QQuickWindow's shutdown which is called
// only when the GUI is blocked, and multiple threads will call it in
// sequence. (see qsgdefaultglyphnode_p.cpp's init())
- for (QSet<QFontEngine *>::const_iterator it = m_fontEnginesToClean.constBegin(),
- end = m_fontEnginesToClean.constEnd(); it != end; ++it) {
- (*it)->clearGlyphCache(m_rhi);
- if (!(*it)->ref.deref())
- delete *it;
+ for (auto it = m_fontEnginesToClean.constBegin(); it != m_fontEnginesToClean.constEnd(); ++it) {
+ it.key()->clearGlyphCache(this);
+ if (!it.key()->ref.deref())
+ delete it.key();
}
m_fontEnginesToClean.clear();
+ qDeleteAll(m_curveGlyphAtlases);
+ m_curveGlyphAtlases.clear();
+
qDeleteAll(m_glyphCaches);
m_glyphCaches.clear();
- releaseGlyphCacheResourceUpdates();
+ resetGlyphCacheResources();
m_rhi = nullptr;
@@ -205,27 +227,6 @@ QSGTexture *QSGDefaultRenderContext::compressedTextureForFactory(const QSGCompre
return nullptr;
}
-QString QSGDefaultRenderContext::fontKey(const QRawFont &font, int renderTypeQuality)
-{
- QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
- if (!fe->faceId().filename.isEmpty()) {
- QByteArray keyName =
- fe->faceId().filename + ' ' + QByteArray::number(fe->faceId().index)
- + (font.style() != QFont::StyleNormal ? QByteArray(" I") : QByteArray())
- + (font.weight() != QFont::Normal ? ' ' + QByteArray::number(font.weight()) : QByteArray())
- + ' ' + QByteArray::number(renderTypeQuality)
- + QByteArray(" DF");
- return QString::fromUtf8(keyName);
- } else {
- return QString::fromLatin1("%1_%2_%3_%4_%5")
- .arg(font.familyName())
- .arg(font.styleName())
- .arg(font.weight())
- .arg(font.style())
- .arg(renderTypeQuality);
- }
-}
-
void QSGDefaultRenderContext::initializeRhiShader(QSGMaterialShader *shader, QShader::Variant shaderVariant)
{
QSGMaterialShaderPrivate::get(shader)->prepare(shaderVariant);
@@ -239,11 +240,23 @@ void QSGDefaultRenderContext::preprocess()
}
}
+QSGCurveGlyphAtlas *QSGDefaultRenderContext::curveGlyphAtlas(const QRawFont &font)
+{
+ FontKey key = FontKey(font, 0);
+ QSGCurveGlyphAtlas *atlas = m_curveGlyphAtlases.value(key, nullptr);
+ if (atlas == nullptr) {
+ atlas = new QSGCurveGlyphAtlas(font);
+ m_curveGlyphAtlases.insert(key, atlas);
+ }
+
+ return atlas;
+}
+
QSGDistanceFieldGlyphCache *QSGDefaultRenderContext::distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality)
{
- QString key = fontKey(font, renderTypeQuality);
+ FontKey key(font, renderTypeQuality);
QSGDistanceFieldGlyphCache *cache = m_glyphCaches.value(key, 0);
- if (!cache) {
+ if (!cache && font.isValid()) {
cache = new QSGRhiDistanceFieldGlyphCache(this, font, renderTypeQuality);
m_glyphCaches.insert(key, cache);
}
@@ -264,12 +277,23 @@ QRhiResourceUpdateBatch *QSGDefaultRenderContext::glyphCacheResourceUpdates()
return m_glyphCacheResourceUpdates;
}
-void QSGDefaultRenderContext::releaseGlyphCacheResourceUpdates()
+void QSGDefaultRenderContext::deferredReleaseGlyphCacheTexture(QRhiTexture *texture)
+{
+ if (texture)
+ m_pendingGlyphCacheTextures.insert(texture);
+}
+
+void QSGDefaultRenderContext::resetGlyphCacheResources()
{
if (m_glyphCacheResourceUpdates) {
m_glyphCacheResourceUpdates->release();
m_glyphCacheResourceUpdates = nullptr;
}
+
+ for (QRhiTexture *t : std::as_const(m_pendingGlyphCacheTextures))
+ t->deleteLater(); // the QRhiTexture object stays valid for the current frame
+
+ m_pendingGlyphCacheTextures.clear();
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdefaultrendercontext_p.h b/src/quick/scenegraph/qsgdefaultrendercontext_p.h
index 448255a594..c3352aa89f 100644
--- a/src/quick/scenegraph/qsgdefaultrendercontext_p.h
+++ b/src/quick/scenegraph/qsgdefaultrendercontext_p.h
@@ -16,7 +16,7 @@
//
#include <QtQuick/private/qsgcontext_p.h>
-#include <QtGui/private/qshader_p.h>
+#include <rhi/qshader.h>
QT_BEGIN_NAMESPACE
@@ -24,6 +24,7 @@ class QRhi;
class QRhiCommandBuffer;
class QRhiRenderPassDescriptor;
class QRhiResourceUpdateBatch;
+class QRhiTexture;
class QSGMaterialShader;
class QSurface;
@@ -31,7 +32,7 @@ namespace QSGRhiAtlasTexture {
class Manager;
}
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultRenderContext : public QSGRenderContext
+class Q_QUICK_EXPORT QSGDefaultRenderContext : public QSGRenderContext
{
Q_OBJECT
public:
@@ -69,6 +70,7 @@ public:
void preprocess() override;
void invalidateGlyphCaches() override;
QSGDistanceFieldGlyphCache *distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality) override;
+ QSGCurveGlyphAtlas *curveGlyphAtlas(const QRawFont &font) override;
QSGTexture *createTexture(const QImage &image, uint flags) const override;
QSGRenderer *createRenderer(QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D) override;
@@ -102,11 +104,10 @@ public:
QRhiResourceUpdateBatch *maybeGlyphCacheResourceUpdates();
QRhiResourceUpdateBatch *glyphCacheResourceUpdates();
- void releaseGlyphCacheResourceUpdates();
+ void deferredReleaseGlyphCacheTexture(QRhiTexture *texture);
+ void resetGlyphCacheResources();
protected:
- static QString fontKey(const QRawFont &font, int renderTypeQuality);
-
InitParams m_initParams;
QRhi *m_rhi;
int m_maxTextureSize;
@@ -116,6 +117,8 @@ protected:
qreal m_currentDevicePixelRatio;
bool m_useDepthBufferFor2D;
QRhiResourceUpdateBatch *m_glyphCacheResourceUpdates;
+ QSet<QRhiTexture *> m_pendingGlyphCacheTextures;
+ QHash<FontKey, QSGCurveGlyphAtlas *> m_curveGlyphAtlases;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdefaultspritenode.cpp b/src/quick/scenegraph/qsgdefaultspritenode.cpp
index 233d8e17ac..242c844e9c 100644
--- a/src/quick/scenegraph/qsgdefaultspritenode.cpp
+++ b/src/quick/scenegraph/qsgdefaultspritenode.cpp
@@ -53,7 +53,7 @@ QQuickSpriteMaterial::~QQuickSpriteMaterial()
class SpriteMaterialRhiShader : public QSGMaterialShader
{
public:
- SpriteMaterialRhiShader();
+ SpriteMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -61,10 +61,10 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-SpriteMaterialRhiShader::SpriteMaterialRhiShader()
+SpriteMaterialRhiShader::SpriteMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.frag.qsb"), viewCount);
}
bool SpriteMaterialRhiShader::updateUniformData(RenderState &state,
@@ -80,20 +80,24 @@ bool SpriteMaterialRhiShader::updateUniformData(RenderState &state,
QByteArray *buf = state.uniformData();
Q_ASSERT(buf->size() >= 96);
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
float animPosAndData[7] = { mat->animX1, mat->animY1, mat->animX2, mat->animY2,
mat->animW, mat->animH, mat->animT };
- memcpy(buf->data() + 64, animPosAndData, 28);
+ memcpy(buf->data() + 64 * shaderMatrixCount, animPosAndData, 28);
changed = true;
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 92, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 16 + 12, &opacity, 4);
changed = true;
}
@@ -120,7 +124,7 @@ void SpriteMaterialRhiShader::updateSampledImage(RenderState &state, int binding
QSGMaterialShader *QQuickSpriteMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new SpriteMaterialRhiShader;
+ return new SpriteMaterialRhiShader(viewCount());
}
static QSGGeometry::Attribute Sprite_Attributes[] = {
diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp
index 64f862f948..d5adb52a9f 100644
--- a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp
+++ b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp
@@ -41,7 +41,6 @@ QSGDistanceFieldGlyphNode::~QSGDistanceFieldGlyphNode()
if (m_glyph_cache) {
m_glyph_cache->release(m_glyphs.glyphIndexes());
m_glyph_cache->unregisterGlyphNode(this);
- m_glyph_cache->unregisterOwnerElement(ownerElement());
}
}
@@ -90,15 +89,13 @@ void QSGDistanceFieldGlyphNode::setGlyphs(const QPointF &position, const QGlyphR
return;
if (m_glyph_cache != oldCache) {
- Q_ASSERT(ownerElement() != nullptr);
if (oldCache) {
oldCache->unregisterGlyphNode(this);
- oldCache->unregisterOwnerElement(ownerElement());
}
m_glyph_cache->registerGlyphNode(this);
- m_glyph_cache->registerOwnerElement(ownerElement());
}
- m_glyph_cache->populate(glyphs.glyphIndexes());
+ if (m_glyph_cache)
+ m_glyph_cache->populate(glyphs.glyphIndexes());
const QVector<quint32> glyphIndexes = m_glyphs.glyphIndexes();
for (int i = 0; i < glyphIndexes.size(); ++i)
@@ -158,7 +155,8 @@ void QSGDistanceFieldGlyphNode::invalidateGlyphs(const QVector<quint32> &glyphs)
void QSGDistanceFieldGlyphNode::updateGeometry()
{
- Q_ASSERT(m_glyph_cache);
+ if (!m_glyph_cache)
+ return;
// Remove previously created sub glyph nodes
// We assume all the children are sub glyph nodes
diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp
index b3da72dc81..afb41f32e4 100644
--- a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp
+++ b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.cpp
@@ -36,7 +36,7 @@ static float spreadFunc(float glyphScale)
class QSGDistanceFieldTextMaterialRhiShader : public QSGMaterialShader
{
public:
- QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture);
+ QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -47,19 +47,16 @@ public:
protected:
float m_fontScale = 1.0;
float m_matrixScale = 1.0;
+ quint32 m_currentUbufOffset;
};
-QSGDistanceFieldTextMaterialRhiShader::QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture)
+QSGDistanceFieldTextMaterialRhiShader::QSGDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"));
-
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.frag.qsb"), viewCount);
}
bool QSGDistanceFieldTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -85,36 +82,47 @@ bool QSGDistanceFieldTextMaterialRhiShader::updateUniformData(RenderState &state
updateRange = true;
}
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
m_matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio();
updateRange = true;
}
+ quint32 offset = 0;
+ const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
+ offset += 64;
+ }
if (textureUpdated || !oldMat || oldMat->texture()->texture != mat->texture()->texture) {
const QVector2D ts(1.0f / mat->textureSize().width(), 1.0f / mat->textureSize().height());
Q_ASSERT(sizeof(ts) == 8);
- memcpy(buf->data() + 64, &ts, 8);
+ memcpy(buf->data() + offset, &ts, 8);
changed = true;
}
+ offset += 8 + 8; // 8 is padding for vec4 alignment
if (!oldMat || mat->color() != oldMat->color() || state.isOpacityDirty()) {
const QVector4D color = mat->color() * state.opacity();
Q_ASSERT(sizeof(color) == 16);
- memcpy(buf->data() + 80, &color, 16);
+ memcpy(buf->data() + offset, &color, 16);
changed = true;
}
+ offset += 16;
if (updateRange) { // deferred because depends on m_fontScale and m_matrixScale
const float combinedScale = m_fontScale * m_matrixScale;
const float base = thresholdFunc(combinedScale);
const float range = spreadFunc(combinedScale);
const QVector2D alphaMinMax(qMax(0.0f, base - range), qMin(base + range, 1.0f));
- memcpy(buf->data() + 96, &alphaMinMax, 8);
+ memcpy(buf->data() + offset, &alphaMinMax, 8);
changed = true;
}
+ offset += 8; // not adding any padding here since we are not sure what comes afterwards in the subclasses' shaders
// move texture uploads/copies onto the renderer's soon-to-be-committed list
static_cast<QSGRhiDistanceFieldGlyphCache *>(mat->glyphCache())->commitResourceUpdates(state.resourceUpdateBatch());
+ m_currentUbufOffset = offset;
return changed;
}
@@ -134,20 +142,17 @@ void QSGDistanceFieldTextMaterialRhiShader::updateSampledImage(RenderState &stat
class DistanceFieldAnisotropicTextMaterialRhiShader : public QSGDistanceFieldTextMaterialRhiShader
{
public:
- DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-DistanceFieldAnisotropicTextMaterialRhiShader::DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture)
- : QSGDistanceFieldTextMaterialRhiShader(alphaTexture)
+DistanceFieldAnisotropicTextMaterialRhiShader::DistanceFieldAnisotropicTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldtext_fwidth.frag.qsb"), viewCount);
}
QSGDistanceFieldTextMaterial::QSGDistanceFieldTextMaterial()
@@ -180,9 +185,9 @@ void QSGDistanceFieldTextMaterial::setColor(const QColor &color)
QSGMaterialShader *QSGDistanceFieldTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new QSGDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new QSGDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
bool QSGDistanceFieldTextMaterial::updateTextureSize()
@@ -234,13 +239,13 @@ int QSGDistanceFieldTextMaterial::compare(const QSGMaterial *o) const
class DistanceFieldStyledTextMaterialRhiShader : public QSGDistanceFieldTextMaterialRhiShader
{
public:
- DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-DistanceFieldStyledTextMaterialRhiShader::DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture)
- : QSGDistanceFieldTextMaterialRhiShader(alphaTexture)
+DistanceFieldStyledTextMaterialRhiShader::DistanceFieldStyledTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
}
@@ -254,12 +259,17 @@ bool DistanceFieldStyledTextMaterialRhiShader::updateUniformData(RenderState &st
QByteArray *buf = state.uniformData();
Q_ASSERT(buf->size() >= 128);
+ // must add 8 bytes padding for vec4 alignment, the base class did not do this
+ m_currentUbufOffset += 8; // now at StyleColor
+
if (!oldMat || mat->styleColor() != oldMat->styleColor() || state.isOpacityDirty()) {
QVector4D styleColor = mat->styleColor();
styleColor *= state.opacity();
- memcpy(buf->data() + 112, &styleColor, 16);
+
+ memcpy(buf->data() + m_currentUbufOffset, &styleColor, 16);
changed = true;
}
+ m_currentUbufOffset += 16;
return changed;
}
@@ -292,42 +302,35 @@ int QSGDistanceFieldStyledTextMaterial::compare(const QSGMaterial *o) const
class DistanceFieldOutlineTextMaterialRhiShader : public DistanceFieldStyledTextMaterialRhiShader
{
public:
- DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-DistanceFieldOutlineTextMaterialRhiShader::DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldStyledTextMaterialRhiShader(alphaTexture)
+DistanceFieldOutlineTextMaterialRhiShader::DistanceFieldOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldStyledTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"));
-
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.frag.qsb"), viewCount);
}
class DistanceFieldAnisotropicOutlineTextMaterialRhiShader : public DistanceFieldOutlineTextMaterialRhiShader
{
public:
- DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-DistanceFieldAnisotropicOutlineTextMaterialRhiShader::DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldOutlineTextMaterialRhiShader(alphaTexture)
+DistanceFieldAnisotropicOutlineTextMaterialRhiShader::DistanceFieldAnisotropicOutlineTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldOutlineTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag.qsb"), viewCount);
}
bool DistanceFieldOutlineTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -349,8 +352,8 @@ bool DistanceFieldOutlineTextMaterialRhiShader::updateUniformData(RenderState &s
float alphaMin = qMax(0.0f, base - range);
float styleAlphaMin0 = qMax(0.0f, outlineLimit - range);
float styleAlphaMin1 = qMin(outlineLimit + range, alphaMin);
- memcpy(buf->data() + 128, &styleAlphaMin0, 4);
- memcpy(buf->data() + 132, &styleAlphaMin1, 4);
+ memcpy(buf->data() + m_currentUbufOffset, &styleAlphaMin0, 4);
+ memcpy(buf->data() + m_currentUbufOffset + 4, &styleAlphaMin1, 4);
changed = true;
}
@@ -375,30 +378,27 @@ QSGMaterialType *QSGDistanceFieldOutlineTextMaterial::type() const
QSGMaterialShader *QSGDistanceFieldOutlineTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new DistanceFieldOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldOutlineTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
class DistanceFieldShiftedStyleTextMaterialRhiShader : public DistanceFieldStyledTextMaterialRhiShader
{
public:
- DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-DistanceFieldShiftedStyleTextMaterialRhiShader::DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldStyledTextMaterialRhiShader(alphaTexture)
+DistanceFieldShiftedStyleTextMaterialRhiShader::DistanceFieldShiftedStyleTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldStyledTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.frag.qsb"), viewCount);
}
bool DistanceFieldShiftedStyleTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -416,7 +416,7 @@ bool DistanceFieldShiftedStyleTextMaterialRhiShader::updateUniformData(RenderSta
{
QVector2D shift(1.0 / mat->fontScale() * mat->shift().x(),
1.0 / mat->fontScale() * mat->shift().y());
- memcpy(buf->data() + 128, &shift, 8);
+ memcpy(buf->data() + m_currentUbufOffset, &shift, 8);
changed = true;
}
@@ -426,20 +426,17 @@ bool DistanceFieldShiftedStyleTextMaterialRhiShader::updateUniformData(RenderSta
class DistanceFieldAnisotropicShiftedTextMaterialRhiShader : public DistanceFieldShiftedStyleTextMaterialRhiShader
{
public:
- DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture);
+ DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-DistanceFieldAnisotropicShiftedTextMaterialRhiShader::DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture)
- : DistanceFieldShiftedStyleTextMaterialRhiShader(alphaTexture)
+DistanceFieldAnisotropicShiftedTextMaterialRhiShader::DistanceFieldAnisotropicShiftedTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : DistanceFieldShiftedStyleTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag.qsb"), viewCount);
}
QSGDistanceFieldShiftedStyleTextMaterial::QSGDistanceFieldShiftedStyleTextMaterial()
@@ -460,9 +457,9 @@ QSGMaterialType *QSGDistanceFieldShiftedStyleTextMaterial::type() const
QSGMaterialShader *QSGDistanceFieldShiftedStyleTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicShiftedTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicShiftedTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new DistanceFieldShiftedStyleTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldShiftedStyleTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
int QSGDistanceFieldShiftedStyleTextMaterial::compare(const QSGMaterial *o) const
@@ -476,26 +473,22 @@ int QSGDistanceFieldShiftedStyleTextMaterial::compare(const QSGMaterial *o) cons
class QSGHiQSubPixelDistanceFieldTextMaterialRhiShader : public QSGDistanceFieldTextMaterialRhiShader
{
public:
- QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture);
+ QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
bool updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture)
- : QSGDistanceFieldTextMaterialRhiShader(alphaTexture)
+QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
setFlag(UpdatesGraphicsPipelineState, true);
-
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag.qsb"), viewCount);
}
bool QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::updateUniformData(RenderState &state,
@@ -510,15 +503,16 @@ bool QSGHiQSubPixelDistanceFieldTextMaterialRhiShader::updateUniformData(RenderS
if (!oldMat || mat->fontScale() != oldMat->fontScale()) {
float fontScale = mat->fontScale();
- memcpy(buf->data() + 104, &fontScale, 4);
+ memcpy(buf->data() + m_currentUbufOffset, &fontScale, 4);
changed = true;
}
+ m_currentUbufOffset += 4 + 4; // 4 for padding for vec2 alignment
if (!oldMat || state.isMatrixDirty()) {
int viewportWidth = state.viewportRect().width();
QMatrix4x4 mat = state.combinedMatrix().inverted();
QVector4D vecDelta = mat.column(0) * (qreal(2) / viewportWidth);
- memcpy(buf->data() + 112, &vecDelta, 16);
+ memcpy(buf->data() + m_currentUbufOffset, &vecDelta, 16);
}
return changed;
@@ -551,28 +545,25 @@ QSGMaterialType *QSGHiQSubPixelDistanceFieldTextMaterial::type() const
QSGMaterialShader *QSGHiQSubPixelDistanceFieldTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
class QSGLoQSubPixelDistanceFieldTextMaterialRhiShader : public QSGHiQSubPixelDistanceFieldTextMaterialRhiShader
{
public:
- QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture);
+ QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount);
};
-QSGLoQSubPixelDistanceFieldTextMaterialRhiShader::QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture)
- : QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(alphaTexture)
+QSGLoQSubPixelDistanceFieldTextMaterialRhiShader::QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(bool alphaTexture, int viewCount)
+ : QSGHiQSubPixelDistanceFieldTextMaterialRhiShader(alphaTexture, viewCount)
{
- setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert.qsb"), viewCount);
if (alphaTexture)
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag.qsb"), viewCount);
else
- setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag.qsb"));
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag.qsb"), viewCount);
}
QSGMaterialType *QSGLoQSubPixelDistanceFieldTextMaterial::type() const
@@ -584,9 +575,9 @@ QSGMaterialType *QSGLoQSubPixelDistanceFieldTextMaterial::type() const
QSGMaterialShader *QSGLoQSubPixelDistanceFieldTextMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
if (renderMode == QSGRendererInterface::RenderMode3D && m_glyph_cache->screenSpaceDerivativesSupported())
- return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new DistanceFieldAnisotropicTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
else
- return new QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled());
+ return new QSGLoQSubPixelDistanceFieldTextMaterialRhiShader(m_glyph_cache->eightBitFormatIsAlphaSwizzled(), viewCount());
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h b/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h
index 06d05a928b..207facf213 100644
--- a/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h
+++ b/src/quick/scenegraph/qsgdistancefieldglyphnode_p_p.h
@@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE
class QSGPlainTexture;
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldTextMaterial: public QSGMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldTextMaterial: public QSGMaterial
{
public:
QSGDistanceFieldTextMaterial();
@@ -61,7 +61,7 @@ protected:
QSGPlainTexture *m_sgTexture;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldStyledTextMaterial : public QSGDistanceFieldTextMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldStyledTextMaterial : public QSGDistanceFieldTextMaterial
{
public:
QSGDistanceFieldStyledTextMaterial();
@@ -78,7 +78,7 @@ protected:
QVector4D m_styleColor;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldOutlineTextMaterial : public QSGDistanceFieldStyledTextMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldOutlineTextMaterial : public QSGDistanceFieldStyledTextMaterial
{
public:
QSGDistanceFieldOutlineTextMaterial();
@@ -88,7 +88,7 @@ public:
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDistanceFieldShiftedStyleTextMaterial : public QSGDistanceFieldStyledTextMaterial
+class Q_QUICK_EXPORT QSGDistanceFieldShiftedStyleTextMaterial : public QSGDistanceFieldStyledTextMaterial
{
public:
QSGDistanceFieldShiftedStyleTextMaterial();
@@ -105,7 +105,7 @@ protected:
QPointF m_shift;
};
-class Q_QUICK_PRIVATE_EXPORT QSGHiQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
+class Q_QUICK_EXPORT QSGHiQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
{
public:
QSGMaterialType *type() const override;
@@ -116,7 +116,7 @@ public:
}
};
-class Q_QUICK_PRIVATE_EXPORT QSGLoQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
+class Q_QUICK_EXPORT QSGLoQSubPixelDistanceFieldTextMaterial : public QSGDistanceFieldTextMaterial
{
public:
QSGMaterialType *type() const override;
diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp
index e620a22fb7..3c8da0fc6a 100644
--- a/src/quick/scenegraph/qsgrenderloop.cpp
+++ b/src/quick/scenegraph/qsgrenderloop.cpp
@@ -40,6 +40,18 @@ extern bool qsg_useConsistentTiming();
#define ENABLE_DEFAULT_BACKEND
+Q_TRACE_POINT(qtquick, QSG_renderWindow_entry)
+Q_TRACE_POINT(qtquick, QSG_renderWindow_exit)
+Q_TRACE_POINT(qtquick, QSG_polishItems_entry)
+Q_TRACE_POINT(qtquick, QSG_polishItems_exit)
+Q_TRACE_POINT(qtquick, QSG_sync_entry)
+Q_TRACE_POINT(qtquick, QSG_sync_exit)
+Q_TRACE_POINT(qtquick, QSG_render_entry)
+Q_TRACE_POINT(qtquick, QSG_render_exit)
+Q_TRACE_POINT(qtquick, QSG_swap_entry)
+Q_TRACE_POINT(qtquick, QSG_swap_exit)
+
+
/*
- Uses one QRhi per window. (and so each window has its own QOpenGLContext or ID3D11Device(Context) etc.)
- Animations are advanced using the standard timer (no custom animation
@@ -126,6 +138,7 @@ public:
void releaseSwapchain(QQuickWindow *window);
void handleDeviceLoss();
+ void teardownGraphics();
bool eventFilter(QObject *watched, QEvent *event) override;
@@ -154,6 +167,8 @@ public:
mutable QSet<QSGRenderContext *> pendingRenderContexts;
bool m_inPolish = false;
+
+ bool swRastFallbackDueToSwapchainFailure = false;
};
#endif
@@ -246,6 +261,15 @@ void QSGRenderLoop::setInstance(QSGRenderLoop *instance)
void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
{
+ // Must always be called on the gui thread.
+
+ // Guard for recursion; relevant due to the MessageBox() on Windows.
+ static QSet<QQuickWindow *> recurseGuard;
+ if (recurseGuard.contains(window))
+ return;
+
+ recurseGuard.insert(window);
+
QString translatedMessage;
QString untranslatedMessage;
QQuickWindowPrivate::rhiCreationFailureMessage(QSGRhiSupport::instance()->rhiBackendName(),
@@ -266,6 +290,8 @@ void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
#endif // Q_OS_WIN
if (!signalEmitted)
qFatal("%s", qPrintable(untranslatedMessage));
+
+ recurseGuard.remove(window);
}
#ifdef ENABLE_DEFAULT_BACKEND
@@ -354,13 +380,28 @@ void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
}
}
+void QSGGuiThreadRenderLoop::teardownGraphics()
+{
+ for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
+ if (it->rhi) {
+ QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown();
+ if (it->rc)
+ it->rc->invalidate();
+ releaseSwapchain(it.key());
+ if (it->ownRhi)
+ QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
+ it->rhi = nullptr;
+ }
+ }
+}
+
void QSGGuiThreadRenderLoop::handleDeviceLoss()
{
qWarning("Graphics device lost, cleaning up scenegraph and releasing RHIs");
for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
if (!it->rhi || !it->rhi->isDeviceLost())
- return;
+ continue;
QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown();
@@ -424,7 +465,8 @@ bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
if (!offscreenSurface)
offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
- QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface);
+ const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
+ QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
data.rhi = rhiResult.rhi;
data.ownRhi = rhiResult.own;
@@ -496,7 +538,7 @@ bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
}
cd->swapchain->setWindow(window);
- rhiSupport->applySwapChainFormat(cd->swapchain);
+ rhiSupport->applySwapChainFormat(cd->swapchain, window);
qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
data.sampleCount, alpha ? "yes" : "no");
cd->swapchain->setSampleCount(data.sampleCount);
@@ -591,15 +633,24 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
// signals and want to do graphics stuff already there.
if (cd->swapchain) {
Q_ASSERT(!effectiveOutputSize.isEmpty());
+ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
const QSize previousOutputSize = cd->swapchain->currentPixelSize();
if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
if (cd->swapchainJustBecameRenderable)
qCDebug(QSG_LOG_RENDERLOOP, "just became exposed");
cd->hasActiveSwapchain = cd->swapchain->createOrResize();
- if (!cd->hasActiveSwapchain && data.rhi->isDeviceLost()) {
- handleDeviceLoss();
- return;
+ if (!cd->hasActiveSwapchain) {
+ if (data.rhi->isDeviceLost()) {
+ handleDeviceLoss();
+ return;
+ } else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
+ qWarning("Failed to create swapchain."
+ " Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
+ swRastFallbackDueToSwapchainFailure = true;
+ teardownGraphics();
+ return;
+ }
}
cd->swapchainJustBecameRenderable = false;
@@ -659,6 +710,7 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
Q_TRACE(QSG_swap_entry);
const bool needsPresent = alsoSwap && window->isVisible();
+ double lastCompletedGpuTime = 0;
if (cd->swapchain) {
QRhi::EndFrameFlags flags;
if (!needsPresent)
@@ -669,6 +721,8 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
handleDeviceLoss();
else if (frameResult == QRhi::FrameOpError)
qWarning("Failed to end frame");
+ } else {
+ lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
}
}
if (needsPresent)
@@ -694,6 +748,11 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
int((renderTime - syncTime) / 1000000),
int((swapTime - renderTime) / 1000000),
int(data.timeBetweenRenders.restart()));
+ if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
+ qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] syncAndRender: last retrieved GPU frame time was %.4f ms",
+ window,
+ lastCompletedGpuTime * 1000.0);
+ }
}
// Might have been set during syncSceneGraph()
diff --git a/src/quick/scenegraph/qsgrenderloop_p.h b/src/quick/scenegraph/qsgrenderloop_p.h
index 1a25154658..9157e2edd2 100644
--- a/src/quick/scenegraph/qsgrenderloop_p.h
+++ b/src/quick/scenegraph/qsgrenderloop_p.h
@@ -20,6 +20,7 @@
#include <private/qtquickglobal_p.h>
#include <QtCore/qset.h>
#include <QtCore/qobject.h>
+#include <QtCore/qcoreevent.h>
QT_BEGIN_NAMESPACE
@@ -29,7 +30,7 @@ class QSGRenderContext;
class QAnimationDriver;
class QRunnable;
-class Q_QUICK_PRIVATE_EXPORT QSGRenderLoop : public QObject
+class Q_QUICK_EXPORT QSGRenderLoop : public QObject
{
Q_OBJECT
@@ -94,6 +95,33 @@ enum QSGRenderLoopType
ThreadedRenderLoop
};
+enum QSGCustomEvents {
+
+// Passed from the RL to the RT when a window is removed obscured and
+// should be removed from the render loop.
+WM_Obscure = QEvent::User + 1,
+
+// Passed from the RL to RT when GUI has been locked, waiting for sync
+// (updatePaintNode())
+WM_RequestSync = QEvent::User + 2,
+
+// Passed by the RL to the RT to free up maybe release SG and GL contexts
+// if no windows are rendering.
+WM_TryRelease = QEvent::User + 4,
+
+// Passed by the RL to the RT when a QQuickWindow::grabWindow() is
+// called.
+WM_Grab = QEvent::User + 5,
+
+// Passed by the window when there is a render job to run
+WM_PostJob = QEvent::User + 6,
+
+// When using the QRhi this is sent upon PlatformSurfaceAboutToBeDestroyed from
+// the event filter installed on the QQuickWindow.
+WM_ReleaseSwapchain = QEvent::User + 7,
+
+};
+
QT_END_NAMESPACE
#endif // QSGRENDERLOOP_P_H
diff --git a/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp b/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
index a68842577d..54cf298943 100644
--- a/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
+++ b/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
@@ -32,19 +32,10 @@ QSGRhiDistanceFieldGlyphCache::QSGRhiDistanceFieldGlyphCache(QSGDefaultRenderCon
QSGRhiDistanceFieldGlyphCache::~QSGRhiDistanceFieldGlyphCache()
{
- // A plain delete should work, but just in case commitResourceUpdates was
- // not called and something is enqueued on the update batch for a texture,
- // defer until the end of the frame.
- for (int i = 0; i < m_textures.size(); ++i) {
- if (m_textures[i].texture)
- m_textures[i].texture->deleteLater();
- }
+ for (const TextureInfo &t : std::as_const(m_textures))
+ m_rc->deferredReleaseGlyphCacheTexture(t.texture);
delete m_areaAllocator;
-
- // should be empty, but just in case
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater();
}
void QSGRhiDistanceFieldGlyphCache::requestGlyphs(const QSet<glyph_t> &glyphs)
@@ -243,7 +234,7 @@ void QSGRhiDistanceFieldGlyphCache::resizeTexture(TextureInfo *texInfo, int widt
resourceUpdates->copyTexture(texInfo->texture, oldTexture);
}
- m_pendingDispose.insert(oldTexture);
+ m_rc->deferredReleaseGlyphCacheTexture(oldTexture);
}
bool QSGRhiDistanceFieldGlyphCache::useTextureResizeWorkaround() const
@@ -522,14 +513,8 @@ void QSGRhiDistanceFieldGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatc
{
if (QRhiResourceUpdateBatch *resourceUpdates = m_rc->maybeGlyphCacheResourceUpdates()) {
mergeInto->merge(resourceUpdates);
- m_rc->releaseGlyphCacheResourceUpdates();
+ m_rc->resetGlyphCacheResources();
}
-
- // now let's assume the resource updates will be committed in this frame
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater(); // will be deleted after the frame is submitted -> safe
-
- m_pendingDispose.clear();
}
bool QSGRhiDistanceFieldGlyphCache::eightBitFormatIsAlphaSwizzled() const
diff --git a/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h b/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h
index b7653881f5..4959cd2c50 100644
--- a/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h
+++ b/src/quick/scenegraph/qsgrhidistancefieldglyphcache_p.h
@@ -17,13 +17,13 @@
#include "qsgadaptationlayer_p.h"
#include <private/qsgareaallocator_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGRhiDistanceFieldGlyphCache : public QSGDistanceFieldGlyphCache
+class Q_QUICK_EXPORT QSGRhiDistanceFieldGlyphCache : public QSGDistanceFieldGlyphCache
{
public:
QSGRhiDistanceFieldGlyphCache(QSGDefaultRenderContext *rc, const QRawFont &font, int renderTypeQuality);
diff --git a/src/quick/scenegraph/qsgrhiinternaltextnode.cpp b/src/quick/scenegraph/qsgrhiinternaltextnode.cpp
new file mode 100644
index 0000000000..6108ff7044
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhiinternaltextnode.cpp
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgrhiinternaltextnode_p.h"
+
+#include <private/qquadpath_p.h>
+#include <private/qsgcurvefillnode_p.h>
+#include <private/qsgcurvestrokenode_p.h>
+#include <private/qsgcurveprocessor_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGRhiInternalTextNode::QSGRhiInternalTextNode(QSGRenderContext *renderContext)
+ : QSGInternalTextNode(renderContext)
+{
+}
+
+void QSGRhiInternalTextNode::addDecorationNode(const QRectF &rect, const QColor &color)
+{
+ QSGCurveStrokeNode *node = new QSGCurveStrokeNode;
+ node->setColor(color);
+ node->setStrokeWidth(rect.height());
+
+ QQuadPath path;
+ QPointF c = rect.center();
+ path.moveTo(QVector2D(rect.left(), c.y()));
+ path.lineTo(QVector2D(rect.right(), c.y()));
+
+ QSGCurveProcessor::processStroke(path, 2, rect.height(), Qt::MiterJoin, Qt::FlatCap,
+ [&node](const std::array<QVector2D, 3> &s,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n,
+ bool isLine) {
+ Q_ASSERT(isLine);
+ node->appendTriangle(s, std::array<QVector2D, 2>{p.at(0), p.at(2)}, n);
+ });
+ node->cookGeometry();
+ appendChildNode(node);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgrhiinternaltextnode_p.h b/src/quick/scenegraph/qsgrhiinternaltextnode_p.h
new file mode 100644
index 0000000000..88980773b3
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhiinternaltextnode_p.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGRHIINTERNALTEXTNODE_P_H
+#define QSGRHIINTERNALTEXTNODE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qsginternaltextnode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGRhiInternalTextNode : public QSGInternalTextNode
+{
+public:
+ QSGRhiInternalTextNode(QSGRenderContext *renderContext);
+ void addDecorationNode(const QRectF &rect, const QColor &color) override;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGRHIINTERNALTEXTNODE_P_H
diff --git a/src/quick/scenegraph/qsgrhilayer.cpp b/src/quick/scenegraph/qsgrhilayer.cpp
index 14ed4dee6d..5299cb54ce 100644
--- a/src/quick/scenegraph/qsgrhilayer.cpp
+++ b/src/quick/scenegraph/qsgrhilayer.cpp
@@ -443,10 +443,13 @@ QImage QSGRhiLayer::toImage() const
return QImage();
}
- // There is no room for negotiation here, the texture is RGBA8, and the
- // readback happens with GL_RGBA on GL, so RGBA8888 is the only option.
+ // There is little room for negotiation here, the texture is one of the formats from setFormat.
// Also, Quick is always premultiplied alpha.
- const QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
+ QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
+ if (m_format == QRhiTexture::RGBA16F)
+ imageFormat = QImage::Format_RGBA16FPx4_Premultiplied;
+ else if (m_format == QRhiTexture::RGBA32F)
+ imageFormat = QImage::Format_RGBA32FPx4_Premultiplied;
const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
return QImage(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat).mirrored();
diff --git a/src/quick/scenegraph/qsgrhilayer_p.h b/src/quick/scenegraph/qsgrhilayer_p.h
index 502672b725..922192ec5e 100644
--- a/src/quick/scenegraph/qsgrhilayer_p.h
+++ b/src/quick/scenegraph/qsgrhilayer_p.h
@@ -17,13 +17,13 @@
#include <private/qsgadaptationlayer_p.h>
#include <private/qsgcontext_p.h>
#include <private/qsgtexture_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGRhiLayer : public QSGLayer
+class Q_QUICK_EXPORT QSGRhiLayer : public QSGLayer
{
Q_OBJECT
diff --git a/src/quick/scenegraph/qsgrhishadereffectnode.cpp b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
index 6d6e774918..c3b7b42cb9 100644
--- a/src/quick/scenegraph/qsgrhishadereffectnode.cpp
+++ b/src/quick/scenegraph/qsgrhishadereffectnode.cpp
@@ -7,7 +7,7 @@
#include <qsgmaterialshader.h>
#include <qsgtextureprovider.h>
#include <private/qsgplaintexture_p.h>
-#include <QtGui/private/qshaderdescription_p.h>
+#include <rhi/qshaderdescription.h>
#include <QQmlFile>
#include <QFile>
#include <QFileSelector>
@@ -23,7 +23,7 @@ void QSGRhiShaderLinker::reset(const QShader &vs, const QShader &fs)
m_error = false;
- m_constantBufferSize = 0;
+ //m_constantBufferSize = 0;
m_constants.clear();
m_samplers.clear();
m_samplerNameMap.clear();
@@ -39,7 +39,6 @@ void QSGRhiShaderLinker::feedConstants(const QSGShaderEffectNode::ShaderData &sh
Q_ASSERT(shader.shaderInfo.variables.size() == shader.varData.size());
static bool shaderEffectDebug = qEnvironmentVariableIntValue("QSG_RHI_SHADEREFFECT_DEBUG");
if (!dirtyIndices) {
- m_constantBufferSize = qMax(m_constantBufferSize, shader.shaderInfo.constantDataSize);
for (int i = 0; i < shader.shaderInfo.variables.size(); ++i) {
const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(i));
if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Constant) {
@@ -86,6 +85,12 @@ void QSGRhiShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &sha
const QSGShaderEffectNode::VariableData &vd(shader.varData.at(i));
if (var.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
Q_ASSERT(vd.specialType == QSGShaderEffectNode::VariableData::Source);
+
+#ifndef QT_NO_DEBUG
+ int existingBindPoint = m_samplerNameMap.value(var.name, -1);
+ Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
+#endif
+
m_samplers.insert(var.bindPoint, vd.value);
m_samplerNameMap.insert(var.name, var.bindPoint);
}
@@ -94,6 +99,12 @@ void QSGRhiShaderLinker::feedSamplers(const QSGShaderEffectNode::ShaderData &sha
for (int idx : *dirtyIndices) {
const QSGGuiThreadShaderEffectManager::ShaderInfo::Variable &var(shader.shaderInfo.variables.at(idx));
const QSGShaderEffectNode::VariableData &vd(shader.varData.at(idx));
+
+#ifndef QT_NO_DEBUG
+ int existingBindPoint = m_samplerNameMap.value(var.name, -1);
+ Q_ASSERT(existingBindPoint < 0 || existingBindPoint == var.bindPoint);
+#endif
+
m_samplers.insert(var.bindPoint, vd.value);
m_samplerNameMap.insert(var.name, var.bindPoint);
}
@@ -125,7 +136,7 @@ void QSGRhiShaderLinker::dump()
qDebug() << "Failed to generate program data";
return;
}
- qDebug() << "Combined shader data" << m_vs << m_fs << "cbuffer size" << m_constantBufferSize;
+ qDebug() << "Combined shader data" << m_vs << m_fs;
qDebug() << " - constants" << m_constants;
qDebug() << " - samplers" << m_samplers;
}
@@ -176,7 +187,7 @@ struct QSGRhiShaderMaterialTypeCache
QSGMaterialType *type;
};
QHash<Key, MaterialType> m_types;
- QVector<QSGMaterialType *> m_graveyard;
+ QHash<Key, QSGMaterialType *> m_graveyard;
};
size_t qHash(const QSGRhiShaderMaterialTypeCache::Key &key, size_t seed = 0)
@@ -196,6 +207,14 @@ QSGMaterialType *QSGRhiShaderMaterialTypeCache::ref(const QShader &vs, const QSh
return it->type;
}
+ auto reuseIt = m_graveyard.constFind(k);
+ if (reuseIt != m_graveyard.cend()) {
+ QSGMaterialType *t = reuseIt.value();
+ m_types.insert(k, { 1, t });
+ m_graveyard.erase(reuseIt);
+ return t;
+ }
+
QSGMaterialType *t = new QSGMaterialType;
m_types.insert(k, { 1, t });
return t;
@@ -208,7 +227,7 @@ void QSGRhiShaderMaterialTypeCache::unref(const QShader &vs, const QShader &fs)
auto it = m_types.find(k);
if (it != m_types.end()) {
if (!--it->ref) {
- m_graveyard.append(it->type);
+ m_graveyard.insert(k, it->type);
m_types.erase(it);
}
}
@@ -286,8 +305,27 @@ bool QSGRhiShaderEffectMaterialShader::updateUniformData(RenderState &state, QSG
}
} else if (c.specialType == QSGShaderEffectNode::VariableData::Matrix) {
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- fillUniformBlockMember<float>(dst, m.constData(), 16, c.size);
+ Q_ASSERT(state.projectionMatrixCount() == mat->viewCount());
+ const int rendererViewCount = state.projectionMatrixCount();
+ const int shaderMatrixCount = c.size / 64;
+ if (shaderMatrixCount < mat->viewCount() && mat->viewCount() >= 2) {
+ qWarning("qt_Matrix uniform block member size is wrong: expected at least view_count * 64 bytes, "
+ "where view_count is %d, meaning %d bytes in total, but got only %d bytes. "
+ "This may be due to the ShaderEffect and its shaders not being multiview-capable, "
+ "or they are used with an unexpected render target. "
+ "Check if the shaders declare qt_Matrix as appropriate, "
+ "and if gl_ViewIndex is used correctly in the vertex shader.",
+ mat->viewCount(), mat->viewCount() * 64, c.size);
+ }
+ const int matrixCount = qMin(rendererViewCount, shaderMatrixCount);
+ size_t offset = 0;
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ fillUniformBlockMember<float>(dst + offset, m.constData(), 16, 64);
+ offset += 64;
+ }
+ if (offset < c.size)
+ memset(dst + offset, 0, c.size - offset);
changed = true;
}
} else if (c.specialType == QSGShaderEffectNode::VariableData::SubRect) {
@@ -651,10 +689,42 @@ static QShader loadShaderFromFile(const QString &filename)
return QShader::fromSerialized(f.readAll());
}
+struct QSGRhiShaderEffectDefaultShader
+{
+ QShader shader;
+ quint32 matrixArrayByteSize;
+ quint32 opacityOffset;
+ qint8 viewCount;
+ static QSGRhiShaderEffectDefaultShader create(const QString &filename, int viewCount);
+};
+
+QSGRhiShaderEffectDefaultShader QSGRhiShaderEffectDefaultShader::create(const QString &filename, int viewCount)
+{
+ QSGRhiShaderEffectDefaultShader s;
+ s.shader = loadShaderFromFile(filename);
+ const QList<QShaderDescription::BlockVariable> uboMembers = s.shader.description().uniformBlocks().constFirst().members;
+ for (const auto &member: uboMembers) {
+ if (member.name == QByteArrayLiteral("qt_Matrix"))
+ s.matrixArrayByteSize = member.size;
+ else if (member.name == QByteArrayLiteral("qt_Opacity"))
+ s.opacityOffset = member.offset;
+ }
+ s.viewCount = viewCount;
+ return s;
+}
+
void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
{
- static QShader defaultVertexShader;
- static QShader defaultFragmentShader;
+ static const int defaultVertexShaderCount = 2;
+ static QSGRhiShaderEffectDefaultShader defaultVertexShaders[defaultVertexShaderCount] = {
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"), 1),
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb.mv2qsb"), 2)
+ };
+ static const int defaultFragmentShaderCount = 2;
+ static QSGRhiShaderEffectDefaultShader defaultFragmentShaders[defaultFragmentShaderCount] = {
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"), 1),
+ QSGRhiShaderEffectDefaultShader::create(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb.mv2qsb"), 2)
+ };
if (bool(m_material.flags() & QSGMaterial::Blending) != syncData->blending) {
m_material.setFlag(QSGMaterial::Blending, syncData->blending);
@@ -673,21 +743,45 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
}
m_material.m_hasCustomVertexShader = syncData->vertex.shader->hasShaderCode;
+ quint32 defaultMatrixArrayByteSize = 0;
if (m_material.m_hasCustomVertexShader) {
m_material.m_vertexShader = syncData->vertex.shader->shaderInfo.rhiShader;
} else {
- if (!defaultVertexShader.isValid())
- defaultVertexShader = loadShaderFromFile(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.vert.qsb"));
- m_material.m_vertexShader = defaultVertexShader;
+ bool found = false;
+ for (int i = 0; i < defaultVertexShaderCount; ++i) {
+ if (defaultVertexShaders[i].viewCount == syncData->viewCount) {
+ m_material.m_vertexShader = defaultVertexShaders[i].shader;
+ defaultMatrixArrayByteSize = defaultVertexShaders[i].matrixArrayByteSize;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ qWarning("No default vertex shader found for view count %d", syncData->viewCount);
+ m_material.m_vertexShader = defaultVertexShaders[0].shader;
+ defaultMatrixArrayByteSize = 64;
+ }
}
m_material.m_hasCustomFragmentShader = syncData->fragment.shader->hasShaderCode;
+ quint32 defaultOpacityOffset = 0;
if (m_material.m_hasCustomFragmentShader) {
m_material.m_fragmentShader = syncData->fragment.shader->shaderInfo.rhiShader;
} else {
- if (!defaultFragmentShader.isValid())
- defaultFragmentShader = loadShaderFromFile(QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shadereffect.frag.qsb"));
- m_material.m_fragmentShader = defaultFragmentShader;
+ bool found = false;
+ for (int i = 0; i < defaultFragmentShaderCount; ++i) {
+ if (defaultFragmentShaders[i].viewCount == syncData->viewCount) {
+ m_material.m_fragmentShader = defaultFragmentShaders[i].shader;
+ defaultOpacityOffset = defaultFragmentShaders[i].opacityOffset;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ qWarning("No default fragment shader found for view count %d", syncData->viewCount);
+ m_material.m_fragmentShader = defaultFragmentShaders[0].shader;
+ defaultOpacityOffset = 64;
+ }
}
m_material.m_materialType = shaderMaterialTypeCache[syncData->materialTypeCacheKey].ref(m_material.m_vertexShader,
@@ -705,16 +799,15 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
defaultSD.shaderInfo.rhiShader = m_material.m_vertexShader;
defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex;
- // { mat4 qt_Matrix; float qt_Opacity; } where only the matrix is used
+ // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the matrix is used
QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
v.name = QByteArrayLiteral("qt_Matrix");
v.offset = 0;
- v.size = 16 * sizeof(float);
+ v.size = defaultMatrixArrayByteSize;
defaultSD.shaderInfo.variables.append(v);
QSGShaderEffectNode::VariableData vd;
vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
defaultSD.varData.append(vd);
- defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
m_material.m_linker.feedConstants(defaultSD);
}
@@ -727,10 +820,10 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
defaultSD.shaderInfo.rhiShader = m_material.m_fragmentShader;
defaultSD.shaderInfo.type = QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
- // { mat4 qt_Matrix; float qt_Opacity; } where only the opacity is used
+ // { mat4 qt_Matrix[VIEW_COUNT]; float qt_Opacity; } where only the opacity is used
QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
v.name = QByteArrayLiteral("qt_Opacity");
- v.offset = 16 * sizeof(float);
+ v.offset = defaultOpacityOffset;
v.size = sizeof(float);
defaultSD.shaderInfo.variables.append(v);
QSGShaderEffectNode::VariableData vd;
@@ -750,8 +843,6 @@ void QSGRhiShaderEffectNode::syncMaterial(SyncData *syncData)
vd.specialType = QSGShaderEffectNode::VariableData::Source;
defaultSD.varData.append(vd);
- defaultSD.shaderInfo.constantDataSize = (16 + 1) * sizeof(float);
-
m_material.m_linker.feedConstants(defaultSD);
m_material.m_linker.feedSamplers(defaultSD);
}
@@ -875,7 +966,6 @@ bool QSGRhiGuiThreadShaderEffectManager::reflect(ShaderInfo *result)
}
const QShaderDescription desc = result->rhiShader.description();
- result->constantDataSize = 0;
int ubufBinding = -1;
const QVector<QShaderDescription::UniformBlock> ubufs = desc.uniformBlocks();
@@ -884,7 +974,6 @@ bool QSGRhiGuiThreadShaderEffectManager::reflect(ShaderInfo *result)
const QShaderDescription::UniformBlock &ubuf(ubufs[i]);
if (ubufBinding == -1 && ubuf.binding >= 0) {
ubufBinding = ubuf.binding;
- result->constantDataSize = ubuf.size;
for (const QShaderDescription::BlockVariable &member : ubuf.members) {
ShaderInfo::Variable v;
v.type = ShaderInfo::Constant;
diff --git a/src/quick/scenegraph/qsgrhishadereffectnode_p.h b/src/quick/scenegraph/qsgrhishadereffectnode_p.h
index ed3bf1f1e9..376c7d38ab 100644
--- a/src/quick/scenegraph/qsgrhishadereffectnode_p.h
+++ b/src/quick/scenegraph/qsgrhishadereffectnode_p.h
@@ -50,7 +50,6 @@ public:
bool m_error;
QShader m_vs;
QShader m_fs;
- uint m_constantBufferSize;
QHash<uint, Constant> m_constants; // offset -> Constant
QHash<int, QVariant> m_samplers; // binding -> value (source ref)
QHash<QByteArray, int> m_samplerNameMap; // name -> binding
diff --git a/src/quick/scenegraph/qsgrhisupport.cpp b/src/quick/scenegraph/qsgrhisupport.cpp
index f18d6a7d00..45c183a5f8 100644
--- a/src/quick/scenegraph/qsgrhisupport.cpp
+++ b/src/quick/scenegraph/qsgrhisupport.cpp
@@ -50,19 +50,22 @@ void QSGRhiSupport::applySettings()
if (m_requested.valid) {
// explicit rhi backend request from C++ (e.g. via QQuickWindow)
switch (m_requested.api) {
- case QSGRendererInterface::OpenGLRhi:
+ case QSGRendererInterface::OpenGL:
m_rhiBackend = QRhi::OpenGLES2;
break;
- case QSGRendererInterface::Direct3D11Rhi:
+ case QSGRendererInterface::Direct3D11:
m_rhiBackend = QRhi::D3D11;
break;
- case QSGRendererInterface::VulkanRhi:
+ case QSGRendererInterface::Direct3D12:
+ m_rhiBackend = QRhi::D3D12;
+ break;
+ case QSGRendererInterface::Vulkan:
m_rhiBackend = QRhi::Vulkan;
break;
- case QSGRendererInterface::MetalRhi:
+ case QSGRendererInterface::Metal:
m_rhiBackend = QRhi::Metal;
break;
- case QSGRendererInterface::NullRhi:
+ case QSGRendererInterface::Null:
m_rhiBackend = QRhi::Null;
break;
default:
@@ -79,6 +82,8 @@ void QSGRhiSupport::applySettings()
m_rhiBackend = QRhi::OpenGLES2;
} else if (rhiBackend == QByteArrayLiteral("d3d11") || rhiBackend == QByteArrayLiteral("d3d")) {
m_rhiBackend = QRhi::D3D11;
+ } else if (rhiBackend == QByteArrayLiteral("d3d12")) {
+ m_rhiBackend = QRhi::D3D12;
} else if (rhiBackend == QByteArrayLiteral("vulkan")) {
m_rhiBackend = QRhi::Vulkan;
} else if (rhiBackend == QByteArrayLiteral("metal")) {
@@ -92,7 +97,7 @@ void QSGRhiSupport::applySettings()
}
#if defined(Q_OS_WIN)
m_rhiBackend = QRhi::D3D11;
-#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#elif QT_CONFIG(metal)
m_rhiBackend = QRhi::Metal;
#elif QT_CONFIG(opengl)
m_rhiBackend = QRhi::OpenGLES2;
@@ -111,26 +116,11 @@ void QSGRhiSupport::applySettings()
// (QQuickWindow) may depend on the graphics API as well (surfaceType
// f.ex.), and all that is based on what we report from here. So further
// adjustments are not possible (or, at minimum, not safe and portable).
-
- m_killDeviceFrameCount = qEnvironmentVariableIntValue("QSG_RHI_SIMULATE_DEVICE_LOSS");
- if (m_killDeviceFrameCount > 0 && m_rhiBackend == QRhi::D3D11)
- qDebug("Graphics device will be reset every %d frames", m_killDeviceFrameCount);
-
- QByteArray hdrRequest = qgetenv("QSG_RHI_HDR");
- if (!hdrRequest.isEmpty()) {
- hdrRequest = hdrRequest.toLower();
- if (hdrRequest == QByteArrayLiteral("scrgb") || hdrRequest == QByteArrayLiteral("extendedsrgblinear"))
- m_swapChainFormat = QRhiSwapChain::HDRExtendedSrgbLinear;
- else if (hdrRequest == QByteArrayLiteral("hdr10"))
- m_swapChainFormat = QRhiSwapChain::HDR10;
- else
- qWarning("Unknown HDR mode '%s'", hdrRequest.constData());
- }
}
void QSGRhiSupport::adjustToPlatformQuirks()
{
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
// A macOS VM may not have Metal support at all. We have to decide at this
// point, it will be too late afterwards, and the only way is to see if
// MTLCreateSystemDefaultDevice succeeds.
@@ -225,12 +215,24 @@ void QSGRhiSupport::checkEnvQSgInfo()
#define GL_RGB10_A2 0x8059
#endif
-QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format)
+#ifndef GL_SRGB_ALPHA
+#define GL_SRGB_ALPHA 0x8C42
+#endif
+
+#ifndef GL_SRGB8_ALPHA8
+#define GL_SRGB8_ALPHA8 0x8C43
+#endif
+
+QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format, QRhiTexture::Flags *flags)
{
+ bool sRGB = false;
auto rhiFormat = QRhiTexture::UnknownFormat;
switch (format) {
- case GL_RGBA:
+ case GL_SRGB_ALPHA:
+ case GL_SRGB8_ALPHA8:
+ sRGB = true;
Q_FALLTHROUGH();
+ case GL_RGBA:
case GL_RGBA8:
case 0:
rhiFormat = QRhiTexture::RGBA8;
@@ -292,6 +294,8 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format)
qWarning("GL format %d is not supported", format);
break;
}
+ if (sRGB)
+ (*flags) |=(QRhiTexture::sRGB);
return rhiFormat;
}
#endif
@@ -508,7 +512,7 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromVulkan(uint format, QRh
#endif
#ifdef Q_OS_WIN
-QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromD3D11(uint format, QRhiTexture::Flags *flags)
+QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromDXGI(uint format, QRhiTexture::Flags *flags)
{
auto rhiFormat = QRhiTexture::UnknownFormat;
bool sRGB = false;
@@ -608,7 +612,7 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromD3D11(uint format, QRhi
}
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
namespace QSGRhiSupportMac {
QRhiTexture::Format toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags);
}
@@ -655,15 +659,17 @@ QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const
{
switch (m_rhiBackend) {
case QRhi::Null:
- return QSGRendererInterface::NullRhi;
+ return QSGRendererInterface::Null;
case QRhi::Vulkan:
- return QSGRendererInterface::VulkanRhi;
+ return QSGRendererInterface::Vulkan;
case QRhi::OpenGLES2:
- return QSGRendererInterface::OpenGLRhi;
+ return QSGRendererInterface::OpenGL;
case QRhi::D3D11:
- return QSGRendererInterface::Direct3D11Rhi;
+ return QSGRendererInterface::Direct3D11;
+ case QRhi::D3D12:
+ return QSGRendererInterface::Direct3D12;
case QRhi::Metal:
- return QSGRendererInterface::MetalRhi;
+ return QSGRendererInterface::Metal;
default:
return QSGRendererInterface::Unknown;
}
@@ -677,6 +683,7 @@ QSurface::SurfaceType QSGRhiSupport::windowSurfaceType() const
case QRhi::OpenGLES2:
return QSurface::OpenGLSurface;
case QRhi::D3D11:
+ case QRhi::D3D12:
return QSurface::Direct3DSurface;
case QRhi::Metal:
return QSurface::MetalSurface;
@@ -714,6 +721,10 @@ static const void *qsgrhi_vk_rifResource(QSGRendererInterface::Resource res,
return &maybeVkRpNat->renderPass;
else
return nullptr;
+ case QSGRendererInterface::GraphicsQueueFamilyIndexResource:
+ return &vknat->gfxQueueFamilyIdx;
+ case QSGRendererInterface::GraphicsQueueIndexResource:
+ return &vknat->gfxQueueIdx;
default:
return nullptr;
}
@@ -746,9 +757,22 @@ static const void *qsgrhi_d3d11_rifResource(QSGRendererInterface::Resource res,
return nullptr;
}
}
+
+static const void *qsgrhi_d3d12_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
+{
+ const QRhiD3D12NativeHandles *d3dnat = static_cast<const QRhiD3D12NativeHandles *>(nat);
+ switch (res) {
+ case QSGRendererInterface::DeviceResource:
+ return d3dnat->dev;
+ case QSGRendererInterface::CommandQueueResource:
+ return d3dnat->commandQueue;
+ default:
+ return nullptr;
+ }
+}
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
static const void *qsgrhi_mtl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat,
const QRhiNativeHandles *cbNat)
{
@@ -795,7 +819,7 @@ const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res,
case QSGRendererInterface::RhiRedirectCommandBuffer:
return QQuickWindowPrivate::get(w)->redirect.commandBuffer;
case QSGRendererInterface::RhiRedirectRenderTarget:
- return QQuickWindowPrivate::get(w)->redirect.rt.renderTarget;
+ return QQuickWindowPrivate::get(w)->redirect.rt.rt.renderTarget;
default:
break;
}
@@ -822,8 +846,10 @@ const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res,
#ifdef Q_OS_WIN
case QRhi::D3D11:
return qsgrhi_d3d11_rifResource(res, nat);
+ case QRhi::D3D12:
+ return qsgrhi_d3d12_rifResource(res, nat);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
case QRhi::Metal:
{
QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
@@ -1026,8 +1052,18 @@ void QSGRhiSupport::finalizePipelineCache(QRhi *rhi, const QQuickGraphicsConfigu
// If empty, do nothing. This is exactly what will happen if the rhi was
// created without QRhi::EnablePipelineCacheDataSave set.
- if (buf.isEmpty())
+ if (buf.isEmpty()) {
+ if (isAutomatic) {
+ // Attempt to remove the file. If it does not exist or this fails,
+ // that's fine. The goal is just to prevent warnings from
+ // setPipelineCacheData in future runs, e.g. if the Qt or driver
+ // version does not match _and_ we do not generate any data at run
+ // time, then not writing the file out also means the warning would
+ // appear again and again on every run. Prevent that.
+ QDir().remove(pipelineCacheSave);
+ }
return;
+ }
QLockFile lock(pipelineCacheLockFileName(pipelineCacheSave));
if (!lock.lock()) {
@@ -1068,7 +1104,7 @@ void QSGRhiSupport::finalizePipelineCache(QRhi *rhi, const QQuickGraphicsConfigu
}
// must be called on the render thread
-QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QSurface *offscreenSurface)
+QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QSurface *offscreenSurface, bool forcePreferSwRenderer)
{
QRhi *rhi = nullptr;
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
@@ -1083,7 +1119,8 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
const bool debugLayer = wd->graphicsConfig.isDebugLayerEnabled();
const bool debugMarkers = wd->graphicsConfig.isDebugMarkersEnabled();
- const bool preferSoftware = wd->graphicsConfig.prefersSoftwareDevice();
+ const bool timestamps = wd->graphicsConfig.timestampsEnabled();
+ const bool preferSoftware = wd->graphicsConfig.prefersSoftwareDevice() || forcePreferSwRenderer;
const bool pipelineCacheSave = !wd->graphicsConfig.pipelineCacheSaveFile().isEmpty()
|| (wd->graphicsConfig.isAutomaticPipelineCacheEnabled()
&& !isAutomaticPipelineCacheSaveSkippedForWindow(window->flags()));
@@ -1093,13 +1130,18 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
"Creating QRhi with backend %s for window %p (wflags 0x%X)\n"
" Graphics API debug/validation layers: %d\n"
" Debug markers: %d\n"
- " Prefer software device: %d\n"
+ " Timestamps: %d\n"
+ " Prefer software device: %d%s\n"
" Shader/pipeline cache collection: %d",
- qPrintable(backendName), window, int(window->flags()), debugLayer, debugMarkers, preferSoftware, pipelineCacheSave);
+ qPrintable(backendName), window, int(window->flags()), debugLayer,
+ debugMarkers, timestamps, preferSoftware, forcePreferSwRenderer ? " [FORCED]" : "", pipelineCacheSave);
QRhi::Flags flags;
+ flags |= QRhi::SuppressSmokeTestWarnings;
if (debugMarkers)
flags |= QRhi::EnableDebugMarkers;
+ if (timestamps)
+ flags |= QRhi::EnableTimestamps;
if (preferSoftware)
flags |= QRhi::PreferSoftwareRenderer;
if (pipelineCacheSave)
@@ -1169,10 +1211,6 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
if (backend == QRhi::D3D11) {
QRhiD3D11InitParams rhiParams;
rhiParams.enableDebugLayer = debugLayer;
- if (m_killDeviceFrameCount > 0) {
- rhiParams.framesUntilKillingDeviceViaTdr = m_killDeviceFrameCount;
- rhiParams.repeatDeviceKill = true;
- }
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
QRhiD3D11NativeHandles importDev;
importDev.dev = customDevD->u.deviceAndContext.device;
@@ -1190,7 +1228,32 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
} else {
rhi = QRhi::create(backend, &rhiParams, flags);
- if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
+ if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
+ qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; "
+ "attempting to get a software rasterizer backed device instead");
+ flags |= QRhi::PreferSoftwareRenderer;
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
+ }
+ } else if (backend == QRhi::D3D12) {
+ QRhiD3D12InitParams rhiParams;
+ rhiParams.enableDebugLayer = debugLayer;
+ if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
+ QRhiD3D12NativeHandles importDev;
+ importDev.dev = customDevD->u.deviceAndContext.device;
+ qCDebug(QSG_LOG_INFO, "Using existing native D3D12 device %p", importDev.dev);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Adapter) {
+ QRhiD3D12NativeHandles importDev;
+ importDev.adapterLuidLow = customDevD->u.adapter.luidLow;
+ importDev.adapterLuidHigh = customDevD->u.adapter.luidHigh;
+ importDev.minimumFeatureLevel = customDevD->u.adapter.featureLevel;
+ qCDebug(QSG_LOG_INFO, "Using D3D12 adapter LUID %u, %d and minimum feature level %d",
+ importDev.adapterLuidLow, importDev.adapterLuidHigh, importDev.minimumFeatureLevel);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else {
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ if (!rhi && attemptReinitWithSwRastUponFail() && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; "
"attempting to get a software rasterizer backed device instead");
flags |= QRhi::PreferSoftwareRenderer;
@@ -1199,7 +1262,7 @@ QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QS
}
}
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
if (backend == QRhi::Metal) {
QRhiMetalInitParams rhiParams;
if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndCommandQueue) {
@@ -1460,10 +1523,28 @@ QImage QSGRhiSupport::grabOffscreenForProtectedContent(QQuickWindow *window)
}
#endif
-void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet)
+void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet, QQuickWindow *window)
{
+ Q_ASSERT(scWithWindowSet->window() == window);
+
+ QRhiSwapChain::Format swapChainFormat = QRhiSwapChain::SDR;
+
+ QByteArray hdrRequest = qgetenv("QSG_RHI_HDR");
+ if (hdrRequest.isEmpty())
+ hdrRequest = window->property("_qt_sg_hdr_format").toByteArray();
+
+ if (!hdrRequest.isEmpty()) {
+ hdrRequest = hdrRequest.toLower();
+ if (hdrRequest == QByteArrayLiteral("scrgb") || hdrRequest == QByteArrayLiteral("extendedsrgblinear"))
+ swapChainFormat = QRhiSwapChain::HDRExtendedSrgbLinear;
+ else if (hdrRequest == QByteArrayLiteral("hdr10"))
+ swapChainFormat = QRhiSwapChain::HDR10;
+ else if (hdrRequest == QByteArrayLiteral("p3"))
+ swapChainFormat = QRhiSwapChain::HDRExtendedDisplayP3Linear;
+ }
+
const char *fmtStr = "unknown";
- switch (m_swapChainFormat) {
+ switch (swapChainFormat) {
case QRhiSwapChain::SDR:
fmtStr = "SDR";
break;
@@ -1473,12 +1554,15 @@ void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet)
case QRhiSwapChain::HDR10:
fmtStr = "HDR10";
break;
+ case QRhiSwapChain::HDRExtendedDisplayP3Linear:
+ fmtStr = "Extended Linear Display P3";
+ break;
default:
break;
}
- if (!scWithWindowSet->isFormatSupported(m_swapChainFormat)) {
- if (m_swapChainFormat != QRhiSwapChain::SDR) {
+ if (!scWithWindowSet->isFormatSupported(swapChainFormat)) {
+ if (swapChainFormat != QRhiSwapChain::SDR) {
qCDebug(QSG_LOG_INFO, "Requested a %s swapchain but it is reported to be unsupported with the current display(s). "
"In multi-screen configurations make sure the window is located on a HDR-enabled screen. "
"Request ignored, using SDR swapchain.", fmtStr);
@@ -1486,9 +1570,9 @@ void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet)
return;
}
- scWithWindowSet->setFormat(m_swapChainFormat);
+ scWithWindowSet->setFormat(swapChainFormat);
- if (m_swapChainFormat != QRhiSwapChain::SDR) {
+ if (swapChainFormat != QRhiSwapChain::SDR) {
qCDebug(QSG_LOG_INFO, "Creating %s swapchain", fmtStr);
qCDebug(QSG_LOG_INFO) << "HDR output info:" << scWithWindowSet->hdrInfo();
}
@@ -1504,19 +1588,36 @@ QRhiTexture::Format QSGRhiSupport::toRhiTextureFormat(uint nativeFormat, QRhiTex
#if QT_CONFIG(opengl)
case QRhi::OpenGLES2:
Q_UNUSED(flags);
- return toRhiTextureFormatFromGL(nativeFormat);
+ return toRhiTextureFormatFromGL(nativeFormat, flags);
#endif
#ifdef Q_OS_WIN
case QRhi::D3D11:
- return toRhiTextureFormatFromD3D11(nativeFormat, flags);
+ case QRhi::D3D12:
+ return toRhiTextureFormatFromDXGI(nativeFormat, flags);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
case QRhi::Metal:
return toRhiTextureFormatFromMetal(nativeFormat, flags);
#endif
default:
return QRhiTexture::UnknownFormat;
}
+ Q_UNUSED(nativeFormat)
+ Q_UNUSED(flags)
+}
+
+bool QSGRhiSupport::attemptReinitWithSwRastUponFail() const
+{
+ const QRhi::Implementation backend = rhiBackend();
+
+ // On Windows it makes sense to retry using a software adapter whenever
+ // device creation or swapchain creation fails, as WARP is usually available
+ // (built in to the OS) and is good quality. This helps a lot in particular
+ // when running in a VM that cripples proper 3D graphics.
+ if (backend == QRhi::D3D11 || backend == QRhi::D3D12)
+ return true;
+
+ return false;
}
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgrhisupport_p.h b/src/quick/scenegraph/qsgrhisupport_p.h
index 42cbecabcb..535317d48c 100644
--- a/src/quick/scenegraph/qsgrhisupport_p.h
+++ b/src/quick/scenegraph/qsgrhisupport_p.h
@@ -18,25 +18,7 @@
#include "qsgrenderloop_p.h"
#include "qsgrendererinterface.h"
-#include <QtGui/private/qrhi_p.h>
-
-#include <QtGui/private/qrhinull_p.h>
-
-#if QT_CONFIG(opengl)
-#include <QtGui/private/qrhigles2_p.h>
-#endif
-
-#if QT_CONFIG(vulkan)
-#include <QtGui/private/qrhivulkan_p.h>
-#endif
-
-#ifdef Q_OS_WIN
-#include <QtGui/private/qrhid3d11_p.h>
-#endif
-
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
-#include <QtGui/private/qrhimetal_p.h>
-#endif
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -52,7 +34,7 @@ class QQuickGraphicsConfiguration;
// In addition, the class provides handy conversion and query stuff for the
// renderloop and the QSGRendererInterface implementations.
//
-class Q_QUICK_PRIVATE_EXPORT QSGRhiSupport
+class Q_QUICK_EXPORT QSGRhiSupport
{
public:
static QSGRhiSupport *instance_internal();
@@ -63,7 +45,7 @@ public:
static void checkEnvQSgInfo();
#if QT_CONFIG(opengl)
- static QRhiTexture::Format toRhiTextureFormatFromGL(uint format);
+ static QRhiTexture::Format toRhiTextureFormatFromGL(uint format, QRhiTexture::Flags *flags);
#endif
#if QT_CONFIG(vulkan)
@@ -71,10 +53,10 @@ public:
#endif
#if defined(Q_OS_WIN)
- static QRhiTexture::Format toRhiTextureFormatFromD3D11(uint format, QRhiTexture::Flags *flags);
+ static QRhiTexture::Format toRhiTextureFormatFromDXGI(uint format, QRhiTexture::Flags *flags);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
static QRhiTexture::Format toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags);
#endif
@@ -95,7 +77,7 @@ public:
QRhi *rhi;
bool own;
};
- RhiCreateResult createRhi(QQuickWindow *window, QSurface *offscreenSurface);
+ RhiCreateResult createRhi(QQuickWindow *window, QSurface *offscreenSurface, bool forcePreferSwRenderer = false);
void destroyRhi(QRhi *rhi, const QQuickGraphicsConfiguration &config);
void prepareWindowForRhi(QQuickWindow *window);
@@ -104,11 +86,12 @@ public:
QImage grabOffscreenForProtectedContent(QQuickWindow *window);
#endif
- QRhiSwapChain::Format swapChainFormat() const { return m_swapChainFormat; }
- void applySwapChainFormat(QRhiSwapChain *scWithWindowSet);
+ void applySwapChainFormat(QRhiSwapChain *scWithWindowSet, QQuickWindow *window);
QRhiTexture::Format toRhiTextureFormat(uint nativeFormat, QRhiTexture::Flags *flags) const;
+ bool attemptReinitWithSwRastUponFail() const;
+
private:
QSGRhiSupport();
void applySettings();
@@ -121,8 +104,6 @@ private:
} m_requested;
bool m_settingsApplied = false;
QRhi::Implementation m_rhiBackend = QRhi::Null;
- int m_killDeviceFrameCount;
- QRhiSwapChain::Format m_swapChainFormat = QRhiSwapChain::SDR;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp
index 2324db5762..7e626be8e2 100644
--- a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp
+++ b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp
@@ -23,15 +23,7 @@ QSGRhiTextureGlyphCache::QSGRhiTextureGlyphCache(QSGDefaultRenderContext *rc,
QSGRhiTextureGlyphCache::~QSGRhiTextureGlyphCache()
{
- // A plain delete should work, but just in case commitResourceUpdates was
- // not called and something is enqueued on the update batch for m_texture,
- // defer until the end of the frame.
- if (m_texture)
- m_texture->deleteLater();
-
- // should be empty, but just in case
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater();
+ m_rc->deferredReleaseGlyphCacheTexture(m_texture);
}
QRhiTexture *QSGRhiTextureGlyphCache::createEmptyTexture(QRhiTexture::Format format)
@@ -97,7 +89,7 @@ void QSGRhiTextureGlyphCache::resizeTextureData(int width, int height)
resourceUpdates->uploadTexture(t, QRhiTextureUploadEntry(0, 0, subresDesc));
}
- m_pendingDispose.insert(m_texture);
+ m_rc->deferredReleaseGlyphCacheTexture(m_texture);
m_texture = t;
}
}
@@ -117,36 +109,42 @@ void QSGRhiTextureGlyphCache::prepareGlyphImage(QImage *img)
m_bgra = false;
if (img->format() == QImage::Format_Mono) {
- *img = img->convertToFormat(QImage::Format_Grayscale8);
- } else if (img->depth() == 32) {
- if (img->format() == QImage::Format_RGB32 || img->format() == QImage::Format_ARGB32_Premultiplied) {
- // We need to make the alpha component equal to the average of the RGB values.
- // This is needed when drawing sub-pixel antialiased text on translucent targets.
+ *img = std::move(*img).convertToFormat(QImage::Format_Grayscale8);
+ } else if (img->format() == QImage::Format_RGB32 || img->format() == QImage::Format_ARGB32_Premultiplied) {
+ // We need to make the alpha component equal to the average of the RGB values.
+ // This is needed when drawing sub-pixel antialiased text on translucent targets.
+ if (img->format() == QImage::Format_RGB32
+#if Q_BYTE_ORDER != Q_BIG_ENDIAN
+ || !supportsBgra
+#endif
+ ) {
for (int y = 0; y < maskHeight; ++y) {
- QRgb *src = (QRgb *) img->scanLine(y);
+ QRgb *src = reinterpret_cast<QRgb *>(img->scanLine(y));
for (int x = 0; x < maskWidth; ++x) {
- int r = qRed(src[x]);
- int g = qGreen(src[x]);
- int b = qBlue(src[x]);
- int avg;
- if (img->format() == QImage::Format_RGB32)
- avg = (r + g + b + 1) / 3; // "+1" for rounding.
- else // Format_ARGB_Premultiplied
- avg = qAlpha(src[x]);
-
- src[x] = qRgba(r, g, b, avg);
+ QRgb &rgb = src[x];
+
+ if (img->format() == QImage::Format_RGB32) {
+ int r = qRed(rgb);
+ int g = qGreen(rgb);
+ int b = qBlue(rgb);
+ int avg = (r + g + b + 1) / 3; // "+1" for rounding.
+ rgb = qRgba(r, g, b, avg);
+ }
+
#if Q_BYTE_ORDER != Q_BIG_ENDIAN
- if (supportsBgra) {
- m_bgra = true;
- } else {
+ if (!supportsBgra) {
// swizzle the bits to accommodate for the RGBA upload.
- src[x] = ARGB2RGBA(src[x]);
+ rgb = ARGB2RGBA(rgb);
m_bgra = false;
}
#endif
}
}
}
+#if Q_BYTE_ORDER != Q_BIG_ENDIAN
+ if (supportsBgra)
+ m_bgra = true;
+#endif
}
}
@@ -157,9 +155,9 @@ void QSGRhiTextureGlyphCache::fillTexture(const Coord &c, glyph_t glyph, const Q
if (!m_resizeWithTextureCopy) {
QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition);
- mask = image();
- subresDesc.setSourceTopLeft(QPoint(c.x, c.y));
- subresDesc.setSourceSize(QSize(c.w, c.h));
+ // Explicitly copy() here to avoid fillTexture detaching the *entire* image() when
+ // it is still referenced by QRhiTextureSubresourceUploadDescription.
+ mask = image().copy(QRect(c.x, c.y, c.w, c.h));
} else {
mask = textureMapForGlyph(glyph, subPixelPosition);
}
@@ -220,14 +218,8 @@ void QSGRhiTextureGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatch *mer
{
if (QRhiResourceUpdateBatch *resourceUpdates = m_rc->maybeGlyphCacheResourceUpdates()) {
mergeInto->merge(resourceUpdates);
- m_rc->releaseGlyphCacheResourceUpdates();
+ m_rc->resetGlyphCacheResources();
}
-
- // now let's assume the resource updates will be committed in this frame
- for (QRhiTexture *t : std::as_const(m_pendingDispose))
- t->deleteLater(); // will be deleted after the frame is submitted -> safe
-
- m_pendingDispose.clear();
}
bool QSGRhiTextureGlyphCache::eightBitFormatIsAlphaSwizzled() const
diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache_p.h b/src/quick/scenegraph/qsgrhitextureglyphcache_p.h
index 2960c91b01..fbe020c3a2 100644
--- a/src/quick/scenegraph/qsgrhitextureglyphcache_p.h
+++ b/src/quick/scenegraph/qsgrhitextureglyphcache_p.h
@@ -16,7 +16,7 @@
//
#include <QtGui/private/qtextureglyphcache_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -60,7 +60,6 @@ private:
QSize m_size;
bool m_bgra = false;
QVarLengthArray<QRhiTextureUploadEntry, 16> m_uploads;
- QSet<QRhiTexture *> m_pendingDispose;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
index 2a1ceedb8f..8b4afea707 100644
--- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp
+++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
@@ -7,7 +7,7 @@
#include <QtCore/QWaitCondition>
#include <QtCore/QAnimationDriver>
#include <QtCore/QQueue>
-#include <QtCore/QTime>
+#include <QtCore/QTimer>
#include <QtGui/QGuiApplication>
#include <QtGui/QScreen>
@@ -18,6 +18,7 @@
#include <QtQuick/QQuickWindow>
#include <private/qquickwindow_p.h>
#include <private/qquickitem_p.h>
+#include <QtGui/qpa/qplatformwindow_p.h>
#include <QtQuick/private/qsgrenderer_p.h>
@@ -85,6 +86,15 @@
QT_BEGIN_NAMESPACE
+Q_TRACE_POINT(qtquick, QSG_polishAndSync_entry)
+Q_TRACE_POINT(qtquick, QSG_polishAndSync_exit)
+Q_TRACE_POINT(qtquick, QSG_wait_entry)
+Q_TRACE_POINT(qtquick, QSG_wait_exit)
+Q_TRACE_POINT(qtquick, QSG_syncAndRender_entry)
+Q_TRACE_POINT(qtquick, QSG_syncAndRender_exit)
+Q_TRACE_POINT(qtquick, QSG_animations_entry)
+Q_TRACE_POINT(qtquick, QSG_animations_exit)
+
#define QSG_RT_PAD " (RT) %s"
extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
@@ -92,40 +102,16 @@ extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_
// RL: Render Loop
// RT: Render Thread
-// Passed from the RL to the RT when a window is removed obscured and
-// should be removed from the render loop.
-const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
-
-// Passed from the RL to RT when GUI has been locked, waiting for sync
-// (updatePaintNode())
-const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
-
-// Passed by the RL to the RT to free up maybe release SG and GL contexts
-// if no windows are rendering.
-const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
-
-// Passed by the RL to the RT when a QQuickWindow::grabWindow() is
-// called.
-const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
-
-// Passed by the window when there is a render job to run
-const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
-
-// When using the QRhi this is sent upon PlatformSurfaceAboutToBeDestroyed from
-// the event filter installed on the QQuickWindow.
-const QEvent::Type WM_ReleaseSwapchain = QEvent::Type(QEvent::User + 7);
-template <typename T> T *windowFor(const QList<T> &list, QQuickWindow *window)
+QSGThreadedRenderLoop::Window *QSGThreadedRenderLoop::windowFor(QQuickWindow *window)
{
- for (int i=0; i<list.size(); ++i) {
- const T &t = list.at(i);
+ for (const auto &t : std::as_const(m_windows)) {
if (t.window == window)
- return const_cast<T *>(&t);
+ return const_cast<Window *>(&t);
}
return nullptr;
}
-
class WMWindowEvent : public QEvent
{
public:
@@ -137,7 +123,7 @@ class WMTryReleaseEvent : public WMWindowEvent
{
public:
WMTryReleaseEvent(QQuickWindow *win, bool destroy, bool needsFallbackSurface)
- : WMWindowEvent(win, WM_TryRelease)
+ : WMWindowEvent(win, QEvent::Type(WM_TryRelease))
, inDestructor(destroy)
, needsFallback(needsFallbackSurface)
{}
@@ -150,7 +136,7 @@ class WMSyncEvent : public WMWindowEvent
{
public:
WMSyncEvent(QQuickWindow *c, bool inExpose, bool force, const QRhiSwapChainProxyData &scProxyData)
- : WMWindowEvent(c, WM_RequestSync)
+ : WMWindowEvent(c, QEvent::Type(WM_RequestSync))
, size(c->size())
, dpr(float(c->effectiveDevicePixelRatio()))
, syncInExpose(inExpose)
@@ -168,7 +154,8 @@ public:
class WMGrabEvent : public WMWindowEvent
{
public:
- WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {}
+ WMGrabEvent(QQuickWindow *c, QImage *result) :
+ WMWindowEvent(c, QEvent::Type(WM_Grab)), image(result) {}
QImage *image;
};
@@ -176,7 +163,7 @@ class WMJobEvent : public WMWindowEvent
{
public:
WMJobEvent(QQuickWindow *c, QRunnable *postedJob)
- : WMWindowEvent(c, WM_PostJob), job(postedJob) {}
+ : WMWindowEvent(c, QEvent::Type(WM_PostJob)), job(postedJob) {}
~WMJobEvent() { delete job; }
QRunnable *job;
};
@@ -184,7 +171,8 @@ public:
class WMReleaseSwapchainEvent : public WMWindowEvent
{
public:
- WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { }
+ WMReleaseSwapchainEvent(QQuickWindow *c) :
+ WMWindowEvent(c, QEvent::Type(WM_ReleaseSwapchain)) { }
};
class QSGRenderThreadEventQueue : public QQueue<QEvent *>
@@ -293,6 +281,7 @@ public:
};
void ensureRhi();
+ void teardownGraphics();
void handleDeviceLoss();
QSGThreadedRenderLoop *wm;
@@ -322,6 +311,7 @@ public:
bool rhiDeviceLost = false;
bool rhiDoomed = false;
bool guiNotifiedAboutRhiFailure = false;
+ bool swRastFallbackDueToSwapchainFailure = false;
// Local event queue stuff...
bool stopEventProcessing;
@@ -588,22 +578,27 @@ void QSGRenderThread::sync(bool inExpose)
}
}
-void QSGRenderThread::handleDeviceLoss()
+void QSGRenderThread::teardownGraphics()
{
- if (!rhi || !rhi->isDeviceLost())
- return;
-
- qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI");
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
wd->cleanupNodesOnShutdown();
sgrc->invalidate();
wm->releaseSwapchain(window);
- rhiDeviceLost = true;
if (ownRhi)
QSGRhiSupport::instance()->destroyRhi(rhi, {});
rhi = nullptr;
}
+void QSGRenderThread::handleDeviceLoss()
+{
+ if (!rhi || !rhi->isDeviceLost())
+ return;
+
+ qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI");
+ teardownGraphics();
+ rhiDeviceLost = true;
+}
+
void QSGRenderThread::syncAndRender()
{
const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
@@ -633,6 +628,7 @@ void QSGRenderThread::syncAndRender()
pendingUpdate = 0;
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
// Begin the frame before syncing -> sync is where we may invoke
// updatePaintNode() on the items and they may want to do resource updates.
// Also relevant for applications that connect to the before/afterSynchronizing
@@ -654,10 +650,29 @@ void QSGRenderThread::syncAndRender()
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed");
cd->hasActiveSwapchain = cd->swapchain->createOrResize();
- if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) {
- handleDeviceLoss();
- QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
- return;
+ if (!cd->hasActiveSwapchain) {
+ bool bailOut = false;
+ if (rhi->isDeviceLost()) {
+ handleDeviceLoss();
+ bailOut = true;
+ } else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
+ qWarning("Failed to create swapchain."
+ " Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
+ swRastFallbackDueToSwapchainFailure = true;
+ teardownGraphics();
+ bailOut = true;
+ }
+ if (bailOut) {
+ QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
+ if (syncRequested) {
+ // Lock like sync() would do. Note that exposeRequested always includes syncRequested.
+ qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed swapchain init, wake Gui");
+ mutex.lock();
+ waitCondition.wakeOne();
+ mutex.unlock();
+ }
+ return;
+ }
}
cd->swapchainJustBecameRenderable = false;
@@ -739,7 +754,7 @@ void QSGRenderThread::syncAndRender()
// Zero size windows do not initialize a swapchain and
// rendercontext. So no sync or render can be done then.
const bool canRender = d->renderer && hasValidSwapChain;
-
+ double lastCompletedGpuTime = 0;
if (canRender) {
if (!syncRequested) // else this was already done in sync()
rhi->makeThreadLocalNativeContextCurrent();
@@ -761,6 +776,8 @@ void QSGRenderThread::syncAndRender()
qWarning("Failed to end frame");
if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
+ } else {
+ lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
}
d->fireFrameSwapped();
} else {
@@ -811,6 +828,12 @@ void QSGRenderThread::syncAndRender()
int((syncTime/1000000)),
int((renderTime - syncTime) / 1000000),
int((threadTimer.nsecsElapsed() - renderTime) / 1000000));
+ if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
+ qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][render thread %p] syncAndRender: last retrieved GPU frame time was %.4f ms",
+ window,
+ QThread::currentThread(),
+ lastCompletedGpuTime * 1000.0);
+ }
}
Q_TRACE(QSG_swap_exit);
@@ -856,7 +879,8 @@ void QSGRenderThread::ensureRhi()
if (rhiDoomed) // no repeated attempts if the initial attempt failed
return;
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
- QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface);
+ const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
+ QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
rhi = rhiResult.rhi;
ownRhi = rhiResult.own;
if (rhi) {
@@ -915,7 +939,7 @@ void QSGRenderThread::ensureRhi()
}
cd->swapchain->setWindow(window);
cd->swapchain->setProxyData(scProxyData);
- QSGRhiSupport::instance()->applySwapChainFormat(cd->swapchain);
+ QSGRhiSupport::instance()->applySwapChainFormat(cd->swapchain, window);
qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s.",
rhiSampleCount, alpha ? "yes" : "no");
cd->swapchain->setSampleCount(rhiSampleCount);
@@ -1054,6 +1078,9 @@ void QSGThreadedRenderLoop::animationStopped()
void QSGThreadedRenderLoop::startOrStopAnimationTimer()
{
+ if (!sg->isVSyncDependent(m_animation_driver))
+ return;
+
int exposedWindows = 0;
int unthrottledWindows = 0;
int badVSync = 0;
@@ -1123,7 +1150,7 @@ void QSGThreadedRenderLoop::hide(QQuickWindow *window)
qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
if (window->isExposed())
- handleObscurity(windowFor(m_windows, window));
+ handleObscurity(windowFor(window));
releaseResources(window);
}
@@ -1132,7 +1159,7 @@ void QSGThreadedRenderLoop::resize(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "reisze()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
@@ -1149,7 +1176,7 @@ void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
@@ -1220,7 +1247,7 @@ void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
if (!skipThisExpose)
handleExposure(window);
} else {
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
handleObscurity(w);
}
@@ -1234,7 +1261,7 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w) {
qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
Window win;
@@ -1322,6 +1349,9 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
*/
void QSGThreadedRenderLoop::handleObscurity(Window *w)
{
+ if (!w)
+ return;
+
qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
if (w->thread->isRunning()) {
if (!QQuickWindowPrivate::get(w->window)->updatesEnabled) {
@@ -1329,7 +1359,7 @@ void QSGThreadedRenderLoop::handleObscurity(Window *w)
return;
}
w->thread->mutex.lock();
- w->thread->postEvent(new WMWindowEvent(w->window, WM_Obscure));
+ w->thread->postEvent(new WMWindowEvent(w->window, QEvent::Type(WM_Obscure)));
w->thread->waitCondition.wait(&w->thread->mutex);
w->thread->mutex.unlock();
}
@@ -1344,7 +1374,7 @@ bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
QQuickWindow *window = qobject_cast<QQuickWindow *>(watched);
if (window) {
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w && w->thread->isRunning()) {
w->thread->mutex.lock();
w->thread->postEvent(new WMReleaseSwapchainEvent(window));
@@ -1369,14 +1399,14 @@ void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
return;
}
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
polishAndSync(w);
}
void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
maybeUpdate(w);
}
@@ -1428,14 +1458,33 @@ void QSGThreadedRenderLoop::maybeUpdate(Window *w)
*/
void QSGThreadedRenderLoop::update(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
- if (w->thread == QThread::currentThread()) {
- qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
- w->thread->requestRepaint();
- return;
+ const bool isRenderThread = QThread::currentThread() == w->thread;
+
+#if defined(Q_OS_MACOS)
+ using namespace QNativeInterface::Private;
+ if (auto *cocoaWindow = dynamic_cast<QCocoaWindow*>(window->handle())) {
+ // If the window is being resized we don't want to schedule unthrottled
+ // updates on the render thread, as this will starve the main thread
+ // from getting drawables for displaying the updated window size.
+ if (isRenderThread && cocoaWindow->inLiveResize()) {
+ // In most cases the window will already have update requested
+ // due to the animator triggering a sync, but just in case we
+ // schedule an update request on the main thread explicitly.
+ qCDebug(QSG_LOG_RENDERLOOP) << "window is resizing. update on window" << w->window;
+ QTimer::singleShot(0, window, [=]{ window->requestUpdate(); });
+ return;
+ }
+ }
+#endif
+
+ if (isRenderThread) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
+ w->thread->requestRepaint();
+ return;
}
qCDebug(QSG_LOG_RENDERLOOP) << "update on window" << w->window;
@@ -1448,7 +1497,7 @@ void QSGThreadedRenderLoop::update(QQuickWindow *window)
void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
releaseResources(w, false);
}
@@ -1506,7 +1555,7 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
// Flush pending touch events.
QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
// The delivery of the event might have caused the window to stop rendering
- w = windowFor(m_windows, window);
+ w = windowFor(window);
if (!w || !w->thread || !w->thread->window) {
qCDebug(QSG_LOG_RENDERLOOP, "- removed after event flushing, abort");
return;
@@ -1520,7 +1569,7 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
const qint64 elapsedSinceLastMs = w->timeBetweenPolishAndSyncs.restart();
- if (w->actualWindowFormat.swapInterval() != 0) {
+ if (w->actualWindowFormat.swapInterval() != 0 && sg->isVSyncDependent(m_animation_driver)) {
w->psTimeAccumulator += elapsedSinceLastMs;
w->psTimeSampleCount += 1;
// cannot be too high because we'd then delay recognition of broken vsync at start
@@ -1627,10 +1676,13 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
QQuickProfiler::SceneGraphPolishAndSyncSync);
Q_TRACE(QSG_animations_entry);
- // Now is the time to advance the regular animations (as we are throttled to
- // vsync due to the wait above), but this is only relevant when there is one
- // single window. With multiple windows m_animation_timer is active, and
- // advance() happens instead in response to a good old timer event, not here.
+ // Now is the time to advance the regular animations (as we are throttled
+ // to vsync due to the wait above), but this is only relevant when there is
+ // one single window. With multiple windows m_animation_timer is active,
+ // and advance() happens instead in response to a good old timer event, not
+ // here. (the above applies only when the QSGAnimationDriver reports
+ // isVSyncDependent() == true, if not then we always use the driver and
+ // just advance here)
if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
m_animation_driver->advance();
@@ -1674,6 +1726,7 @@ bool QSGThreadedRenderLoop::event(QEvent *e)
switch ((int) e->type()) {
case QEvent::Timer: {
+ Q_ASSERT(sg->isVSyncDependent(m_animation_driver));
QTimerEvent *te = static_cast<QTimerEvent *>(e);
if (te->timerId() == m_animation_timer) {
qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-render thread timer");
@@ -1681,6 +1734,7 @@ bool QSGThreadedRenderLoop::event(QEvent *e)
emit timeToIncubate();
return true;
}
+ break;
}
default:
@@ -1706,7 +1760,7 @@ QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
Q_ASSERT(w);
if (!w->thread->isRunning())
@@ -1741,7 +1795,7 @@ QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
*/
void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w && w->thread && w->thread->window)
w->thread->postEvent(new WMJobEvent(window, job));
else
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop_p.h b/src/quick/scenegraph/qsgthreadedrenderloop_p.h
index 4e0c471ab4..5d209f2f0f 100644
--- a/src/quick/scenegraph/qsgthreadedrenderloop_p.h
+++ b/src/quick/scenegraph/qsgthreadedrenderloop_p.h
@@ -76,6 +76,8 @@ private:
friend class QSGRenderThread;
+
+ Window *windowFor(QQuickWindow *window);
void releaseResources(Window *window, bool inDestructor);
bool checkAndResetForceUpdate(QQuickWindow *window);
diff --git a/src/quick/scenegraph/shaders_ng/24bittextmask.frag b/src/quick/scenegraph/shaders_ng/24bittextmask.frag
index ed8da4cd30..c6e39c80db 100644
--- a/src/quick/scenegraph/shaders_ng/24bittextmask.frag
+++ b/src/quick/scenegraph/shaders_ng/24bittextmask.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,14 +10,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
vec4 glyph = texture(_qt_texture, sampleCoord);
- fragColor = vec4(glyph.rgb * ubuf.color.a, glyph.a);
+ fragColor = glyph * color.a;
}
diff --git a/src/quick/scenegraph/shaders_ng/32bitcolortext.frag b/src/quick/scenegraph/shaders_ng/32bitcolortext.frag
index 4198a4d339..9d67c8b302 100644
--- a/src/quick/scenegraph/shaders_ng/32bitcolortext.frag
+++ b/src/quick/scenegraph/shaders_ng/32bitcolortext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,13 +10,17 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
- fragColor = texture(_qt_texture, sampleCoord) * ubuf.color.a;
+ fragColor = texture(_qt_texture, sampleCoord) * color.a;
}
diff --git a/src/quick/scenegraph/shaders_ng/8bittextmask.frag b/src/quick/scenegraph/shaders_ng/8bittextmask.frag
index a06743876d..530ebd69ef 100644
--- a/src/quick/scenegraph/shaders_ng/8bittextmask.frag
+++ b/src/quick/scenegraph/shaders_ng/8bittextmask.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,13 +10,17 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
- fragColor = ubuf.color * texture(_qt_texture, sampleCoord).r;
+ fragColor = color * texture(_qt_texture, sampleCoord).r;
}
diff --git a/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag b/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag
index f725cbc5e7..d6ace867a3 100644
--- a/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag
+++ b/src/quick/scenegraph/shaders_ng/8bittextmask_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,13 +10,17 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
- fragColor = ubuf.color * texture(_qt_texture, sampleCoord).a; // take .a instead of .r
+ fragColor = color * texture(_qt_texture, sampleCoord).a; // take .a instead of .r
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag
index b1551d8ef4..1ece7b1e51 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,12 +22,12 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
float d = texture(_qt_texture, sampleCoord).r;
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, d);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a)
- * smoothstep(ubuf.outlineAlphaMax0, ubuf.outlineAlphaMax1, d);
+ float a = smoothstep(alphaMin, alphaMax, d);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a)
+ * smoothstep(outlineAlphaMax0, outlineAlphaMax1, d);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert
index 8f0d618503..107ec9dfdc 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext.vert
@@ -6,7 +6,11 @@ layout(location = 1) in vec2 tCoord;
layout(location = 0) out vec2 sampleCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,12 +19,14 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag
index 7c6bd9a493..ac289221f9 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,12 +22,12 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
float d = texture(_qt_texture, sampleCoord).a;
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, d);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a)
- * smoothstep(ubuf.outlineAlphaMax0, ubuf.outlineAlphaMax1, d);
+ float a = smoothstep(alphaMin, alphaMax, d);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a)
+ * smoothstep(outlineAlphaMax0, outlineAlphaMax1, d);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag
index 30ec465791..ddf8f28d1d 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_a_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,7 +22,7 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
@@ -24,8 +31,8 @@ void main()
// The outlineLimit is based on font size, but scales with the transform, so
// we can calculate it from the outline span.
- float outlineLimit = (ubuf.outlineAlphaMax1 - ubuf.outlineAlphaMax0) / 2.0 + ubuf.outlineAlphaMax0;
+ float outlineLimit = (outlineAlphaMax1 - outlineAlphaMax0) / 2.0 + outlineAlphaMax0;
float a = smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag
index 511bffb09a..8aa5326be0 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldoutlinetext_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,7 +9,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,7 +22,7 @@ layout(std140, binding = 0) uniform buf {
vec4 styleColor;
float outlineAlphaMax0;
float outlineAlphaMax1;
-} ubuf;
+};
void main()
{
@@ -24,8 +31,8 @@ void main()
// The outlineLimit is based on font size, but scales with the transform, so
// we can calculate it from the outline span.
- float outlineLimit = (ubuf.outlineAlphaMax1 - ubuf.outlineAlphaMax0) / 2.0 + ubuf.outlineAlphaMax0;
+ float outlineLimit = (outlineAlphaMax1 - outlineAlphaMax0) / 2.0 + outlineAlphaMax0;
float a = smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
- fragColor = step(1.0 - a, 1.0) * mix(ubuf.styleColor, ubuf.color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
+ fragColor = step(1.0 - a, 1.0) * mix(styleColor, color, a) * smoothstep(max(0.0, outlineLimit - f), min(outlineLimit + f, 0.5 - f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag
index aa3390094b..6374f2d3ac 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,12 +23,12 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, texture(_qt_texture, sampleCoord).r);
- vec4 shifted = ubuf.styleColor * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
+ float a = smoothstep(alphaMin, alphaMax, texture(_qt_texture, sampleCoord).r);
+ vec4 shifted = styleColor * smoothstep(alphaMin, alphaMax,
texture(_qt_texture, shiftedSampleCoord).r);
- fragColor = mix(shifted, ubuf.color, a);
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert
index f3a7671435..5eeb6d8ecb 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext.vert
@@ -7,7 +7,11 @@ layout(location = 0) out vec2 sampleCoord;
layout(location = 1) out vec2 shiftedSampleCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,13 +19,15 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- shiftedSampleCoord = (tCoord - ubuf.shift) * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+ shiftedSampleCoord = (tCoord - shift) * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag
index ab3a5f63ff..cd505e35fb 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,12 +23,12 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
- float a = smoothstep(ubuf.alphaMin, ubuf.alphaMax, texture(_qt_texture, sampleCoord).a);
- vec4 shifted = ubuf.styleColor * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
+ float a = smoothstep(alphaMin, alphaMax, texture(_qt_texture, sampleCoord).a);
+ vec4 shifted = styleColor * smoothstep(alphaMin, alphaMax,
texture(_qt_texture, shiftedSampleCoord).a);
- fragColor = mix(shifted, ubuf.color, a);
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag
index 8f528fea1e..e7112929a7 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_a_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,7 +23,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -28,6 +35,6 @@ void main()
float shiftedF = fwidth(shiftedDistance);
float shiftedA = smoothstep(0.5 - shiftedF, 0.5 + shiftedF, shiftedDistance);
- vec4 shifted = ubuf.styleColor * shiftedA;
- fragColor = mix(shifted, ubuf.color, a);
+ vec4 shifted = styleColor * shiftedA;
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag
index a71cc1d9b0..373e97ffff 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldshiftedtext_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,7 +23,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -28,6 +35,6 @@ void main()
float shiftedF = fwidth(shiftedDistance);
float shiftedA = smoothstep(0.5 - shiftedF, 0.5 + shiftedF, shiftedDistance);
- vec4 shifted = ubuf.styleColor * shiftedA;
- fragColor = mix(shifted, ubuf.color, a);
+ vec4 shifted = styleColor * shiftedA;
+ fragColor = mix(shifted, color, a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext.frag
index d594207567..a94a1257b9 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,15 +9,19 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
- fragColor = ubuf.color * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
- texture(_qt_texture, sampleCoord).r);
+ fragColor = color * smoothstep(alphaMin, alphaMax,
+ texture(_qt_texture, sampleCoord).r);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext.vert b/src/quick/scenegraph/shaders_ng/distancefieldtext.vert
index d56ddddd24..eeb9f7bcce 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext.vert
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext.vert
@@ -6,17 +6,23 @@ layout(location = 1) in vec2 tCoord;
layout(location = 0) out vec2 sampleCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag
index bb807d86d8..dccca64a34 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,15 +9,19 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
- fragColor = ubuf.color * smoothstep(ubuf.alphaMin, ubuf.alphaMax,
- texture(_qt_texture, sampleCoord).a);
+ fragColor = color * smoothstep(alphaMin, alphaMax,
+ texture(_qt_texture, sampleCoord).a);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag
index 1aa1175b57..e4d2bced7a 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext_a_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,16 +9,20 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
float distance = texture(_qt_texture, sampleCoord).a;
float f = fwidth(distance);
- fragColor = ubuf.color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
+ fragColor = color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag b/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag
index a698c19550..e895071811 100644
--- a/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag
+++ b/src/quick/scenegraph/shaders_ng/distancefieldtext_fwidth.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -6,16 +9,20 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
float alphaMax;
-} ubuf;
+};
void main()
{
float distance = texture(_qt_texture, sampleCoord).r;
float f = fwidth(distance);
- fragColor = ubuf.color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
+ fragColor = color * smoothstep(max(0.0, 0.5 - f), min(1.0, 0.5 + f), distance);
}
diff --git a/src/quick/scenegraph/shaders_ng/flatcolor.frag b/src/quick/scenegraph/shaders_ng/flatcolor.frag
index 3a677b7c93..c5f9f3f2c4 100644
--- a/src/quick/scenegraph/shaders_ng/flatcolor.frag
+++ b/src/quick/scenegraph/shaders_ng/flatcolor.frag
@@ -1,13 +1,20 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 color;
-} ubuf;
+};
void main()
{
- fragColor = ubuf.color;
+ fragColor = color;
}
diff --git a/src/quick/scenegraph/shaders_ng/flatcolor.vert b/src/quick/scenegraph/shaders_ng/flatcolor.vert
index b5dfd32197..eec2a2d6ed 100644
--- a/src/quick/scenegraph/shaders_ng/flatcolor.vert
+++ b/src/quick/scenegraph/shaders_ng/flatcolor.vert
@@ -3,13 +3,19 @@
layout(location = 0) in vec4 vertexCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 color;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- gl_Position = ubuf.matrix * vertexCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+#else
+ gl_Position = matrix * vertexCoord;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag
index 723227a04d..fc3d04cb60 100644
--- a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag
+++ b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -11,7 +14,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -19,7 +26,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
@@ -31,10 +38,10 @@ void main()
n.w = textureProj(_qt_texture, sampleFarRight).r;
vec2 d = min(abs(n.yw - n.xz) * 2., 0.67);
- vec2 lo = mix(vec2(ubuf.alphaMin), vec2(0.5), d);
- vec2 hi = mix(vec2(ubuf.alphaMax), vec2(0.5), d);
+ vec2 lo = mix(vec2(alphaMin), vec2(0.5), d);
+ vec2 hi = mix(vec2(alphaMax), vec2(0.5), d);
n = smoothstep(lo.xxyy, hi.xxyy, n);
c = smoothstep(lo.x + lo.y, hi.x + hi.y, 2. * c);
- fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * ubuf.color.w;
+ fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert
index 9c7281c31c..922074791b 100644
--- a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert
+++ b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext.vert
@@ -10,7 +10,11 @@ layout(location = 3) out vec3 sampleNearRight;
layout(location = 4) out vec3 sampleFarRight;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -18,24 +22,26 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
// Calculate neighbor pixel position in item space.
- vec3 wDelta = gl_Position.w * ubuf.vecDelta.xyw;
+ vec3 wDelta = gl_Position.w * vecDelta.xyw;
vec3 farLeft = vCoord.xyw - 0.667 * wDelta;
vec3 nearLeft = vCoord.xyw - 0.333 * wDelta;
vec3 nearRight = vCoord.xyw + 0.333 * wDelta;
vec3 farRight = vCoord.xyw + 0.667 * wDelta;
// Calculate neighbor texture coordinate.
- vec2 scale = ubuf.textureScale / ubuf.fontScale;
+ vec2 scale = textureScale / fontScale;
vec2 base = sampleCoord - scale * vCoord.xy;
sampleFarLeft = vec3(base * farLeft.z + scale * farLeft.xy, farLeft.z);
sampleNearLeft = vec3(base * nearLeft.z + scale * nearLeft.xy, nearLeft.z);
diff --git a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag
index a9d56f6380..3bd18f1dec 100644
--- a/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/hiqsubpixeldistancefieldtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -11,7 +14,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -19,7 +26,7 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
@@ -31,10 +38,10 @@ void main()
n.w = textureProj(_qt_texture, sampleFarRight).a;
vec2 d = min(abs(n.yw - n.xz) * 2., 0.67);
- vec2 lo = mix(vec2(ubuf.alphaMin), vec2(0.5), d);
- vec2 hi = mix(vec2(ubuf.alphaMax), vec2(0.5), d);
+ vec2 lo = mix(vec2(alphaMin), vec2(0.5), d);
+ vec2 hi = mix(vec2(alphaMax), vec2(0.5), d);
n = smoothstep(lo.xxyy, hi.xxyy, n);
c = smoothstep(lo.x + lo.y, hi.x + hi.y, 2. * c);
- fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * ubuf.color.w;
+ fragColor = vec4(0.333 * (n.xyz + n.yzw + c), c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag
index 08b2ce5187..5571417bc5 100644
--- a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag
+++ b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec3 sampleNearLeft;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,14 +23,14 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
vec2 n;
n.x = textureProj(_qt_texture, sampleNearLeft).r;
n.y = textureProj(_qt_texture, sampleNearRight).r;
- n = smoothstep(ubuf.alphaMin, ubuf.alphaMax, n);
+ n = smoothstep(alphaMin, alphaMax, n);
float c = 0.5 * (n.x + n.y);
- fragColor = vec4(n.x, c, n.y, c) * ubuf.color.w;
+ fragColor = vec4(n.x, c, n.y, c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert
index 187c384959..1b1d3b4013 100644
--- a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert
+++ b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext.vert
@@ -7,7 +7,11 @@ layout(location = 0) out vec3 sampleNearLeft;
layout(location = 1) out vec3 sampleNearRight;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -15,22 +19,24 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- vec2 sampleCoord = tCoord * ubuf.textureScale;
- gl_Position = ubuf.matrix * vCoord;
+ vec2 sampleCoord = tCoord * textureScale;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vCoord;
+#else
+ gl_Position = matrix * vCoord;
+#endif
// Calculate neighbor pixel position in item space.
- vec3 wDelta = gl_Position.w * ubuf.vecDelta.xyw;
+ vec3 wDelta = gl_Position.w * vecDelta.xyw;
vec3 nearLeft = vCoord.xyw - 0.25 * wDelta;
vec3 nearRight = vCoord.xyw + 0.25 * wDelta;
// Calculate neighbor texture coordinate.
- vec2 scale = ubuf.textureScale / ubuf.fontScale;
+ vec2 scale = textureScale / fontScale;
vec2 base = sampleCoord - scale * vCoord.xy;
sampleNearLeft = vec3(base * nearLeft.z + scale * nearLeft.xy, nearLeft.z);
sampleNearRight = vec3(base * nearRight.z + scale * nearRight.xy, nearRight.z);
diff --git a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag
index ef9407491b..3cd7176721 100644
--- a/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/loqsubpixeldistancefieldtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec3 sampleNearLeft;
@@ -8,7 +11,11 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 textureScale;
vec4 color;
float alphaMin;
@@ -16,14 +23,14 @@ layout(std140, binding = 0) uniform buf {
// up to this point it must match distancefieldtext
float fontScale;
vec4 vecDelta;
-} ubuf;
+};
void main()
{
vec2 n;
n.x = textureProj(_qt_texture, sampleNearLeft).a;
n.y = textureProj(_qt_texture, sampleNearRight).a;
- n = smoothstep(ubuf.alphaMin, ubuf.alphaMax, n);
+ n = smoothstep(alphaMin, alphaMax, n);
float c = 0.5 * (n.x + n.y);
- fragColor = vec4(n.x, c, n.y, c) * ubuf.color.w;
+ fragColor = vec4(n.x, c, n.y, c) * color.w;
}
diff --git a/src/quick/scenegraph/shaders_ng/opaquetexture.frag b/src/quick/scenegraph/shaders_ng/opaquetexture.frag
index 2cd2175f87..18dd7e0fcb 100644
--- a/src/quick/scenegraph/shaders_ng/opaquetexture.frag
+++ b/src/quick/scenegraph/shaders_ng/opaquetexture.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 qt_TexCoord;
diff --git a/src/quick/scenegraph/shaders_ng/opaquetexture.vert b/src/quick/scenegraph/shaders_ng/opaquetexture.vert
index 5b52a59004..22df4a239a 100644
--- a/src/quick/scenegraph/shaders_ng/opaquetexture.vert
+++ b/src/quick/scenegraph/shaders_ng/opaquetexture.vert
@@ -6,13 +6,19 @@ layout(location = 1) in vec2 qt_VertexTexCoord;
layout(location = 0) out vec2 qt_TexCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+#endif
+};
void main()
{
qt_TexCoord = qt_VertexTexCoord;
- gl_Position = ubuf.qt_Matrix * qt_VertexPosition;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = qt_Matrix[gl_ViewIndex] * qt_VertexPosition;
+#else
+ gl_Position = qt_Matrix * qt_VertexPosition;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/outlinedtext.frag b/src/quick/scenegraph/shaders_ng/outlinedtext.frag
index e2f82d3845..5c81f26272 100644
--- a/src/quick/scenegraph/shaders_ng/outlinedtext.frag
+++ b/src/quick/scenegraph/shaders_ng/outlinedtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -12,14 +15,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -30,5 +37,5 @@ void main()
texture(_qt_texture, sCoordRight).r,
0.0, 1.0) - glyph,
0.0, 1.0);
- fragColor = outline * ubuf.styleColor + step(1.0 - glyph, 1.0) * glyph * ubuf.color;
+ fragColor = outline * styleColor + step(1.0 - glyph, 1.0) * glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/outlinedtext.vert b/src/quick/scenegraph/shaders_ng/outlinedtext.vert
index 4068e42f28..a75b15556d 100644
--- a/src/quick/scenegraph/shaders_ng/outlinedtext.vert
+++ b/src/quick/scenegraph/shaders_ng/outlinedtext.vert
@@ -11,24 +11,30 @@ layout(location = 4) out vec2 sCoordRight;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- sCoordUp = (tCoord - vec2(0.0, -1.0)) * ubuf.textureScale;
- sCoordDown = (tCoord - vec2(0.0, 1.0)) * ubuf.textureScale;
- sCoordLeft = (tCoord - vec2(-1.0, 0.0)) * ubuf.textureScale;
- sCoordRight = (tCoord - vec2(1.0, 0.0)) * ubuf.textureScale;
- vec4 xformed = ubuf.modelViewMatrix * vCoord;
- gl_Position = ubuf.projectionMatrix * vec4(floor(xformed.xyz * ubuf.dpr + 0.5) / ubuf.dpr, xformed.w);
+ sampleCoord = tCoord * textureScale;
+ sCoordUp = (tCoord - vec2(0.0, -1.0)) * textureScale;
+ sCoordDown = (tCoord - vec2(0.0, 1.0)) * textureScale;
+ sCoordLeft = (tCoord - vec2(-1.0, 0.0)) * textureScale;
+ sCoordRight = (tCoord - vec2(1.0, 0.0)) * textureScale;
+ vec4 xformed = modelViewMatrix * vCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = projectionMatrix[gl_ViewIndex] * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#else
+ gl_Position = projectionMatrix * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag b/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag
index 274d891a3c..c4db8b54f0 100644
--- a/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/outlinedtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -12,14 +15,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
@@ -30,5 +37,5 @@ void main()
texture(_qt_texture, sCoordRight).a,
0.0, 1.0) - glyph,
0.0, 1.0);
- fragColor = outline * ubuf.styleColor + step(1.0 - glyph, 1.0) * glyph * ubuf.color;
+ fragColor = outline * styleColor + step(1.0 - glyph, 1.0) * glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/shadereffect.frag b/src/quick/scenegraph/shaders_ng/shadereffect.frag
index bde493f6ce..3d1037ed05 100644
--- a/src/quick/scenegraph/shaders_ng/shadereffect.frag
+++ b/src/quick/scenegraph/shaders_ng/shadereffect.frag
@@ -1,10 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform qt_buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float qt_Opacity;
} qt_ubuf;
diff --git a/src/quick/scenegraph/shaders_ng/shadereffect.vert b/src/quick/scenegraph/shaders_ng/shadereffect.vert
index ae65059f19..e030b5ee71 100644
--- a/src/quick/scenegraph/shaders_ng/shadereffect.vert
+++ b/src/quick/scenegraph/shaders_ng/shadereffect.vert
@@ -6,14 +6,20 @@ layout(location = 1) in vec2 qt_MultiTexCoord0;
layout(location = 0) out vec2 qt_TexCoord0;
layout(std140, binding = 0) uniform qt_buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float qt_Opacity;
} qt_ubuf; // must use a name that does not clash with custom code when no uniform blocks
-out gl_PerVertex { vec4 gl_Position; };
-
void main()
{
qt_TexCoord0 = qt_MultiTexCoord0;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = qt_ubuf.qt_Matrix[gl_ViewIndex] * qt_Vertex;
+#else
gl_Position = qt_ubuf.qt_Matrix * qt_Vertex;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/shapecurve.frag b/src/quick/scenegraph/shaders_ng/shapecurve.frag
new file mode 100644
index 0000000000..cc97f375ab
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.frag
@@ -0,0 +1,152 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#version 440
+
+layout(location = 0) in vec4 qt_TexCoord;
+layout(location = 1) in vec4 gradient;
+
+#if defined(LINEARGRADIENT)
+layout(location = 2) in float gradTabIndex;
+#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+layout(location = 2) in vec2 coord;
+#elif defined(TEXTUREFILL)
+layout(location = 2) in vec2 textureCoord;
+#endif
+
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+ float matrixScale;
+ float opacity;
+ float debug;
+ float reserved3;
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
+ mat4 gradientMatrix;
+#endif
+#if defined(LINEARGRADIENT)
+ vec2 gradientStart;
+ vec2 gradientEnd;
+#elif defined(RADIALGRADIENT)
+ vec2 translationPoint;
+ vec2 focalToCenter;
+ float centerRadius;
+ float focalRadius;
+#elif defined(CONICALGRADIENT)
+ vec2 translationPoint;
+ float angle;
+#elif defined(TEXTUREFILL)
+ vec2 boundsSize;
+#else
+ vec4 color;
+#endif
+} ubuf;
+
+#define INVERSE_2PI 0.1591549430918953358
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+layout(binding = 1) uniform sampler2D gradTabTexture;
+#elif defined(TEXTUREFILL)
+layout(binding = 1) uniform sampler2D sourceTexture;
+#endif
+
+vec4 baseColor()
+{
+#if defined(LINEARGRADIENT)
+ return texture(gradTabTexture, vec2(gradTabIndex, 0.5));
+#elif defined(RADIALGRADIENT)
+ float rd = ubuf.centerRadius - ubuf.focalRadius;
+ float b = 2.0 * (rd * ubuf.focalRadius + dot(coord, ubuf.focalToCenter));
+ float fmp2_m_radius2 = -ubuf.focalToCenter.x * ubuf.focalToCenter.x - ubuf.focalToCenter.y * ubuf.focalToCenter.y + rd * rd;
+ float inverse_2_fmp2_m_radius2 = 1.0 / (2.0 * fmp2_m_radius2);
+ float det = b * b - 4.0 * fmp2_m_radius2 * ((ubuf.focalRadius * ubuf.focalRadius) - dot(coord, coord));
+ vec4 result = vec4(0.0);
+ if (det >= 0.0) {
+ float detSqrt = sqrt(det);
+ float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2);
+ if (ubuf.focalRadius + w * (ubuf.centerRadius - ubuf.focalRadius) >= 0.0)
+ result = texture(gradTabTexture, vec2(w, 0.5));
+ }
+
+ return result;
+#elif defined(CONICALGRADIENT)
+ float t;
+ if (abs(coord.y) == abs(coord.x))
+ t = (atan(-coord.y + 0.002, coord.x) + ubuf.angle) * INVERSE_2PI;
+ else
+ t = (atan(-coord.y, coord.x) + ubuf.angle) * INVERSE_2PI;
+ return texture(gradTabTexture, vec2(t - floor(t), 0.5));
+#elif defined(TEXTUREFILL)
+ return texture(sourceTexture, textureCoord);
+#else
+ return vec4(ubuf.color.rgb, 1.0) * ubuf.color.a;
+#endif
+}
+
+void main()
+{
+ float f = qt_TexCoord.z * (qt_TexCoord.x * qt_TexCoord.x - qt_TexCoord.y) // curve
+ + (1.0 - abs(qt_TexCoord.z)) * (qt_TexCoord.x - qt_TexCoord.y); // line
+
+#if defined(USE_DERIVATIVES)
+ float _ddx = dFdx(f);
+ float _ddy = dFdy(f);
+ float df = length(vec2(_ddx, _ddy));
+#else
+ // We calculate the partial derivatives for f'(x, y) based on knowing the partial derivatives
+ // for the texture coordinates (u, v).
+ // So for curves:
+ // f(x,y) = u(x, y) * u(x, y) - v(x, y)
+ // f(x,y) = p(u(x,y)) - v(x, y) where p(u) = u^2
+ // So f'(x, y) = p'(u(x, y)) * u'(x, y) - v'(x, y)
+ // (by chain rule and sum rule)
+ // f'(x, y) = 2 * u(x, y) * u'(x, y) - v'(x, y)
+ // And so:
+ // df/dx = 2 * u(x, y) * du/dx - dv/dx
+ // df/dy = 2 * u(x, y) * du/dy - dv/dy
+ //
+ // and similarly for straight lines:
+ // f(x, y) = u(x, y) - v(x, y)
+ // f'(x, y) = dudx - dvdx
+
+ float dudx = gradient.x;
+ float dvdx = gradient.y;
+ float dudy = gradient.z;
+ float dvdy = gradient.w;
+
+ // Test with analytic derivatives
+// dudx = dFdx(qt_TexCoord.x);
+// dvdx = dFdx(qt_TexCoord.y);
+// dudy = dFdy(qt_TexCoord.x);
+// dvdy = dFdy(qt_TexCoord.y);
+
+ float dfx_curve = 2.0f * qt_TexCoord.x * dudx - dvdx;
+ float dfy_curve = 2.0f * qt_TexCoord.x * dudy - dvdy;
+
+ float dfx_line = dudx - dvdx;
+ float dfy_line = dudy - dvdy;
+
+ float dfx = qt_TexCoord.z * dfx_curve + (1.0 - abs(qt_TexCoord.z)) * dfx_line;
+ float dfy = qt_TexCoord.z * dfy_curve + (1.0 - abs(qt_TexCoord.z)) * dfy_line;
+ float df = length(vec2(dfx, dfy));
+#endif
+
+ float isLine = 1.0 - abs(qt_TexCoord.z);
+ float isCurve = 1.0 - isLine;
+ float debugR = isCurve * min(1.0, 1.0 - qt_TexCoord.z);
+ float debugG = isLine;
+ float debugB = isCurve * min(1.0, 1.0 - qt_TexCoord.z * -1.0) + debugG;
+ vec3 debugColor = vec3(debugR, debugG, debugB);
+
+ // Special case: mask out concave curve in "negative space".
+ int specialCaseMask = 1 - int(qt_TexCoord.w != 0.0) * (int(qt_TexCoord.x < 0.0) + int(qt_TexCoord.x > 1.0));
+ float fillCoverage = clamp(0.5 + f / df, 0.0, 1.0) * float(specialCaseMask);
+
+ fragColor = mix(baseColor() * fillCoverage, vec4(debugColor, 1.0), ubuf.debug) * ubuf.opacity;
+}
diff --git a/src/quick/scenegraph/shaders_ng/shapecurve.vert b/src/quick/scenegraph/shaders_ng/shapecurve.vert
new file mode 100644
index 0000000000..3ff53978b8
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.vert
@@ -0,0 +1,92 @@
+#version 440
+
+layout(location = 0) in vec4 vertexCoord;
+layout(location = 1) in vec4 vertexTexCoord;
+layout(location = 2) in vec4 vertexGradient;
+layout(location = 3) in vec2 normalVector;
+
+layout(location = 0) out vec4 qt_TexCoord;
+layout(location = 1) out vec4 gradient;
+
+#if defined(LINEARGRADIENT)
+layout(location = 2) out float gradTabIndex;
+#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+layout(location = 2) out vec2 coord;
+#elif defined(TEXTUREFILL)
+layout(location = 2) out vec2 textureCoord;
+#endif
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+ float matrixScale;
+ float opacity;
+ float debug;
+ float reserved3;
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
+ mat4 gradientMatrix;
+#endif
+#if defined(LINEARGRADIENT)
+ vec2 gradientStart;
+ vec2 gradientEnd;
+#elif defined(RADIALGRADIENT)
+ vec2 translationPoint;
+ vec2 focalToCenter;
+ float centerRadius;
+ float focalRadius;
+#elif defined(CONICALGRADIENT)
+ vec2 translationPoint;
+ float angle;
+#elif defined(TEXTUREFILL)
+ vec2 boundsSize;
+#else
+ vec4 color;
+#endif
+} ubuf;
+
+#define SQRT2 1.41421356237
+
+vec4 addOffset(vec4 texCoord, vec2 offset, vec4 duvdxy)
+{
+ float dudx = duvdxy.x;
+ float dvdx = duvdxy.y;
+ float dudy = duvdxy.z;
+ float dvdy = duvdxy.w;
+ float u = offset.x * dudx + offset.y * dudy;
+ float v = offset.x * dvdx + offset.y * dvdy;
+ // special case external triangles for concave curves
+ int specialCase = int(texCoord.z > 0) * (int(offset.x != 0) + int(offset.y != 0));
+ return vec4(texCoord.x + u, texCoord.y + v, texCoord.z, float(specialCase));
+}
+
+void main()
+{
+ vec2 offset = normalVector * SQRT2/ubuf.matrixScale;
+
+ qt_TexCoord = addOffset(vertexTexCoord, offset, vertexGradient);
+
+ gradient = vertexGradient / ubuf.matrixScale;
+
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
+ vec2 gradVertexCoord = (ubuf.gradientMatrix * vertexCoord).xy;
+#endif
+#if defined(LINEARGRADIENT)
+ vec2 gradVec = ubuf.gradientEnd - ubuf.gradientStart;
+ gradTabIndex = dot(gradVec, gradVertexCoord - ubuf.gradientStart) / dot(gradVec, gradVec);
+#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+ coord = gradVertexCoord - ubuf.translationPoint;
+#elif defined(TEXTUREFILL)
+ textureCoord = vec2(gradVertexCoord.x / ubuf.boundsSize.x,
+ gradVertexCoord.y / ubuf.boundsSize.y);
+#endif
+
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.qt_Matrix[gl_ViewIndex] * (vertexCoord + vec4(offset, 0, 0));
+#else
+ gl_Position = ubuf.qt_Matrix * (vertexCoord + vec4(offset, 0, 0));
+#endif
+}
diff --git a/src/quick/scenegraph/shaders_ng/shapestroke.frag b/src/quick/scenegraph/shaders_ng/shapestroke.frag
new file mode 100644
index 0000000000..cedfa0845a
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapestroke.frag
@@ -0,0 +1,134 @@
+#version 440
+
+layout(location = 0) in vec4 P;
+layout(location = 1) in vec2 A;
+layout(location = 2) in vec2 B;
+layout(location = 3) in vec2 C;
+layout(location = 4) in vec2 HG;
+layout(location = 5) in float offset;
+
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+
+ float matrixScale;
+ float opacity;
+ float reserved2;
+ float reserved3;
+
+ vec4 strokeColor;
+
+ float strokeWidth;
+ float debug;
+ float reserved5;
+ float reserved6;
+} ubuf;
+
+float cuberoot(float x)
+{
+ return sign(x) * pow(abs(x), 1 / 3.);
+}
+
+#define PI 3.1415926538
+
+vec3 solveDepressedCubic(float p, float q)
+{
+ float D = q * q / 4. + p * p * p / 27.;
+
+ float u1 = cuberoot(-q / 2. - sqrt(D));
+ float u2 = cuberoot(-q / 2. + sqrt(D));
+ vec3 rootsD1 = vec3(u1 - p / (3. * u1), u2 - p / (3. * u2), 0);
+
+ float v = 2.*sqrt(-p / 3.);
+ float t = acos(3. * q / p / v) / 3.;
+ float k = 2. * PI / 3.;
+ vec3 rootsD2 = vec3(v * cos(t), v * cos(t - k), v * cos(t - 2. * k));
+
+ return D > 0 ? rootsD1 : rootsD2;
+}
+
+mat2 qInverse(mat2 matrix) {
+ float a = matrix[0][0], b = matrix[0][1];
+ float c = matrix[1][0], d = matrix[1][1];
+
+ float determinant = a * d - b * c;
+ float invDet = 1.0 / determinant;
+
+ mat2 inverseMatrix;
+ inverseMatrix[0][0] = d * invDet;
+ inverseMatrix[0][1] = -b * invDet;
+ inverseMatrix[1][0] = -c * invDet;
+ inverseMatrix[1][1] = a * invDet;
+
+ return inverseMatrix;
+}
+
+void main()
+{
+ vec3 s = solveDepressedCubic(HG.x, HG.y) - vec3(offset, offset, offset);
+
+ vec2 Qmin = vec2(1e10, 1e10);
+ float dmin = 1e4;
+ for (int i = 0; i < 3; i++) {
+ float t = clamp(s[i], 0., 1.);
+ vec2 Q = A * t * t + B * t + C;
+ float d = length(Q - P.xy);
+ float foundNewMin = step(d, dmin);
+ dmin = min(d, dmin);
+ Qmin = foundNewMin * Q + (1. - foundNewMin) * Qmin;
+ }
+ vec2 n = (P.xy - Qmin) / dmin;
+ vec2 Q1 = (Qmin + ubuf.strokeWidth / 2. * n);
+ vec2 Q2 = (Qmin - ubuf.strokeWidth / 2. * n);
+
+ // Converting to screen coordinates:
+#if defined(USE_DERIVATIVES)
+ mat2 T = mat2(dFdx(P.x), dFdy(P.x), dFdx(P.y), dFdy(P.y));
+ mat2 Tinv = qInverse(T);
+ vec2 Q1_s = Tinv * Q1;
+ vec2 Q2_s = Tinv * Q2;
+ vec2 P_s = Tinv * P.xy;
+ vec2 n_s = Tinv * n;
+ n_s = n_s / length(n_s);
+#else
+ vec2 Q1_s = ubuf.matrixScale * Q1;
+ vec2 Q2_s = ubuf.matrixScale * Q2;
+ vec2 P_s = ubuf.matrixScale * P.xy;
+ vec2 n_s = n;
+#endif
+
+ // Geometric solution for anti aliasing using the known distances
+ // to the edges of the path in the screen coordinate system.
+ float dist1 = dot(P_s - Q1_s, n_s);
+ float dist2 = dot(P_s - Q2_s, n_s);
+
+ // Calculate the fill coverage if the line is crossing the square cell
+ // normally (vertically or horizontally).
+ // float fillCoverageLin = clamp(0.5-dist1, 0., 1.) - clamp(0.5-dist2, 0., 1.);
+
+ // Calculate the fill coverage if the line is crossing the square cell
+ // diagonally.
+ float fillCoverageDia = clamp(step(0., -dist1) + sign(dist1) * pow(max(0., sqrt(2.) / 2. - abs(dist1)), 2.), 0., 1.) -
+ clamp(step(0., -dist2) + sign(dist2) * pow(max(0., sqrt(2.) / 2. - abs(dist2)), 2.), 0., 1.);
+
+ // Merge the normal and the diagonal solution. The merge factor is periodic
+ // in 90 degrees and 0/1 at 0 and 45 degree. The simple equation was
+ // estimated after numerical experiments.
+ // float mergeFactor = 2 * abs(n_s.x) * abs(n_s.y);
+ // float fillCoverage = mergeFactor * fillCoverageDia + (1-mergeFactor) * fillCoverageLin;
+
+ // It seems to be sufficient to use the equation for the diagonal.
+ float fillCoverage = fillCoverageDia;
+
+ // The center line is sometimes not filled because of numerical issues. This fixes this.
+ float centerline = step(ubuf.strokeWidth * 0.01, dmin);
+ fillCoverage = fillCoverage * centerline + min(1., ubuf.strokeWidth * ubuf.matrixScale) * (1. - centerline);
+
+ fragColor = vec4(ubuf.strokeColor.rgb, 1.0) *ubuf.strokeColor.a * fillCoverage * ubuf.opacity
+ + ubuf.debug * vec4(0.0, 0.5, 1.0, 1.0) * (1.0 - fillCoverage) * ubuf.opacity;
+}
diff --git a/src/quick/scenegraph/shaders_ng/shapestroke.vert b/src/quick/scenegraph/shaders_ng/shapestroke.vert
new file mode 100644
index 0000000000..e358e059eb
--- /dev/null
+++ b/src/quick/scenegraph/shaders_ng/shapestroke.vert
@@ -0,0 +1,82 @@
+#version 440
+
+layout(location = 0) in vec4 vertexCoord;
+layout(location = 1) in vec2 inA;
+layout(location = 2) in vec2 inB;
+layout(location = 3) in vec2 inC;
+layout(location = 4) in vec2 normalVector;
+
+layout(location = 0) out vec4 P;
+layout(location = 1) out vec2 A;
+layout(location = 2) out vec2 B;
+layout(location = 3) out vec2 C;
+layout(location = 4) out vec2 HG;
+layout(location = 5) out float offset;
+
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+
+ float matrixScale;
+ float opacity;
+ float reserved2;
+ float reserved3;
+
+ vec4 strokeColor;
+
+ float strokeWidth;
+ float debug;
+ float reserved5;
+ float reserved6;
+} ubuf;
+
+#define SQRT2 1.41421356237
+
+float qdot(vec2 a, vec2 b)
+{
+ return a.x * b.x + a.y * b.y;
+}
+
+void main()
+{
+ P = vertexCoord + vec4(normalVector, 0.0, 0.0) * SQRT2/ubuf.matrixScale;
+
+ A = inA;
+ B = inB;
+ C = inC;
+
+ // Find the parameters H, G for the depressed cubic
+ // t^2+H*t+G=0
+ // that results from the equation
+ // Q'(s).(p-Q(s)) = 0
+ // The last parameter is the static offset between s and t:
+ // s = t - b/(3a)
+ // use it to get back the parameter t
+
+ // this is a constant for the curve
+ float a = -2. * qdot(A, A);
+ // this is a constant for the curve
+ float b = -3. * qdot(A, B);
+ //this is linear in p so it can be put into the shader with vertex data
+ float c = 2. * qdot(A, P.xy) - qdot(B, B) - 2. * qdot(A, C);
+ //this is linear in p so it can be put into the shader with vertex data
+ float d = qdot(B, P.xy) - qdot(B, C);
+ // convert to depressed cubic.
+ // both functions are linear in c and d and thus linear in p
+ float H = (3. * a * c - b * b) / (3. * a * a);
+ float G = (2. * b * b * b - 9. * a * b * c + 27. * a * a * d) / (27. * a * a * a);
+ HG = vec2(H, G);
+ offset = b/(3*a);
+
+
+
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.qt_Matrix[gl_ViewIndex] * P;
+#else
+ gl_Position = ubuf.qt_Matrix * P;
+#endif
+}
diff --git a/src/quick/scenegraph/shaders_ng/smoothcolor.frag b/src/quick/scenegraph/shaders_ng/smoothcolor.frag
index ede283be0c..314a387922 100644
--- a/src/quick/scenegraph/shaders_ng/smoothcolor.frag
+++ b/src/quick/scenegraph/shaders_ng/smoothcolor.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec4 color;
diff --git a/src/quick/scenegraph/shaders_ng/smoothcolor.vert b/src/quick/scenegraph/shaders_ng/smoothcolor.vert
index 03a3ff8975..6ec10a7e70 100644
--- a/src/quick/scenegraph/shaders_ng/smoothcolor.vert
+++ b/src/quick/scenegraph/shaders_ng/smoothcolor.vert
@@ -7,22 +7,32 @@ layout(location = 2) in vec4 vertexOffset;
layout(location = 0) out vec4 color;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec2 pixelSize;
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- vec4 pos = ubuf.matrix * vertex;
+#if QSHADER_VIEW_COUNT >= 2
+ vec4 pos = matrix[gl_ViewIndex] * vertex;
+ vec4 m0 = matrix[gl_ViewIndex][0];
+ vec4 m1 = matrix[gl_ViewIndex][1];
+#else
+ vec4 pos = matrix * vertex;
+ vec4 m0 = matrix[0];
+ vec4 m1 = matrix[1];
+#endif
gl_Position = pos;
if (vertexOffset.x != 0.) {
- vec4 delta = ubuf.matrix[0] * vertexOffset.x;
+ vec4 delta = m0 * vertexOffset.x;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -34,9 +44,9 @@ void main()
}
if (vertexOffset.y != 0.) {
- vec4 delta = ubuf.matrix[1] * vertexOffset.y;
+ vec4 delta = m1 * vertexOffset.y;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -47,5 +57,5 @@ void main()
gl_Position += scale * delta;
}
- color = vertexColor * ubuf.opacity;
+ color = vertexColor * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/smoothtexture.frag b/src/quick/scenegraph/shaders_ng/smoothtexture.frag
index b06764ad95..a7ddc57535 100644
--- a/src/quick/scenegraph/shaders_ng/smoothtexture.frag
+++ b/src/quick/scenegraph/shaders_ng/smoothtexture.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 texCoord;
diff --git a/src/quick/scenegraph/shaders_ng/smoothtexture.vert b/src/quick/scenegraph/shaders_ng/smoothtexture.vert
index 965c837852..4edde5472c 100644
--- a/src/quick/scenegraph/shaders_ng/smoothtexture.vert
+++ b/src/quick/scenegraph/shaders_ng/smoothtexture.vert
@@ -9,23 +9,33 @@ layout(location = 0) out vec2 texCoord;
layout(location = 1) out float vertexOpacity;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float opacity;
vec2 pixelSize;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- vec4 pos = ubuf.qt_Matrix * vertex;
+#if QSHADER_VIEW_COUNT >= 2
+ vec4 pos = qt_Matrix[gl_ViewIndex] * vertex;
+ vec4 m0 = qt_Matrix[gl_ViewIndex][0];
+ vec4 m1 = qt_Matrix[gl_ViewIndex][1];
+#else
+ vec4 pos = qt_Matrix * vertex;
+ vec4 m0 = qt_Matrix[0];
+ vec4 m1 = qt_Matrix[1];
+#endif
gl_Position = pos;
texCoord = multiTexCoord;
if (vertexOffset.x != 0.) {
- vec4 delta = ubuf.qt_Matrix[0] * vertexOffset.x;
+ vec4 delta = m0 * vertexOffset.x;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -38,9 +48,9 @@ void main()
}
if (vertexOffset.y != 0.) {
- vec4 delta = ubuf.qt_Matrix[1] * vertexOffset.y;
+ vec4 delta = m1 * vertexOffset.y;
vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
- vec2 ndir = .5 * ubuf.pixelSize * normalize(dir / ubuf.pixelSize);
+ vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
float numerator = dot(dir, ndir * pos.w * pos.w);
float scale = 0.0;
@@ -57,5 +67,5 @@ void main()
if (onEdge && outerEdge)
vertexOpacity = 0.;
else
- vertexOpacity = ubuf.opacity;
+ vertexOpacity = opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/sprite.frag b/src/quick/scenegraph/shaders_ng/sprite.frag
index 338f5e957e..387be43bb1 100644
--- a/src/quick/scenegraph/shaders_ng/sprite.frag
+++ b/src/quick/scenegraph/shaders_ng/sprite.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec4 fTexS;
@@ -8,15 +11,19 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D tex;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 animPos;
vec3 animData;
float opacity;
-} ubuf;
+};
void main()
{
fragColor = mix(texture(tex, fTexS.xy),
texture(tex, fTexS.zw),
- progress) * ubuf.opacity;
+ progress) * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/sprite.vert b/src/quick/scenegraph/shaders_ng/sprite.vert
index b76e2b206f..b693e31334 100644
--- a/src/quick/scenegraph/shaders_ng/sprite.vert
+++ b/src/quick/scenegraph/shaders_ng/sprite.vert
@@ -7,23 +7,29 @@ layout(location = 0) out vec4 fTexS;
layout(location = 1) out float progress;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
vec4 animPos; // x,y, x,y (two frames for interpolation)
vec3 animData; // w,h(premultiplied of anim), interpolation progress
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- progress = ubuf.animData.z;
+ progress = animData.z;
// Calculate frame location in texture
- fTexS.xy = ubuf.animPos.xy + vTex.xy * ubuf.animData.xy;
+ fTexS.xy = animPos.xy + vTex.xy * animData.xy;
// Next frame is also passed, for interpolation
- fTexS.zw = ubuf.animPos.zw + vTex.xy * ubuf.animData.xy;
+ fTexS.zw = animPos.zw + vTex.xy * animData.xy;
- gl_Position = ubuf.matrix * vec4(vPos.x, vPos.y, 0, 1);
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vec4(vPos.x, vPos.y, 0, 1);
+#else
+ gl_Position = matrix * vec4(vPos.x, vPos.y, 0, 1);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/stencilclip.frag b/src/quick/scenegraph/shaders_ng/stencilclip.frag
index 3f6222389d..ec4d3a05b1 100644
--- a/src/quick/scenegraph/shaders_ng/stencilclip.frag
+++ b/src/quick/scenegraph/shaders_ng/stencilclip.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) out vec4 fragColor;
diff --git a/src/quick/scenegraph/shaders_ng/styledtext.frag b/src/quick/scenegraph/shaders_ng/styledtext.frag
index 2e380dfeae..e55e10b05e 100644
--- a/src/quick/scenegraph/shaders_ng/styledtext.frag
+++ b/src/quick/scenegraph/shaders_ng/styledtext.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -9,19 +12,23 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
float glyph = texture(_qt_texture, sampleCoord).r;
float style = clamp(texture(_qt_texture, shiftedSampleCoord).r - glyph,
0.0, 1.0);
- fragColor = style * ubuf.styleColor + glyph * ubuf.color;
+ fragColor = style * styleColor + glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/styledtext.vert b/src/quick/scenegraph/shaders_ng/styledtext.vert
index 271dae8d8a..5e0a8fa5c9 100644
--- a/src/quick/scenegraph/shaders_ng/styledtext.vert
+++ b/src/quick/scenegraph/shaders_ng/styledtext.vert
@@ -8,21 +8,27 @@ layout(location = 1) out vec2 shiftedSampleCoord;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- shiftedSampleCoord = (tCoord - ubuf.shift) * ubuf.textureScale;
- vec4 xformed = ubuf.modelViewMatrix * vCoord;
- gl_Position = ubuf.projectionMatrix * vec4(floor(xformed.xyz * ubuf.dpr + 0.5) / ubuf.dpr, xformed.w);
+ sampleCoord = tCoord * textureScale;
+ shiftedSampleCoord = (tCoord - shift) * textureScale;
+ vec4 xformed = modelViewMatrix * vCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = projectionMatrix[gl_ViewIndex] * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#else
+ gl_Position = projectionMatrix * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/styledtext_a.frag b/src/quick/scenegraph/shaders_ng/styledtext_a.frag
index 62e162c851..6a684c2abb 100644
--- a/src/quick/scenegraph/shaders_ng/styledtext_a.frag
+++ b/src/quick/scenegraph/shaders_ng/styledtext_a.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -9,19 +12,23 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
+ vec4 color;
// the above must stay compatible with textmask/8bittextmask
vec4 styleColor;
vec2 shift;
-} ubuf;
+};
void main()
{
float glyph = texture(_qt_texture, sampleCoord).a; // take .a instead of .r
float style = clamp(texture(_qt_texture, shiftedSampleCoord).a - glyph,
0.0, 1.0);
- fragColor = style * ubuf.styleColor + glyph * ubuf.color;
+ fragColor = style * styleColor + glyph * color;
}
diff --git a/src/quick/scenegraph/shaders_ng/textmask.frag b/src/quick/scenegraph/shaders_ng/textmask.frag
index ed8da4cd30..0f9d6b4567 100644
--- a/src/quick/scenegraph/shaders_ng/textmask.frag
+++ b/src/quick/scenegraph/shaders_ng/textmask.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 sampleCoord;
@@ -7,14 +10,18 @@ layout(binding = 1) uniform sampler2D _qt_texture;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
+ vec4 color;
+};
void main()
{
vec4 glyph = texture(_qt_texture, sampleCoord);
- fragColor = vec4(glyph.rgb * ubuf.color.a, glyph.a);
+ fragColor = vec4(glyph.rgb * color.a, glyph.a);
}
diff --git a/src/quick/scenegraph/shaders_ng/textmask.vert b/src/quick/scenegraph/shaders_ng/textmask.vert
index e0b3c01bce..41fffa282f 100644
--- a/src/quick/scenegraph/shaders_ng/textmask.vert
+++ b/src/quick/scenegraph/shaders_ng/textmask.vert
@@ -7,17 +7,23 @@ layout(location = 0) out vec2 sampleCoord;
layout(std140, binding = 0) uniform buf {
mat4 modelViewMatrix;
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 projectionMatrix[QSHADER_VIEW_COUNT];
+#else
mat4 projectionMatrix;
- vec4 color;
+#endif
vec2 textureScale;
float dpr;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+ vec4 color;
+};
void main()
{
- sampleCoord = tCoord * ubuf.textureScale;
- vec4 xformed = ubuf.modelViewMatrix * vCoord;
- gl_Position = ubuf.projectionMatrix * vec4(floor(xformed.xyz * ubuf.dpr + 0.5) / ubuf.dpr, xformed.w);
+ sampleCoord = tCoord * textureScale;
+ vec4 xformed = modelViewMatrix * vCoord;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = projectionMatrix[gl_ViewIndex] * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#else
+ gl_Position = projectionMatrix * vec4(floor(xformed.xyz * dpr + 0.5) / dpr, xformed.w);
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/texture.frag b/src/quick/scenegraph/shaders_ng/texture.frag
index bd22f817e0..a591e72ccf 100644
--- a/src/quick/scenegraph/shaders_ng/texture.frag
+++ b/src/quick/scenegraph/shaders_ng/texture.frag
@@ -1,16 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 qt_TexCoord;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float opacity;
-} ubuf;
+};
layout(binding = 1) uniform sampler2D qt_Texture;
void main()
{
- fragColor = texture(qt_Texture, qt_TexCoord) * ubuf.opacity;
+ fragColor = texture(qt_Texture, qt_TexCoord) * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/texture.vert b/src/quick/scenegraph/shaders_ng/texture.vert
index 537852d2bd..ae8a27f4a6 100644
--- a/src/quick/scenegraph/shaders_ng/texture.vert
+++ b/src/quick/scenegraph/shaders_ng/texture.vert
@@ -6,14 +6,20 @@ layout(location = 1) in vec2 qt_VertexTexCoord;
layout(location = 0) out vec2 qt_TexCoord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
mat4 qt_Matrix;
+#endif
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
qt_TexCoord = qt_VertexTexCoord;
- gl_Position = ubuf.qt_Matrix * qt_VertexPosition;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = qt_Matrix[gl_ViewIndex] * qt_VertexPosition;
+#else
+ gl_Position = qt_Matrix * qt_VertexPosition;
+#endif
}
diff --git a/src/quick/scenegraph/shaders_ng/vertexcolor.frag b/src/quick/scenegraph/shaders_ng/vertexcolor.frag
index ede283be0c..314a387922 100644
--- a/src/quick/scenegraph/shaders_ng/vertexcolor.frag
+++ b/src/quick/scenegraph/shaders_ng/vertexcolor.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec4 color;
diff --git a/src/quick/scenegraph/shaders_ng/vertexcolor.vert b/src/quick/scenegraph/shaders_ng/vertexcolor.vert
index bfb9a95073..792f663b86 100644
--- a/src/quick/scenegraph/shaders_ng/vertexcolor.vert
+++ b/src/quick/scenegraph/shaders_ng/vertexcolor.vert
@@ -6,14 +6,20 @@ layout(location = 1) in vec4 vertexColor;
layout(location = 0) out vec4 color;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
float opacity;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
+};
void main()
{
- gl_Position = ubuf.matrix * vertexCoord;
- color = vertexColor * ubuf.opacity;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = matrix[gl_ViewIndex] * vertexCoord;
+#else
+ gl_Position = matrix * vertexCoord;
+#endif
+ color = vertexColor * opacity;
}
diff --git a/src/quick/scenegraph/shaders_ng/visualization.frag b/src/quick/scenegraph/shaders_ng/visualization.frag
index 29f718fe5d..378afc2088 100644
--- a/src/quick/scenegraph/shaders_ng/visualization.frag
+++ b/src/quick/scenegraph/shaders_ng/visualization.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
#version 440
layout(location = 0) in vec2 pos;
diff --git a/src/quick/scenegraph/util/qquadpath.cpp b/src/quick/scenegraph/util/qquadpath.cpp
new file mode 100644
index 0000000000..9b5cbcb0ae
--- /dev/null
+++ b/src/quick/scenegraph/util/qquadpath.cpp
@@ -0,0 +1,950 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquadpath_p.h"
+#include <QtGui/private/qbezier_p.h>
+#include <QtMath>
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QVarLengthArray>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcSGCurveProcessor);
+
+static qreal qt_scoreQuadratic(const QBezier &b, QPointF qcp)
+{
+ static bool init = false;
+ const int numSteps = 21;
+ Q_STATIC_ASSERT(numSteps % 2 == 1); // numTries must be odd
+ static qreal t2s[numSteps];
+ static qreal tmts[numSteps];
+ if (!init) {
+ // Precompute bezier factors
+ qreal t = 0.20;
+ const qreal step = (1 - (2 * t)) / (numSteps - 1);
+ for (int i = 0; i < numSteps; i++) {
+ t2s[i] = t * t;
+ tmts[i] = 2 * t * (1 - t);
+ t += step;
+ }
+ init = true;
+ }
+
+ const QPointF midPoint = b.midPoint();
+ auto distForIndex = [&](int i) -> qreal {
+ QPointF qp = (t2s[numSteps - 1 - i] * b.pt1()) + (tmts[i] * qcp) + (t2s[i] * b.pt4());
+ QPointF d = midPoint - qp;
+ return QPointF::dotProduct(d, d);
+ };
+
+ const int halfSteps = (numSteps - 1) / 2;
+ bool foundIt = false;
+ const qreal centerDist = distForIndex(halfSteps);
+ qreal minDist = centerDist;
+ // Search for the minimum in right half
+ for (int i = 0; i < halfSteps; i++) {
+ qreal tDist = distForIndex(halfSteps + 1 + i);
+ if (tDist < minDist) {
+ minDist = tDist;
+ } else {
+ foundIt = (i > 0);
+ break;
+ }
+ }
+ if (!foundIt) {
+ // Search in left half
+ minDist = centerDist;
+ for (int i = 0; i < halfSteps; i++) {
+ qreal tDist = distForIndex(halfSteps - 1 - i);
+ if (tDist < minDist) {
+ minDist = tDist;
+ } else {
+ foundIt = (i > 0);
+ break;
+ }
+ }
+ }
+ return foundIt ? minDist : centerDist;
+}
+
+static QPointF qt_quadraticForCubic(const QBezier &b)
+{
+ const QLineF st = b.startTangent();
+ const QLineF et = b.endTangent();
+ const QPointF midPoint = b.midPoint();
+ bool valid = true;
+ QPointF quadControlPoint;
+ if (st.intersects(et, &quadControlPoint) == QLineF::NoIntersection) {
+ valid = false;
+ } else {
+ // Check if intersection is on wrong side
+ const QPointF bl = b.pt4() - b.pt1();
+ const QPointF ml = midPoint - b.pt1();
+ const QPointF ql = quadControlPoint - b.pt1();
+ qreal cx1 = (ml.x() * bl.y()) - (ml.y() * bl.x());
+ qreal cx2 = (ql.x() * bl.y()) - (ql.y() * bl.x());
+ valid = (std::signbit(cx1) == std::signbit(cx2));
+ }
+ return valid ? quadControlPoint : midPoint;
+}
+
+static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
+{
+ auto isValidRoot = [](qreal r) {
+ return qIsFinite(r) && (r > 0) && (!qFuzzyIsNull(float(r))) && (r < 1)
+ && (!qFuzzyIsNull(float(r - 1)));
+ };
+
+ // normalize so pt1.x,pt1.y,pt4.y == 0
+ QTransform xf;
+ const QLineF l(orig.pt1(), orig.pt4());
+ xf.rotate(l.angle());
+ xf.translate(-orig.pt1().x(), -orig.pt1().y());
+ const QBezier n = orig.mapBy(xf);
+
+ const qreal x2 = n.pt2().x();
+ const qreal x3 = n.pt3().x();
+ const qreal x4 = n.pt4().x();
+ const qreal y2 = n.pt2().y();
+ const qreal y3 = n.pt3().y();
+
+ const qreal p = x3 * y2;
+ const qreal q = x4 * y2;
+ const qreal r = x2 * y3;
+ const qreal s = x4 * y3;
+
+ const qreal a = 18 * ((-3 * p) + (2 * q) + (3 * r) - s);
+ if (qFuzzyIsNull(float(a))) {
+ if (std::signbit(y2) != std::signbit(y3) && qFuzzyCompare(float(x4 - x3), float(x2))) {
+ tpoints[0] = 0.5; // approx
+ return 1;
+ } else if (!a) {
+ return 0;
+ }
+ }
+ const qreal b = 18 * (((3 * p) - q) - (3 * r));
+ const qreal c = 18 * (r - p);
+ const qreal rad = (b * b) - (4 * a * c);
+ if (rad < 0)
+ return 0;
+ const qreal sqr = qSqrt(rad);
+ const qreal root1 = (-b + sqr) / (2 * a);
+ const qreal root2 = (-b - sqr) / (2 * a);
+
+ int res = 0;
+ if (isValidRoot(root1))
+ tpoints[res++] = root1;
+ if (root2 != root1 && isValidRoot(root2))
+ tpoints[res++] = root2;
+
+ if (res == 2 && tpoints[0] > tpoints[1])
+ qSwap(tpoints[0], tpoints[1]);
+
+ return res;
+}
+
+static void qt_addToQuadratics(const QBezier &b, QPolygonF *p, int maxSplits, qreal maxDiff)
+{
+ QPointF qcp = qt_quadraticForCubic(b);
+ if (maxSplits <= 0 || qt_scoreQuadratic(b, qcp) < maxDiff) {
+ p->append(qcp);
+ p->append(b.pt4());
+ } else {
+ QBezier rhs = b;
+ QBezier lhs;
+ rhs.parameterSplitLeft(0.5, &lhs);
+ qt_addToQuadratics(lhs, p, maxSplits - 1, maxDiff);
+ qt_addToQuadratics(rhs, p, maxSplits - 1, maxDiff);
+ }
+}
+
+static void qt_toQuadratics(const QBezier &b, QPolygonF *out, qreal errorLimit = 0.01)
+{
+ out->resize(0);
+ out->append(b.pt1());
+
+ {
+ // Shortcut if the cubic is really a quadratic
+ const qreal f = 3.0 / 2.0;
+ const QPointF c1 = b.pt1() + f * (b.pt2() - b.pt1());
+ const QPointF c2 = b.pt4() + f * (b.pt3() - b.pt4());
+ if (c1 == c2) {
+ out->append(c1);
+ out->append(b.pt4());
+ return;
+ }
+ }
+
+ const QRectF cpr = b.bounds();
+ const QPointF dim = cpr.bottomRight() - cpr.topLeft();
+ qreal maxDiff = QPointF::dotProduct(dim, dim) * errorLimit * errorLimit; // maxdistance^2
+
+ qreal infPoints[2];
+ int numInfPoints = qt_getInflectionPoints(b, infPoints);
+ const int maxSubSplits = numInfPoints > 0 ? 2 : 3;
+ qreal t0 = 0;
+ // number of main segments == #inflectionpoints + 1
+ for (int i = 0; i < numInfPoints + 1; i++) {
+ qreal t1 = (i < numInfPoints) ? infPoints[i] : 1;
+ QBezier segment = b.bezierOnInterval(t0, t1);
+ qt_addToQuadratics(segment, out, maxSubSplits, maxDiff);
+ t0 = t1;
+ }
+}
+
+QVector2D QQuadPath::Element::pointAtFraction(float t) const
+{
+ if (isLine()) {
+ return sp + t * (ep - sp);
+ } else {
+ const float r = 1 - t;
+ return (r * r * sp) + (2 * t * r * cp) + (t * t * ep);
+ }
+}
+
+QQuadPath::Element QQuadPath::Element::segmentFromTo(float t0, float t1) const
+{
+ if (t0 <= 0 && t1 >= 1)
+ return *this;
+
+ Element part;
+ part.sp = pointAtFraction(t0);
+ part.ep = pointAtFraction(t1);
+
+ if (isLine()) {
+ part.cp = 0.5f * (part.sp + part.ep);
+ part.m_isLine = true;
+ } else {
+ // Split curve right at t0, yields { t0, rcp, endPoint } quad segment
+ const QVector2D rcp = (1 - t0) * controlPoint() + t0 * endPoint();
+ // Split that left at t1, yields { t0, lcp, t1 } quad segment
+ float segmentT = (t1 - t0) / (1 - t0);
+ part.cp = (1 - segmentT) * part.sp + segmentT * rcp;
+ }
+ return part;
+}
+
+QQuadPath::Element QQuadPath::Element::reversed() const {
+ Element swappedElement;
+ swappedElement.ep = sp;
+ swappedElement.cp = cp;
+ swappedElement.sp = ep;
+ swappedElement.m_isLine = m_isLine;
+ return swappedElement;
+}
+
+float QQuadPath::Element::extent() const
+{
+ // TBD: cache this value if we start using it a lot
+ QVector2D min(qMin(sp.x(), ep.x()), qMin(sp.y(), ep.y()));
+ QVector2D max(qMax(sp.x(), ep.x()), qMax(sp.y(), ep.y()));
+ if (!isLine()) {
+ min = QVector2D(qMin(min.x(), cp.x()), qMin(min.y(), cp.y()));
+ max = QVector2D(qMax(max.x(), cp.x()), qMax(max.y(), cp.y()));
+ }
+ return (max - min).length();
+}
+
+// Returns the number of intersections between element and a horizontal line at y.
+// The t values of max 2 intersection(s) are stored in the fractions array
+int QQuadPath::Element::intersectionsAtY(float y, float *fractions, bool swapXY) const
+{
+ Q_ASSERT(!isLine());
+
+ auto getY = [=](QVector2D p) -> float { return swapXY ? -p.x() : p.y(); };
+
+ const float y0 = getY(startPoint()) - y;
+ const float y1 = getY(controlPoint()) - y;
+ const float y2 = getY(endPoint()) - y;
+
+ int numRoots = 0;
+ const float a = y0 - (2 * y1) + y2;
+ if (a) {
+ const float b = (y1 * y1) - (y0 * y2);
+ if (b >= 0) {
+ const float sqr = qSqrt(b);
+ const float root1 = -(-y0 + y1 + sqr) / a;
+ if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
+ fractions[numRoots++] = root1;
+ const float root2 = (y0 - y1 + sqr) / a;
+ if (qIsFinite(root2) && root2 != root1 && root2 >= 0 && root2 <= 1)
+ fractions[numRoots++] = root2;
+ }
+ } else if (y1 != y2) {
+ const float root1 = (y2 - (2 * y1)) / (2 * (y2 - y1));
+ if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
+ fractions[numRoots++] = root1;
+ }
+
+ return numRoots;
+}
+
+static float crossProduct(const QVector2D &sp, const QVector2D &p, const QVector2D &ep)
+{
+ QVector2D v1 = ep - sp;
+ QVector2D v2 = p - sp;
+ return (v2.x() * v1.y()) - (v2.y() * v1.x());
+}
+
+bool QQuadPath::isPointOnLeft(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ // Use cross product to compare directions of base vector and vector from start to p
+ return crossProduct(sp, p, ep) >= 0.0f;
+}
+
+bool QQuadPath::isPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ return qFuzzyIsNull(crossProduct(sp, p, ep));
+}
+
+// Assumes sp != ep
+bool QQuadPath::isPointNearLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ // epsilon is max length of p-to-baseline relative to length of baseline. So 0.01 means that
+ // the distance from p to the baseline must be less than 1% of the length of the baseline.
+ constexpr float epsilon = 0.01f;
+ QVector2D bv = ep - sp;
+ float bl2 = QVector2D::dotProduct(bv, bv);
+ float t = QVector2D::dotProduct(p - sp, bv) / bl2;
+ QVector2D pv = p - (sp + t * bv);
+ return (QVector2D::dotProduct(pv, pv) / bl2) < (epsilon * epsilon);
+}
+
+QVector2D QQuadPath::closestPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
+{
+ QVector2D line = ep - sp;
+ float t = QVector2D::dotProduct(p - sp, line) / QVector2D::dotProduct(line, line);
+ return sp + qBound(0.0f, t, 1.0f) * line;
+}
+
+// NOTE: it is assumed that subpaths are closed
+bool QQuadPath::contains(const QVector2D &point) const
+{
+ return contains(point, 0, elementCount() - 1);
+}
+
+bool QQuadPath::contains(const QVector2D &point, int fromIndex, int toIndex) const
+{
+ // if (!controlPointRect().contains(pt) : good opt when we add cpr caching
+ // return false;
+
+ int winding_number = 0;
+ for (int ei = fromIndex; ei <= toIndex; ei++) {
+ const Element &e = m_elements.at(ei);
+ int dir = 1;
+ float y1 = e.startPoint().y();
+ float y2 = e.endPoint().y();
+ if (y2 < y1) {
+ qSwap(y1, y2);
+ dir = -1;
+ }
+ if (e.m_isLine) {
+ if (point.y() < y1 || point.y() >= y2 || y1 == y2)
+ continue;
+ const float t = (point.y() - e.startPoint().y()) / (e.endPoint().y() - e.startPoint().y());
+ const float x = e.startPoint().x() + t * (e.endPoint().x() - e.startPoint().x());
+ if (x <= point.x())
+ winding_number += dir;
+ } else {
+ y1 = qMin(y1, e.controlPoint().y());
+ y2 = qMax(y2, e.controlPoint().y());
+ if (point.y() < y1 || point.y() >= y2)
+ continue;
+ float ts[2];
+ const int numRoots = e.intersectionsAtY(point.y(), ts);
+ // Count if there is exactly one intersection to the left
+ bool oneHit = false;
+ float tForHit = -1;
+ for (int i = 0; i < numRoots; i++) {
+ if (e.pointAtFraction(ts[i]).x() <= point.x()) {
+ oneHit = !oneHit;
+ tForHit = ts[i];
+ }
+ }
+ if (oneHit) {
+ dir = e.tangentAtFraction(tForHit).y() < 0 ? -1 : 1;
+ winding_number += dir;
+ }
+ }
+ };
+
+ return (fillRule() == Qt::WindingFill ? (winding_number != 0) : ((winding_number % 2) != 0));
+}
+
+// similar as contains. But we treat the element with the index elementIdx in a special way
+// that should be numerically more stable. The result is a contains for a point on the left
+// and for the right side of the element.
+QQuadPath::Element::FillSide QQuadPath::fillSideOf(int elementIdx, float elementT) const
+{
+ constexpr float toleranceT = 1e-3f;
+ const QVector2D point = m_elements.at(elementIdx).pointAtFraction(elementT);
+ const QVector2D tangent = m_elements.at(elementIdx).tangentAtFraction(elementT);
+
+ const bool swapXY = qAbs(tangent.x()) > qAbs(tangent.y());
+ auto getX = [=](QVector2D p) -> float { return swapXY ? p.y() : p.x(); };
+ auto getY = [=](QVector2D p) -> float { return swapXY ? -p.x() : p.y(); };
+
+ int winding_number = 0;
+ for (int i = 0; i < elementCount(); i++) {
+ const Element &e = m_elements.at(i);
+ int dir = 1;
+ float y1 = getY(e.startPoint());
+ float y2 = getY(e.endPoint());
+ if (y2 < y1) {
+ qSwap(y1, y2);
+ dir = -1;
+ }
+ if (e.m_isLine) {
+ if (getY(point) < y1 || getY(point) >= y2 || y1 == y2)
+ continue;
+ const float t = (getY(point) - getY(e.startPoint())) / (getY(e.endPoint()) - getY(e.startPoint()));
+ const float x = getX(e.startPoint()) + t * (getX(e.endPoint()) - getX(e.startPoint()));
+ if (x <= getX(point) && (i != elementIdx || qAbs(t - elementT) > toleranceT))
+ winding_number += dir;
+ } else {
+ y1 = qMin(y1, getY(e.controlPoint()));
+ y2 = qMax(y2, getY(e.controlPoint()));
+ if (getY(point) < y1 || getY(point) >= y2)
+ continue;
+ float ts[2];
+ const int numRoots = e.intersectionsAtY(getY(point), ts, swapXY);
+ // Count if there is exactly one intersection to the left
+ bool oneHit = false;
+ float tForHit = -1;
+ for (int j = 0; j < numRoots; j++) {
+ const float x = getX(e.pointAtFraction(ts[j]));
+ if (x <= getX(point) && (i != elementIdx || qAbs(ts[j] - elementT) > toleranceT)) {
+ oneHit = !oneHit;
+ tForHit = ts[j];
+ }
+ }
+ if (oneHit) {
+ dir = getY(e.tangentAtFraction(tForHit)) < 0 ? -1 : 1;
+ winding_number += dir;
+ }
+ }
+ };
+
+ int left_winding_number = winding_number;
+ int right_winding_number = winding_number;
+
+ int dir = getY(tangent) < 0 ? -1 : 1;
+
+ if (dir > 0)
+ left_winding_number += dir;
+ else
+ right_winding_number += dir;
+
+ bool leftInside = (fillRule() == Qt::WindingFill ? (left_winding_number != 0) : ((left_winding_number % 2) != 0));
+ bool rightInside = (fillRule() == Qt::WindingFill ? (right_winding_number != 0) : ((right_winding_number % 2) != 0));
+
+ if (leftInside && rightInside)
+ return QQuadPath::Element::FillSideBoth;
+ else if (leftInside)
+ return QQuadPath::Element::FillSideLeft;
+ else if (rightInside)
+ return QQuadPath::Element::FillSideRight;
+ else
+ return QQuadPath::Element::FillSideUndetermined; //should not happen except for numerical error.
+}
+
+void QQuadPath::addElement(const QVector2D &control, const QVector2D &endPoint, bool isLine)
+{
+ if (qFuzzyCompare(m_currentPoint, endPoint))
+ return; // 0 length element, skip
+
+ isLine = isLine || isPointNearLine(control, m_currentPoint, endPoint); // Turn flat quad into line
+
+ m_elements.resize(m_elements.size() + 1);
+ Element &elem = m_elements.last();
+ elem.sp = m_currentPoint;
+ elem.cp = isLine ? (0.5f * (m_currentPoint + endPoint)) : control;
+ elem.ep = endPoint;
+ elem.m_isLine = isLine;
+ elem.m_isSubpathStart = m_subPathToStart;
+ m_subPathToStart = false;
+ m_currentPoint = endPoint;
+}
+
+void QQuadPath::addElement(const Element &e)
+{
+ m_subPathToStart = false;
+ m_currentPoint = e.endPoint();
+ m_elements.append(e);
+}
+
+#if !defined(QQUADPATH_CONVEX_CHECK_ERROR_MARGIN)
+# define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN (1.0f / 32.0f)
+#endif
+
+QQuadPath::Element::CurvatureFlags QQuadPath::coordinateOrderOfElement(const QQuadPath::Element &element) const
+{
+ QVector2D baseLine = element.endPoint() - element.startPoint();
+ QVector2D midPoint = element.midPoint();
+ // At the midpoint, the tangent of a quad is parallel to the baseline
+ QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
+ float delta = qMin(element.extent() / 100, QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN);
+ QVector2D justRightOfMid = midPoint + (normal * delta);
+ bool pathContainsPoint = contains(justRightOfMid);
+ return pathContainsPoint ? Element::FillOnRight : Element::CurvatureFlags(0);
+}
+
+QQuadPath QQuadPath::fromPainterPath(const QPainterPath &path, PathHints hints)
+{
+ QQuadPath res;
+ res.reserve(path.elementCount());
+ res.setFillRule(path.fillRule());
+
+ const bool isQuadratic = hints & PathQuadratic;
+
+ QPolygonF quads;
+ QPointF sp;
+ for (int i = 0; i < path.elementCount(); ++i) {
+ QPainterPath::Element element = path.elementAt(i);
+
+ QPointF ep(element);
+ switch (element.type) {
+ case QPainterPath::MoveToElement:
+ res.moveTo(QVector2D(ep));
+ break;
+ case QPainterPath::LineToElement:
+ res.lineTo(QVector2D(ep));
+ break;
+ case QPainterPath::CurveToElement: {
+ QPointF cp1 = ep;
+ QPointF cp2(path.elementAt(++i));
+ ep = path.elementAt(++i);
+ if (isQuadratic) {
+ const qreal f = 3.0 / 2.0;
+ const QPointF cp = sp + f * (cp1 - sp);
+ res.quadTo(QVector2D(cp), QVector2D(ep));
+ } else {
+ QBezier b = QBezier::fromPoints(sp, cp1, cp2, ep);
+ qt_toQuadratics(b, &quads);
+ for (int i = 1; i < quads.size(); i += 2) {
+ QVector2D cp(quads[i]);
+ QVector2D ep(quads[i + 1]);
+ res.quadTo(cp, ep);
+ }
+ }
+ break;
+ }
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ sp = ep;
+ }
+
+ res.setPathHints(hints | PathQuadratic);
+ return res;
+}
+
+void QQuadPath::addCurvatureData()
+{
+ // We use the convention that the inside of a curve is on the *right* side of the
+ // direction of the baseline.Thus, as long as this is true: if the control point is
+ // on the left side of the baseline, the curve is convex and otherwise it is
+ // concave. The paths we get can be arbitrary order, but each subpath will have a
+ // consistent order. Therefore, for the first curve element in a subpath, we can
+ // determine if the direction already follows the convention or not, and then we
+ // can easily detect curvature of all subsequent elements in the subpath.
+
+ static bool checkAnomaly = qEnvironmentVariableIntValue("QT_QUICKSHAPES_CHECK_ALL_CURVATURE") != 0;
+ const bool pathHasFillOnRight = testHint(PathFillOnRight);
+
+ Element::CurvatureFlags flags = Element::CurvatureUndetermined;
+ for (QQuadPath::Element &element : m_elements) {
+ Q_ASSERT(element.childCount() == 0);
+ if (element.isSubpathStart()) {
+ if (pathHasFillOnRight && !checkAnomaly)
+ flags = Element::FillOnRight;
+ else
+ flags = coordinateOrderOfElement(element);
+ } else if (checkAnomaly) {
+ Element::CurvatureFlags newFlags = coordinateOrderOfElement(element);
+ if (flags != newFlags) {
+ qDebug() << "Curvature anomaly detected:" << element
+ << "Subpath fill on right:" << (flags & Element::FillOnRight)
+ << "Element fill on right:" << (newFlags & Element::FillOnRight);
+ flags = newFlags;
+ }
+ }
+
+ if (element.isLine()) {
+ element.m_curvatureFlags = flags;
+ } else {
+ bool controlPointOnLeft = element.isControlPointOnLeft();
+ bool isFillOnRight = flags & Element::FillOnRight;
+ bool isConvex = controlPointOnLeft == isFillOnRight;
+
+ if (isConvex)
+ element.m_curvatureFlags = Element::CurvatureFlags(flags | Element::Convex);
+ else
+ element.m_curvatureFlags = flags;
+ }
+ }
+}
+
+QRectF QQuadPath::controlPointRect() const
+{
+ QRectF res;
+ if (elementCount()) {
+ QVector2D min, max;
+ min = max = m_elements.constFirst().sp;
+ // No need to recurse, as split curve's controlpoints are within the parent curve's
+ for (const QQuadPath::Element &e : std::as_const(m_elements)) {
+ min.setX(std::min({ min.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
+ min.setY(std::min({ min.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
+ max.setX(std::max({ max.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
+ max.setY(std::max({ max.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
+ }
+ res = QRectF(min.toPointF(), max.toPointF());
+ }
+ return res;
+}
+
+// Count leaf elements
+int QQuadPath::elementCountRecursive() const
+{
+ int count = 0;
+ iterateElements([&](const QQuadPath::Element &, int) { count++; });
+ return count;
+}
+
+QPainterPath QQuadPath::toPainterPath() const
+{
+ // Currently only converts the main, unsplit path; no need for the split path identified
+ QPainterPath res;
+ res.reserve(elementCount());
+ res.setFillRule(fillRule());
+ for (const Element &element : m_elements) {
+ if (element.m_isSubpathStart)
+ res.moveTo(element.startPoint().toPointF());
+ if (element.m_isLine)
+ res.lineTo(element.endPoint().toPointF());
+ else
+ res.quadTo(element.controlPoint().toPointF(), element.endPoint().toPointF());
+ };
+ return res;
+}
+
+QString QQuadPath::asSvgString() const
+{
+ QString res;
+ QTextStream str(&res);
+ for (const Element &element : m_elements) {
+ if (element.isSubpathStart())
+ str << "M " << element.startPoint().x() << " " << element.startPoint().y() << " ";
+ if (element.isLine())
+ str << "L " << element.endPoint().x() << " " << element.endPoint().y() << " ";
+ else
+ str << "Q " << element.controlPoint().x() << " " << element.controlPoint().y() << " "
+ << element.endPoint().x() << " " << element.endPoint().y() << " ";
+ }
+ return res;
+}
+
+// Returns a new path since doing it inline would probably be less efficient
+// (technically changing it from O(n) to O(n^2))
+// Note that this function should be called before splitting any elements,
+// so we can assume that the structure is a list and not a tree
+QQuadPath QQuadPath::subPathsClosed(bool *didClose) const
+{
+ Q_ASSERT(m_childElements.isEmpty());
+ bool closed = false;
+ QQuadPath res = *this;
+ res.m_subPathToStart = false;
+ res.m_elements = {};
+ res.m_elements.reserve(elementCount());
+ int subStart = -1;
+ int prevElement = -1;
+ for (int i = 0; i < elementCount(); i++) {
+ const auto &element = m_elements.at(i);
+ if (element.m_isSubpathStart) {
+ if (subStart >= 0 && m_elements[i - 1].ep != m_elements[subStart].sp) {
+ res.m_currentPoint = m_elements[i - 1].ep;
+ res.lineTo(m_elements[subStart].sp);
+ closed = true;
+ auto &endElement = res.m_elements.last();
+ endElement.m_isSubpathEnd = true;
+ // lineTo() can bail out if the points are too close.
+ // In that case, just change the end point to be equal to the start
+ // (No need to test because the assignment is a no-op otherwise).
+ endElement.ep = m_elements[subStart].sp;
+ } else if (prevElement >= 0) {
+ res.m_elements[prevElement].m_isSubpathEnd = true;
+ }
+ subStart = i;
+ }
+ res.m_elements.append(element);
+ prevElement = res.m_elements.size() - 1;
+ }
+
+ if (subStart >= 0 && m_elements.last().ep != m_elements[subStart].sp) {
+ res.m_currentPoint = m_elements.last().ep;
+ res.lineTo(m_elements[subStart].sp);
+ closed = true;
+ }
+ if (!res.m_elements.isEmpty()) {
+ auto &endElement = res.m_elements.last();
+ endElement.m_isSubpathEnd = true;
+ endElement.ep = m_elements[subStart].sp;
+ }
+
+ if (didClose)
+ *didClose = closed;
+ return res;
+}
+
+QQuadPath QQuadPath::flattened() const
+{
+ QQuadPath res;
+ res.reserve(elementCountRecursive());
+ iterateElements([&](const QQuadPath::Element &elem, int) { res.m_elements.append(elem); });
+ res.setPathHints(pathHints());
+ res.setFillRule(fillRule());
+ return res;
+}
+
+class ElementCutter
+{
+public:
+ ElementCutter(const QQuadPath::Element &element)
+ : m_element(element)
+ {
+ m_currentPoint = m_element.startPoint();
+ if (m_element.isLine())
+ m_lineLength = (m_element.endPoint() - m_element.startPoint()).length();
+ else
+ fillLUT();
+ }
+
+ bool consume(float length)
+ {
+ m_lastT = m_currentT;
+ m_lastPoint = m_currentPoint;
+ float nextCut = m_consumed + length;
+ float cutT = m_element.isLine() ? nextCut / m_lineLength : tForLength(nextCut);
+ if (cutT < 1) {
+ m_currentT = cutT;
+ m_currentPoint = m_element.pointAtFraction(m_currentT);
+ m_consumed = nextCut;
+ return true;
+ } else {
+ m_currentT = 1;
+ m_currentPoint = m_element.endPoint();
+ return false;
+ }
+ }
+
+ QVector2D currentCutPoint()
+ {
+ return m_currentPoint;
+ }
+
+ QVector2D currentControlPoint()
+ {
+ Q_ASSERT(!m_element.isLine());
+ // Split curve right at lastT, yields { lastPoint, rcp, endPoint } quad segment
+ QVector2D rcp = (1 - m_lastT) * m_element.controlPoint() + m_lastT * m_element.endPoint();
+ // Split that left at currentT, yields { lastPoint, lcp, currentPoint } quad segment
+ float segmentT = (m_currentT - m_lastT) / (1 - m_lastT);
+ QVector2D lcp = (1 - segmentT) * m_lastPoint + segmentT * rcp;
+ return lcp;
+ }
+
+ float lastLength()
+ {
+ float elemLength = m_element.isLine() ? m_lineLength : m_lut.last();
+ return elemLength - m_consumed;
+ }
+
+private:
+ void fillLUT()
+ {
+ Q_ASSERT(!m_element.isLine());
+ QVector2D ap = m_element.startPoint() - 2 * m_element.controlPoint() + m_element.endPoint();
+ QVector2D bp = 2 * m_element.controlPoint() - 2 * m_element.startPoint();
+ float A = 4 * QVector2D::dotProduct(ap, ap);
+ float B = 4 * QVector2D::dotProduct(ap, bp);
+ float C = QVector2D::dotProduct(bp, bp);
+ float b = B / (2 * A);
+ float c = C / A;
+ float k = c - (b * b);
+ float l2 = b * std::sqrt(b * b + k);
+ float lnom = b + std::sqrt(b * b + k);
+ float l0 = 0.5f * std::sqrt(A);
+
+ m_lut.resize(LUTSize, 0);
+ for (int i = 1; i < LUTSize; i++) {
+ float t = float(i) / (LUTSize - 1);
+ float u = t + b;
+ float w = std::sqrt(u * u + k);
+ float l1 = u * w;
+ float lden = u + w;
+ float l3 = k * std::log(std::fabs(lden / lnom));
+ float res = l0 * (l1 - l2 + l3);
+ m_lut[i] = res;
+ }
+ }
+
+ float tForLength(float length)
+ {
+ Q_ASSERT(!m_element.isLine());
+ Q_ASSERT(!m_lut.isEmpty());
+
+ float res = 2; // I.e. invalid, outside [0, 1] range
+ auto it = std::upper_bound(m_lut.cbegin(), m_lut.cend(), length);
+ if (it != m_lut.cend()) {
+ float nextLength = *it--;
+ float prevLength = *it;
+ int prevIndex = std::distance(m_lut.cbegin(), it);
+ float fraction = (length - prevLength) / (nextLength - prevLength);
+ res = (prevIndex + fraction) / (LUTSize - 1);
+ }
+ return res;
+ }
+
+ const QQuadPath::Element &m_element;
+ float m_lastT = 0;
+ float m_currentT = 0;
+ QVector2D m_lastPoint;
+ QVector2D m_currentPoint;
+ float m_consumed = 0;
+ // For line elements:
+ float m_lineLength;
+ // For quadratic curve elements:
+ static constexpr int LUTSize = 21;
+ QVarLengthArray<float, LUTSize> m_lut;
+};
+
+QQuadPath QQuadPath::dashed(qreal lineWidth, const QList<qreal> &dashPattern, qreal dashOffset) const
+{
+ QVarLengthArray<float, 16> pattern;
+ float patternLength = 0;
+ for (int i = 0; i < 2 * (dashPattern.length() / 2); i++) {
+ float dashLength = qMax(lineWidth * dashPattern[i], qreal(0));
+ pattern.append(dashLength);
+ patternLength += dashLength;
+ }
+ if (patternLength == 0)
+ return {};
+
+ int startIndex = 0;
+ float startOffset = std::fmod(lineWidth * dashOffset, patternLength);
+ if (startOffset < 0)
+ startOffset += patternLength;
+ for (float dashLength : pattern) {
+ if (dashLength > startOffset)
+ break;
+ startIndex = (startIndex + 1) % pattern.size(); // The % guards against accuracy issues
+ startOffset -= dashLength;
+ }
+
+ int dashIndex = startIndex;
+ float offset = startOffset;
+ QQuadPath res;
+ for (int i = 0; i < elementCount(); i++) {
+ const Element &element = elementAt(i);
+ if (element.isSubpathStart()) {
+ res.moveTo(element.startPoint());
+ dashIndex = startIndex;
+ offset = startOffset;
+ }
+ ElementCutter cutter(element);
+ while (true) {
+ bool gotAll = cutter.consume(pattern.at(dashIndex) - offset);
+ QVector2D nextPoint = cutter.currentCutPoint();
+ if (dashIndex & 1)
+ res.moveTo(nextPoint); // gap
+ else if (element.isLine())
+ res.lineTo(nextPoint); // dash in line
+ else
+ res.quadTo(cutter.currentControlPoint(), nextPoint); // dash in curve
+ if (gotAll) {
+ offset = 0;
+ dashIndex = (dashIndex + 1) % pattern.size();
+ } else {
+ offset += cutter.lastLength();
+ break;
+ }
+ }
+ }
+ res.setFillRule(fillRule());
+ res.setPathHints(pathHints());
+ return res;
+}
+
+void QQuadPath::splitElementAt(int index)
+{
+ const int newChildIndex = m_childElements.size();
+ m_childElements.resize(newChildIndex + 2);
+ Element &parent = elementAt(index);
+ parent.m_numChildren = 2;
+ parent.m_firstChildIndex = newChildIndex;
+
+ Element &quad1 = m_childElements[newChildIndex];
+ const QVector2D mp = parent.midPoint();
+ quad1.sp = parent.sp;
+ quad1.cp = 0.5f * (parent.sp + parent.cp);
+ quad1.ep = mp;
+ quad1.m_isSubpathStart = parent.m_isSubpathStart;
+ quad1.m_isSubpathEnd = false;
+ quad1.m_curvatureFlags = parent.m_curvatureFlags;
+ quad1.m_isLine = parent.m_isLine; //### || isPointNearLine(quad1.cp, quad1.sp, quad1.ep);
+
+ Element &quad2 = m_childElements[newChildIndex + 1];
+ quad2.sp = mp;
+ quad2.cp = 0.5f * (parent.ep + parent.cp);
+ quad2.ep = parent.ep;
+ quad2.m_isSubpathStart = false;
+ quad2.m_isSubpathEnd = parent.m_isSubpathEnd;
+ quad2.m_curvatureFlags = parent.m_curvatureFlags;
+ quad2.m_isLine = parent.m_isLine; //### || isPointNearLine(quad2.cp, quad2.sp, quad2.ep);
+
+#ifndef QT_NO_DEBUG
+ if (qFuzzyCompare(quad1.sp, quad1.ep) || qFuzzyCompare(quad2.sp, quad2.ep))
+ qCDebug(lcSGCurveProcessor) << "Splitting has resulted in ~null quad";
+#endif
+}
+
+static void printElement(QDebug stream, const QQuadPath::Element &element)
+{
+ auto printPoint = [&](QVector2D p) { stream << "(" << p.x() << ", " << p.y() << ") "; };
+ stream << "{ ";
+ printPoint(element.startPoint());
+ printPoint(element.controlPoint());
+ printPoint(element.endPoint());
+ stream << "} " << (element.isLine() ? "L " : "C ") << (element.isConvex() ? "X " : "O ")
+ << (element.isSubpathStart() ? "S" : element.isSubpathEnd() ? "E" : "");
+}
+
+QDebug operator<<(QDebug stream, const QQuadPath::Element &element)
+{
+ QDebugStateSaver saver(stream);
+ stream.nospace();
+ stream << "QuadPath::Element( ";
+ printElement(stream, element);
+ stream << " )";
+ return stream;
+}
+
+QDebug operator<<(QDebug stream, const QQuadPath &path)
+{
+ QDebugStateSaver saver(stream);
+ stream.nospace();
+ stream << "QuadPath(" << path.elementCount() << " main elements, "
+ << path.elementCountRecursive() << " leaf elements, "
+ << (path.fillRule() == Qt::OddEvenFill ? "OddEven" : "Winding") << Qt::endl;
+ int count = 0;
+ path.iterateElements([&](const QQuadPath::Element &e, int) {
+ stream << " " << count++ << (e.isSubpathStart() ? " >" : " ");
+ printElement(stream, e);
+ stream << Qt::endl;
+ });
+ stream << ")";
+ return stream;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qquadpath_p.h b/src/quick/scenegraph/util/qquadpath_p.h
new file mode 100644
index 0000000000..98ef5b664c
--- /dev/null
+++ b/src/quick/scenegraph/util/qquadpath_p.h
@@ -0,0 +1,341 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUADPATH_P_H
+#define QQUADPATH_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qrect.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qdebug.h>
+#include <QtGui/qvector2d.h>
+#include <QtGui/qpainterpath.h>
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QQuadPath
+{
+public:
+ // This is a copy of the flags in QQuickShapePath ### TODO: use a common definition
+ enum PathHint : quint8 {
+ PathLinear = 0x1,
+ PathQuadratic = 0x2,
+ PathConvex = 0x4,
+ PathFillOnRight = 0x8,
+ PathSolid = 0x10,
+ PathNonIntersecting = 0x20,
+ PathNonOverlappingControlPointTriangles = 0x40
+ };
+ Q_DECLARE_FLAGS(PathHints, PathHint)
+
+ class Q_QUICK_EXPORT Element
+ {
+ public:
+ Element ()
+ : m_isSubpathStart(false), m_isSubpathEnd(false), m_isLine(false)
+ {
+ }
+
+ Element (QVector2D s, QVector2D c, QVector2D e)
+ : sp(s), cp(c), ep(e), m_isSubpathStart(false), m_isSubpathEnd(false), m_isLine(false)
+ {
+ }
+
+ bool isSubpathStart() const
+ {
+ return m_isSubpathStart;
+ }
+
+ bool isSubpathEnd() const
+ {
+ return m_isSubpathEnd;
+ }
+
+ bool isLine() const
+ {
+ return m_isLine;
+ }
+
+ bool isConvex() const
+ {
+ return m_curvatureFlags & Convex;
+ }
+
+ QVector2D startPoint() const
+ {
+ return sp;
+ }
+
+ QVector2D controlPoint() const
+ {
+ return cp;
+ }
+
+ QVector2D endPoint() const
+ {
+ return ep;
+ }
+
+ QVector2D midPoint() const
+ {
+ return isLine() ? 0.5f * (sp + ep) : (0.25f * sp) + (0.5f * cp) + (0.25 * ep);
+ }
+
+ /* For a curve, returns the control point. For a line, returns an arbitrary point on the
+ * inside side of the line (assuming the curvature has been set for the path). The point
+ * doesn't need to actually be inside the shape: it just makes for easier calculations
+ * later when it is at the same side as the fill. */
+ QVector2D referencePoint() const
+ {
+ if (isLine()) {
+ QVector2D normal(sp.y() - ep.y(), ep.x() - sp.x());
+ return m_curvatureFlags & Element::FillOnRight ? sp + normal : sp - normal;
+ } else {
+ return cp;
+ }
+ }
+
+ Element segmentFromTo(float t0, float t1) const;
+
+ Element reversed() const;
+
+ int childCount() const { return m_numChildren; }
+
+ int indexOfChild(int childNumber) const
+ {
+ Q_ASSERT(childNumber >= 0 && childNumber < childCount());
+ return -(m_firstChildIndex + 1 + childNumber);
+ }
+
+ QVector2D pointAtFraction(float t) const;
+
+ QVector2D tangentAtFraction(float t) const
+ {
+ return isLine() ? (ep - sp) : ((1 - t) * 2 * (cp - sp)) + (t * 2 * (ep - cp));
+ }
+
+ QVector2D normalAtFraction(float t) const
+ {
+ const QVector2D tan = tangentAtFraction(t);
+ return QVector2D(-tan.y(), tan.x());
+ }
+
+ float extent() const;
+
+ void setAsConvex(bool isConvex)
+ {
+ if (isConvex)
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags | Element::Convex);
+ else
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags & ~Element::Convex);
+ }
+
+ void setFillOnRight(bool isFillOnRight)
+ {
+ if (isFillOnRight)
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags | Element::FillOnRight);
+ else
+ m_curvatureFlags = Element::CurvatureFlags(m_curvatureFlags & ~Element::FillOnRight);
+ }
+
+ bool isFillOnRight() const { return m_curvatureFlags & FillOnRight; }
+
+ bool isControlPointOnLeft() const
+ {
+ return isPointOnLeft(cp, sp, ep);
+ }
+
+ enum CurvatureFlags : quint8 {
+ CurvatureUndetermined = 0,
+ FillOnRight = 1,
+ Convex = 2
+ };
+
+ enum FillSide : quint8 {
+ FillSideUndetermined = 0,
+ FillSideRight = 1,
+ FillSideLeft = 2,
+ FillSideBoth = 3
+ };
+
+ private:
+ int intersectionsAtY(float y, float *fractions, bool swapXY = false) const;
+
+ QVector2D sp;
+ QVector2D cp;
+ QVector2D ep;
+ int m_firstChildIndex = 0;
+ quint8 m_numChildren = 0;
+ CurvatureFlags m_curvatureFlags = CurvatureUndetermined;
+ quint8 m_isSubpathStart : 1;
+ quint8 m_isSubpathEnd : 1;
+ quint8 m_isLine : 1;
+ friend class QQuadPath;
+ friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath::Element &);
+ };
+
+ void moveTo(const QVector2D &to)
+ {
+ m_subPathToStart = true;
+ m_currentPoint = to;
+ }
+
+ void lineTo(const QVector2D &to)
+ {
+ addElement({}, to, true);
+ }
+
+ void quadTo(const QVector2D &control, const QVector2D &to)
+ {
+ addElement(control, to);
+ }
+
+ Element &elementAt(int i)
+ {
+ return i < 0 ? m_childElements[-(i + 1)] : m_elements[i];
+ }
+
+ const Element &elementAt(int i) const
+ {
+ return i < 0 ? m_childElements[-(i + 1)] : m_elements[i];
+ }
+
+ int indexOfChildAt(int i, int childNumber) const
+ {
+ return elementAt(i).indexOfChild(childNumber);
+ }
+
+ QRectF controlPointRect() const;
+
+ Qt::FillRule fillRule() const { return m_windingFill ? Qt::WindingFill : Qt::OddEvenFill; }
+ void setFillRule(Qt::FillRule rule) { m_windingFill = (rule == Qt::WindingFill); }
+
+ void reserve(int size) { m_elements.reserve(size); }
+ int elementCount() const { return m_elements.size(); }
+ bool isEmpty() const { return m_elements.size() == 0; }
+ int elementCountRecursive() const;
+
+ static QQuadPath fromPainterPath(const QPainterPath &path, PathHints hints = {});
+ QPainterPath toPainterPath() const;
+ QString asSvgString() const;
+
+ QQuadPath subPathsClosed(bool *didClose = nullptr) const;
+ void addCurvatureData();
+ QQuadPath flattened() const;
+ QQuadPath dashed(qreal lineWidth, const QList<qreal> &dashPattern, qreal dashOffset = 0) const;
+ void splitElementAt(int index);
+ bool contains(const QVector2D &point) const;
+ bool contains(const QVector2D &point, int fromIndex, int toIndex) const;
+ Element::FillSide fillSideOf(int elementIdx, float elementT) const;
+
+ template<typename Func>
+ void iterateChildrenOf(Element &e, Func &&lambda)
+ {
+ const int lastChildIndex = e.m_firstChildIndex + e.childCount() - 1;
+ for (int i = e.m_firstChildIndex; i <= lastChildIndex; i++) {
+ Element &c = m_childElements[i];
+ if (c.childCount() > 0)
+ iterateChildrenOf(c, lambda);
+ else
+ lambda(c, -(i + 1));
+ }
+ }
+
+ template<typename Func>
+ void iterateChildrenOf(const Element &e, Func &&lambda) const
+ {
+ const int lastChildIndex = e.m_firstChildIndex + e.childCount() - 1;
+ for (int i = e.m_firstChildIndex; i <= lastChildIndex; i++) {
+ const Element &c = m_childElements[i];
+ if (c.childCount() > 0)
+ iterateChildrenOf(c, lambda);
+ else
+ lambda(c, -(i + 1));
+ }
+ }
+
+ template<typename Func>
+ void iterateElements(Func &&lambda)
+ {
+ for (int i = 0; i < m_elements.size(); i++) {
+ Element &e = m_elements[i];
+ if (e.childCount() > 0)
+ iterateChildrenOf(e, lambda);
+ else
+ lambda(e, i);
+ }
+ }
+
+ template<typename Func>
+ void iterateElements(Func &&lambda) const
+ {
+ for (int i = 0; i < m_elements.size(); i++) {
+ const Element &e = m_elements[i];
+ if (e.childCount() > 0)
+ iterateChildrenOf(e, lambda);
+ else
+ lambda(e, i);
+ }
+ }
+
+ static QVector2D closestPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+ static bool isPointOnLeft(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+ static bool isPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+ static bool isPointNearLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+
+ bool testHint(PathHint hint) const
+ {
+ return m_hints.testFlag(hint);
+ }
+
+ void setHint(PathHint hint, bool on = true)
+ {
+ m_hints.setFlag(hint, on);
+ }
+
+ PathHints pathHints() const
+ {
+ return m_hints;
+ }
+
+ void setPathHints(PathHints newHints)
+ {
+ m_hints = newHints;
+ }
+
+private:
+ void addElement(const QVector2D &control, const QVector2D &to, bool isLine = false);
+ void addElement(const Element &e);
+ Element::CurvatureFlags coordinateOrderOfElement(const Element &element) const;
+
+ friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &);
+
+ QList<Element> m_elements;
+ QList<Element> m_childElements;
+ QVector2D m_currentPoint;
+ bool m_subPathToStart = true;
+ bool m_windingFill = false;
+ PathHints m_hints;
+
+ friend class QSGCurveProcessor;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuadPath::PathHints);
+
+Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath::Element &);
+Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/quick/scenegraph/util/qsgareaallocator.cpp b/src/quick/scenegraph/util/qsgareaallocator.cpp
index 977af81ab7..914b5b6c32 100644
--- a/src/quick/scenegraph/util/qsgareaallocator.cpp
+++ b/src/quick/scenegraph/util/qsgareaallocator.cpp
@@ -13,16 +13,13 @@
QT_BEGIN_NAMESPACE
-namespace
+enum SplitType
{
- enum SplitType
- {
- VerticalSplit,
- HorizontalSplit
- };
+ VerticalSplit,
+ HorizontalSplit
+};
- static const int maxMargin = 2;
-}
+static const int maxMargin = 2;
struct QSGAreaAllocatorNode
{
diff --git a/src/quick/scenegraph/util/qsgareaallocator_p.h b/src/quick/scenegraph/util/qsgareaallocator_p.h
index 07bc1488a2..fa53dde037 100644
--- a/src/quick/scenegraph/util/qsgareaallocator_p.h
+++ b/src/quick/scenegraph/util/qsgareaallocator_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
class QRect;
class QPoint;
struct QSGAreaAllocatorNode;
-class Q_QUICK_PRIVATE_EXPORT QSGAreaAllocator
+class Q_QUICK_EXPORT QSGAreaAllocator
{
public:
QSGAreaAllocator(const QSize &size);
diff --git a/src/quick/scenegraph/util/qsgdefaultimagenode_p.h b/src/quick/scenegraph/util/qsgdefaultimagenode_p.h
index 7a541d5d96..344acd5c55 100644
--- a/src/quick/scenegraph/util/qsgdefaultimagenode_p.h
+++ b/src/quick/scenegraph/util/qsgdefaultimagenode_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultImageNode : public QSGImageNode
+class Q_QUICK_EXPORT QSGDefaultImageNode : public QSGImageNode
{
public:
QSGDefaultImageNode();
diff --git a/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h b/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h
index 298ed31237..ce33e449ab 100644
--- a/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h
+++ b/src/quick/scenegraph/util/qsgdefaultninepatchnode_p.h
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultNinePatchNode : public QSGNinePatchNode
+class Q_QUICK_EXPORT QSGDefaultNinePatchNode : public QSGNinePatchNode
{
public:
QSGDefaultNinePatchNode();
diff --git a/src/quick/scenegraph/util/qsgdefaultpainternode.cpp b/src/quick/scenegraph/util/qsgdefaultpainternode.cpp
index 2d6f6acf08..874e6dac38 100644
--- a/src/quick/scenegraph/util/qsgdefaultpainternode.cpp
+++ b/src/quick/scenegraph/util/qsgdefaultpainternode.cpp
@@ -108,7 +108,8 @@ void QSGDefaultPainterNode::paint()
}
painter.setCompositionMode(QPainter::CompositionMode_Source);
- painter.fillRect(clipRect, m_fillColor);
+ if (m_fillColor.isValid())
+ painter.fillRect(clipRect, m_fillColor);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_item->paint(&painter);
@@ -167,7 +168,7 @@ void QSGDefaultPainterNode::updateRenderTarget()
if (!m_image.isNull() && !m_dirtyGeometry)
return;
- m_image = QImage(m_textureSize, QImage::Format_ARGB32_Premultiplied);
+ m_image = QImage(m_textureSize, QImage::Format_RGBA8888_Premultiplied);
m_image.fill(Qt::transparent);
if (!m_texture) {
diff --git a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h
index 17e158204b..4038aa0733 100644
--- a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h
+++ b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h
@@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE
class QSGDefaultRenderContext;
-class Q_QUICK_PRIVATE_EXPORT QSGPainterTexture : public QSGPlainTexture
+class Q_QUICK_EXPORT QSGPainterTexture : public QSGPlainTexture
{
public:
QSGPainterTexture();
@@ -40,7 +40,7 @@ private:
QRect m_dirty_rect;
};
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultPainterNode : public QSGPainterNode
+class Q_QUICK_EXPORT QSGDefaultPainterNode : public QSGPainterNode
{
public:
QSGDefaultPainterNode(QQuickPaintedItem *item);
diff --git a/src/quick/scenegraph/util/qsgflatcolormaterial.cpp b/src/quick/scenegraph/util/qsgflatcolormaterial.cpp
index c7502c91fb..9c491cb7df 100644
--- a/src/quick/scenegraph/util/qsgflatcolormaterial.cpp
+++ b/src/quick/scenegraph/util/qsgflatcolormaterial.cpp
@@ -9,7 +9,7 @@ QT_BEGIN_NAMESPACE
class FlatColorMaterialRhiShader : public QSGMaterialShader
{
public:
- FlatColorMaterialRhiShader();
+ FlatColorMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -18,10 +18,10 @@ public:
QSGMaterialType FlatColorMaterialRhiShader::type;
-FlatColorMaterialRhiShader::FlatColorMaterialRhiShader()
+FlatColorMaterialRhiShader::FlatColorMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.frag.qsb"), viewCount);
}
bool FlatColorMaterialRhiShader::updateUniformData(RenderState &state,
@@ -33,11 +33,15 @@ bool FlatColorMaterialRhiShader::updateUniformData(RenderState &state,
QSGFlatColorMaterial *mat = static_cast<QSGFlatColorMaterial *>(newMaterial);
bool changed = false;
QByteArray *buf = state.uniformData();
-
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
const QColor &c = mat->color();
@@ -47,7 +51,7 @@ bool FlatColorMaterialRhiShader::updateUniformData(RenderState &state,
const float opacity = state.opacity() * a;
QVector4D v(r * opacity, g * opacity, b * opacity, opacity);
Q_ASSERT(sizeof(v) == 16);
- memcpy(buf->data() + 64, &v, 16);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &v, 16);
changed = true;
}
@@ -130,7 +134,7 @@ QSGMaterialType *QSGFlatColorMaterial::type() const
QSGMaterialShader *QSGFlatColorMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new FlatColorMaterialRhiShader;
+ return new FlatColorMaterialRhiShader(viewCount());
}
diff --git a/src/quick/scenegraph/util/qsggradientcache.cpp b/src/quick/scenegraph/util/qsggradientcache.cpp
new file mode 100644
index 0000000000..ae88cd2e3e
--- /dev/null
+++ b/src/quick/scenegraph/util/qsggradientcache.cpp
@@ -0,0 +1,121 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsggradientcache_p.h"
+
+#include <QtGui/private/qdrawhelper_p.h>
+#include <QtGui/rhi/qrhi.h>
+
+#include <QtQuick/qsgtexture.h>
+#include <QtQuick/private/qsgplaintexture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+static void generateGradientColorTable(const QSGGradientCacheKey &gradient,
+ uint *colorTable, int size, float opacity)
+{
+ int pos = 0;
+ const QGradientStops &s = gradient.stops;
+ Q_ASSERT(!s.isEmpty());
+ const bool colorInterpolation = true;
+
+ uint alpha = qRound(opacity * 256);
+ uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha);
+ qreal incr = 1.0 / qreal(size);
+ qreal fpos = 1.5 * incr;
+ colorTable[pos++] = ARGB2RGBA(qPremultiply(current_color));
+
+ while (fpos <= s.first().first) {
+ colorTable[pos] = colorTable[pos - 1];
+ pos++;
+ fpos += incr;
+ }
+
+ if (colorInterpolation)
+ current_color = qPremultiply(current_color);
+
+ const int sLast = s.size() - 1;
+ for (int i = 0; i < sLast; ++i) {
+ qreal delta = 1/(s[i+1].first - s[i].first);
+ uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha);
+ if (colorInterpolation)
+ next_color = qPremultiply(next_color);
+
+ while (fpos < s[i+1].first && pos < size) {
+ int dist = int(256 * ((fpos - s[i].first) * delta));
+ int idist = 256 - dist;
+ if (colorInterpolation)
+ colorTable[pos] = ARGB2RGBA(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
+ else
+ colorTable[pos] = ARGB2RGBA(qPremultiply(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)));
+ ++pos;
+ fpos += incr;
+ }
+ current_color = next_color;
+ }
+
+ uint last_color = ARGB2RGBA(qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha)));
+ for ( ; pos < size; ++pos)
+ colorTable[pos] = last_color;
+
+ colorTable[size-1] = last_color;
+}
+
+QSGGradientCache::~QSGGradientCache()
+{
+ qDeleteAll(m_textures);
+}
+
+QSGGradientCache *QSGGradientCache::cacheForRhi(QRhi *rhi)
+{
+ static QHash<QRhi *, QSGGradientCache *> caches;
+ auto it = caches.constFind(rhi);
+ if (it != caches.constEnd())
+ return *it;
+
+ QSGGradientCache *cache = new QSGGradientCache;
+ rhi->addCleanupCallback([cache](QRhi *rhi) {
+ caches.remove(rhi);
+ delete cache;
+ });
+ caches.insert(rhi, cache);
+ return cache;
+}
+
+QSGTexture *QSGGradientCache::get(const QSGGradientCacheKey &grad)
+{
+ QSGPlainTexture *tx = m_textures[grad];
+ if (!tx) {
+ static const int W = 1024; // texture size is 1024x1
+ QImage gradTab(W, 1, QImage::Format_RGBA8888_Premultiplied);
+ if (!grad.stops.isEmpty())
+ generateGradientColorTable(grad, reinterpret_cast<uint *>(gradTab.bits()), W, 1.0f);
+ else
+ gradTab.fill(Qt::black);
+ tx = new QSGPlainTexture;
+ tx->setImage(gradTab);
+ switch (grad.spread) {
+ case QGradient::PadSpread:
+ tx->setHorizontalWrapMode(QSGTexture::ClampToEdge);
+ tx->setVerticalWrapMode(QSGTexture::ClampToEdge);
+ break;
+ case QGradient::RepeatSpread:
+ tx->setHorizontalWrapMode(QSGTexture::Repeat);
+ tx->setVerticalWrapMode(QSGTexture::Repeat);
+ break;
+ case QGradient::ReflectSpread:
+ tx->setHorizontalWrapMode(QSGTexture::MirroredRepeat);
+ tx->setVerticalWrapMode(QSGTexture::MirroredRepeat);
+ break;
+ default:
+ qWarning("Unknown gradient spread mode %d", grad.spread);
+ break;
+ }
+ tx->setFiltering(QSGTexture::Linear);
+ m_textures[grad] = tx;
+ }
+ return tx;
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsggradientcache_p.h b/src/quick/scenegraph/util/qsggradientcache_p.h
new file mode 100644
index 0000000000..f384821978
--- /dev/null
+++ b/src/quick/scenegraph/util/qsggradientcache_p.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGGRADIENTCACHE_P_H
+#define QSGGRADIENTCACHE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qhash.h>
+#include <QtGui/qbrush.h>
+
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGTexture;
+class QSGPlainTexture;
+class QRhi;
+
+struct Q_QUICK_EXPORT QSGGradientCacheKey
+{
+ QSGGradientCacheKey(const QGradientStops &stops, QGradient::Spread spread)
+ : stops(stops), spread(spread)
+ { }
+ QGradientStops stops;
+ QGradient::Spread spread;
+ bool operator==(const QSGGradientCacheKey &other) const
+ {
+ return spread == other.spread && stops == other.stops;
+ }
+};
+
+inline size_t qHash(const QSGGradientCacheKey &v, size_t seed = 0)
+{
+ size_t h = seed + v.spread;
+ for (int i = 0; i < 3 && i < v.stops.size(); ++i)
+ h += v.stops[i].second.rgba();
+ return h;
+}
+
+class Q_QUICK_EXPORT QSGGradientCache
+{
+public:
+ struct GradientDesc { // can fully describe a linear/radial/conical gradient
+ QGradientStops stops;
+ QGradient::Spread spread = QGradient::PadSpread;
+ QPointF a; // start (L) or center point (R/C)
+ QPointF b; // end (L) or focal point (R)
+ qreal v0; // center radius (R) or start angle (C)
+ qreal v1; // focal radius (R)
+ };
+
+ ~QSGGradientCache();
+ static QSGGradientCache *cacheForRhi(QRhi *rhi);
+ QSGTexture *get(const QSGGradientCacheKey &grad);
+
+private:
+ QHash<QSGGradientCacheKey, QSGPlainTexture *> m_textures;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGGRADIENTCACHE_P_H
diff --git a/src/quick/scenegraph/util/qsgplaintexture.cpp b/src/quick/scenegraph/util/qsgplaintexture.cpp
index b262047c38..e3ea9ffc17 100644
--- a/src/quick/scenegraph/util/qsgplaintexture.cpp
+++ b/src/quick/scenegraph/util/qsgplaintexture.cpp
@@ -8,7 +8,7 @@
#include <private/qqmlglobal_p.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpa/qplatformnativeinterface.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <QtQuick/private/qsgrhisupport_p.h>
#include <qtquick_tracepoints_p.h>
@@ -67,7 +67,8 @@ void QSGPlainTexture::setTexture(QRhiTexture *texture) // RHI only
void QSGPlainTexture::setTextureFromNativeTexture(QRhi *rhi,
quint64 nativeObjectHandle,
- int nativeLayout, uint nativeFormat,
+ int nativeLayoutOrState,
+ uint nativeFormat,
const QSize &size,
QQuickWindow::CreateTextureOptions options,
QQuickWindowPrivate::TextureFromNativeTextureFlags flags)
@@ -90,7 +91,7 @@ void QSGPlainTexture::setTextureFromNativeTexture(QRhi *rhi,
QRhiTexture *t = rhi->newTexture(format, size, 1, texFlags);
// ownership of the native object is never taken
- t->createFrom({nativeObjectHandle, nativeLayout});
+ t->createFrom({nativeObjectHandle, nativeLayoutOrState});
setTexture(t);
}
diff --git a/src/quick/scenegraph/util/qsgplaintexture_p.h b/src/quick/scenegraph/util/qsgplaintexture_p.h
index 17b9e41413..caa14fa9df 100644
--- a/src/quick/scenegraph/util/qsgplaintexture_p.h
+++ b/src/quick/scenegraph/util/qsgplaintexture_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
class QSGPlainTexturePrivate;
-class Q_QUICK_PRIVATE_EXPORT QSGPlainTexture : public QSGTexture
+class Q_QUICK_EXPORT QSGPlainTexture : public QSGTexture
{
Q_OBJECT
Q_DECLARE_PRIVATE(QSGPlainTexture)
@@ -53,7 +53,7 @@ public:
void setTexture(QRhiTexture *texture);
void setTextureFromNativeTexture(QRhi *rhi,
quint64 nativeObjectHandle,
- int nativeLayout,
+ int nativeLayoutOrState,
uint nativeFormat,
const QSize &size,
QQuickWindow::CreateTextureOptions options,
diff --git a/src/quick/scenegraph/util/qsgrhiatlastexture_p.h b/src/quick/scenegraph/util/qsgrhiatlastexture_p.h
index b766a8f400..5eb9c68495 100644
--- a/src/quick/scenegraph/util/qsgrhiatlastexture_p.h
+++ b/src/quick/scenegraph/util/qsgrhiatlastexture_p.h
@@ -19,7 +19,7 @@
#include <QtQuick/private/qsgplaintexture_p.h>
#include <QtQuick/private/qsgareaallocator_p.h>
#include <QtGui/QSurface>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp
index 74512f6eb1..b8583654e3 100644
--- a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp
+++ b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp
@@ -256,7 +256,7 @@ void QSGSimpleTextureNode::setTextureCoordinatesTransform(QSGSimpleTextureNode::
return;
d->texCoordMode = mode;
qsgsimpletexturenode_update(&m_geometry, texture(), m_rect, d->sourceRect, d->texCoordMode);
- markDirty(DirtyMaterial);
+ markDirty(DirtyGeometry | DirtyMaterial);
}
/*!
diff --git a/src/quick/scenegraph/util/qsgtextnode.cpp b/src/quick/scenegraph/util/qsgtextnode.cpp
new file mode 100644
index 0000000000..6466fd1298
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtextnode.cpp
@@ -0,0 +1,320 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgtextnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSGTextNode
+
+ \brief The QSGTextNode class is a class for drawing text layouts and text documents in
+ the Qt Quick scene graph.
+ \inmodule QtQuick
+ \since 6.7
+
+ QSGTextNode can be useful for creating custom Qt Quick items that require text. It is used
+ in Qt Quick by the Text, TextEdit and TextInput elements.
+
+ You can create QSGTextNode objects using QQuickWindow::createTextNode(). The addTextLayout()
+ and addTextDocument() functions provide ways to add text to the QSGTextNode. The text must
+ already be laid out.
+
+ \note Properties must be set before \l addTextLayout() or \l addTextDocument() are called in
+ order to have an effect.
+
+ \note The destruction of QSGTextNode has to be managed with care. In particular, since it
+ references graphics resources, it must be deleted when the Qt Quick scene graph is invalidated.
+ If the node is part of the graph and has the \c OwnedByParent flag set (which is the default),
+ this will happen automatically. However, if the \c OwnedByParent flag is cleared and the node is
+ disposed of manually, care must be taken to do this when the scene graph is invalidated. This
+ can be done by connecting to the \l{QQuickWindow::sceneGraphInvalidated()} signal, or by
+ implementing a slot in the QQuickItem subclass which is named \c{invalidateSceneGraph()}.
+ See also the documentation of QQuickItem for more details.
+ */
+
+/*!
+ \enum QSGTextNode::TextStyle
+
+ This enum type describes styles that can be applied to text rendering.
+
+ \value Normal The text is drawn without any style applied.
+ \value Outline The text is drawn with an outline.
+ \value Raised The text is drawn raised.
+ \value Sunken The text is drawn sunken.
+
+ \sa setTextStyle(), setStyleColor()
+*/
+
+/*!
+ \enum QSGTextNode::RenderType
+
+ This enum type describes type of glyph node used for rendering the text.
+
+ \value QtRendering Text is rendered using a scalable distance field for each glyph.
+ \value NativeRendering Text is rendered using a platform-specific technique.
+ \value CurveRendering Text is rendered using a curve rasterizer running directly on the
+ graphics hardware.
+
+ Select \c NativeRendering if you prefer text to look native on the target platform and do
+ not require advanced features such as transformation of the text. Using such features in
+ combination with the NativeRendering render type will lend poor and sometimes pixelated
+ results.
+
+ Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
+ \sa setRenderType(), setRenderTypeQuality()
+*/
+
+/*!
+ \fn void QSGTextNode::setColor(QColor color)
+
+ Sets the main color to use when rendering the text to \a color.
+
+ The default is black: \c QColor(0, 0, 0).
+*/
+
+/*!
+ \fn QColor QSGTextNode::color() const
+
+ Returns the main color used when rendering the text.
+*/
+
+/*!
+ \fn void QSGTextNode::setStyleColor(QColor styleColor)
+
+ Sets the style color to use when rendering the text to \a styleColor.
+
+ The default is black: \c QColor(0, 0, 0).
+
+ \sa setTextStyle()
+*/
+
+/*!
+ \fn QColor QSGTextNode::styleColor() const
+
+ Returns the style color used when rendering the text.
+
+ \sa textStyle()
+*/
+
+/*!
+ \fn void QSGTextNode::setTextStyle(QSGTextNode::TextStyle textStyle)
+
+ Sets the style of the rendered text to \a textStyle. The default is \c Normal.
+
+ \sa setStyleColor()
+*/
+
+/*!
+ \fn QSGTextNode::TextStyle QSGTextNode::textStyle()
+
+ Returns the style of the rendered text.
+
+ \sa styleColor()
+*/
+
+/*!
+ \fn void QSGTextNode::setLinkColor(QColor linkColor)
+
+ Sets the color of or hyperlinks to \a linkColor in the text.
+
+ The default is blue: \c QColor(0, 0, 255).
+*/
+
+/*!
+ \fn QColor QSGTextNode::linkColor() const
+
+ Returns the color of hyperlinks in the text.
+*/
+
+/*!
+ \fn void QSGTextNode::setSelectionColor(QColor color)
+
+ Sets the color of the selection background to \a color when any part of the text is
+ marked as selected.
+
+ The default is dark blue: \c QColor(0, 0, 128).
+*/
+
+/*!
+ \fn QColor QSGTextNode::selectionColor() const
+
+ Returns the color of the selection background when any part of the text is marked as selected.
+*/
+
+/*!
+ \fn QColor QSGTextNode::selectionTextColor() const
+
+ Returns the color of the selection text when any part of the text is marked as selected.
+*/
+
+/*!
+ \fn void QSGTextNode::setSelectionTextColor(QColor selectionTextColor)
+
+ Sets the color of the selection text to \a selectionTextColor when any part of the text is
+ marked as selected.
+
+ The default is white: \c QColor(255, 255, 255).
+*/
+
+
+/*!
+ \fn void QSGTextNode::setRenderType(RenderType renderType)
+
+ Sets the type of glyph node in use to \a renderType.
+
+ The default is \l QtRendering.
+*/
+
+/*!
+ \fn QSGTextNode::RenderType QSGTextNode::renderType() const
+
+ Returns the type of glyph node used for rendering the text.
+*/
+
+/*!
+ \fn void QSGTextNode::setRenderTypeQuality(int renderTypeQuality)
+
+ If the \l renderType() in use supports it, set the quality to use when rendering the text.
+ When supported, this can be used to trade visual fidelity for execution speed or memory.
+
+ When the \a renderTypeQuality is < 0, the default quality is used.
+
+ The \a renderTypeQuality can be any integer, although limitations imposed by the underlying
+ graphics hardware may be encountered if extreme values are set. The Qt Quick Text element
+ operates with the following predefined values:
+
+ \value DefaultRenderTypeQuality -1 (default)
+ \value LowRenderTypeQuality 26
+ \value NormalRenderTypeQuality 52
+ \value HighRenderTypeQuality 104
+ \value VeryHighRenderTypeQuality 208
+
+ This value is currently only respected by the QtRendering render type. Setting it changes the
+ resolution of the distance fields used to represent the glyphs. Setting it above normal will
+ cause memory consumption to increase, but reduces filtering artifacts on very large text.
+
+ The default is -1.
+*/
+
+/*!
+ \fn int QSGTextNode::renderTypeQuality() const
+
+ Returns the render type quality of the node. See \l setRenderTypeQuality() for details.
+*/
+
+/*!
+ \fn void QSGTextNode::setFiltering(QSGTexture::Filtering filtering)
+
+ Sets the sampling mode used when scaling images that are part of the displayed text to
+ \a filtering. For smoothly scaled images, use \l{QSGTexture::Linear} here.
+
+ The default is \l{QSGTexture::Nearest}.
+
+ \sa filtering()
+*/
+
+/*!
+ \fn QSGTexture::Filtering QSGTextNode::filtering() const
+
+ Returns the sampling mode used when scaling images that are part of the displayed text.
+
+ \sa setFiltering()
+*/
+
+/*!
+ \fn void QSGTextNode::setViewport(const QRectF &viewport)
+
+ Sets the bounding rect of the viewport where the text is displayed to \a viewport. Providing
+ this information makes it possible for the QSGTextNode to optimize which parts of the text
+ layout or document are included in the scene graph.
+
+ The default is a default-constructed QRectF. For this viewport, all contents will be included
+ in the graph.
+*/
+
+/*!
+ \fn QRectF QSGTextNode::viewport() const
+
+ Returns the current viewport set for this QSGTextNode.
+*/
+
+/*!
+ \fn QSGTextNode::addTextLayout(QPointF position, QTextLayout *layout, int selectionStart = -1, int selectionCount = -1, int lineStart = 0, int lineCount = -1)
+
+ Adds the contents of \a layout to the text node at \a position. If \a selectionStart is >= 0,
+ then this marks the first character in a selected area of \a selectionCount number of
+ characters. The selection is represented as a background fill with the \l selectionColor() and
+ the selected text is rendered in the \l selectionTextColor().
+
+ For convenience, \a lineStart and \a lineCount can be used to select the range of \l QTextLine
+ objects to include from the layout. This can be useful, for instance, when creating elided
+ layouts. If \a lineCount is < 0, then the the node will include the lines from \a lineStart to
+ the end of the layout.
+
+ This function forwards its arguments to the virtual function doAddTextLayout().
+
+ \sa clear(), doAddTextLayout()
+*/
+
+/*!
+ \fn QSGTextNode::addTextDocument(QPointF position, QTextDocument *document, int selectionStart = -1, int selectionCount = -1)
+
+ Adds the contents of \a document to the text node at \a position. If \a selectionStart is >= 0,
+ then this marks the first character in a selected area of \a selectionCount number of
+ characters. The selection is represented as a background fill with the \l selectionColor() and
+ the selected text is rendered in the \l selectionTextColor().
+
+ This function forwards its arguments to the virtual function doAddTextDocument().
+
+ \sa clear(), doAddTextDocument()
+*/
+
+/*!
+ \fn QSGTextNode::doAddTextLayout(QPointF position, QTextLayout *layout, int selectionStart, int selectionCount, int lineStart, int lineCount)
+
+ Virtual function called by addTextLayout(), which converts the contents of \a layout to scene
+ graph nodes and adds them to the current node at \a position.
+
+ If \a selectionStart is >= 0, then this marks the first character in a selected area of
+ \a selectionCount number of characters. The selection is represented as a background fill with
+ the \l selectionColor() and the selected text is rendered in the \l selectionTextColor().
+
+ For convenience, \a lineStart and \a lineCount can be used to select the range of \l QTextLine
+ objects to include from the layout. This can be useful, for instance, when creating elided
+ layouts. If \a lineCount is < 0, then the the node will include the lines from \a lineStart to
+ the end of the layout.
+
+ \sa clear(), addTextLayout()
+*/
+
+/*!
+ \fn QSGTextNode::doAddTextDocument(QPointF position, QTextDocument *document, int selectionStart, int selectionCount)
+
+ Virtual function called by addTextDocument(), which converts the contents of \a document to
+ scene graph nodes and adds them to the current node at \a position.
+
+ If \a selectionStart is >= 0, then this marks the first character in a selected area of
+ \a selectionCount number of characters. The selection is represented as a background fill with
+ the \l selectionColor() and the selected text is rendered in the \l selectionTextColor().
+
+ \sa clear(), addTextDocument()
+*/
+
+/*!
+ \fn QSGTextNode::clear()
+
+ Clears the contents of the node, deleting nodes and other data that represents the layouts
+ and documents that have been added to it.
+
+ \sa addTextLayout(), addTextDocument()
+*/
+
+QSGTextNode::~QSGTextNode() = default;
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsgtextnode.h b/src/quick/scenegraph/util/qsgtextnode.h
new file mode 100644
index 0000000000..47431929af
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtextnode.h
@@ -0,0 +1,101 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGTEXTNODE_H
+#define QSGTEXTNODE_H
+
+#include <QtGui/qtextlayout.h>
+#include <QtQuick/qsgnode.h>
+#include <QtQuick/qsgtexture.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QSGTextNode : public QSGTransformNode
+{
+public:
+ ~QSGTextNode() override;
+
+ // Should match the TextStyle in qquicktext_p.h
+ enum TextStyle : quint8
+ {
+ Normal,
+ Outline,
+ Raised,
+ Sunken
+ };
+
+ // Should match the RenderType in qquicktext_p.h
+ enum RenderType: quint8
+ {
+ QtRendering,
+ NativeRendering,
+ CurveRendering
+ };
+
+ virtual void setColor(QColor color) = 0;
+ virtual QColor color() const = 0;
+
+ virtual void setTextStyle(TextStyle textStyle) = 0;
+ virtual TextStyle textStyle() = 0;
+
+ virtual void setStyleColor(QColor styleColor) = 0;
+ virtual QColor styleColor() const = 0;
+
+ virtual void setLinkColor(QColor linkColor) = 0;
+ virtual QColor linkColor() const = 0;
+
+ virtual void setSelectionColor(QColor selectionColor) = 0;
+ virtual QColor selectionColor() const = 0;
+
+ virtual void setSelectionTextColor(QColor selectionTextColor) = 0;
+ virtual QColor selectionTextColor() const = 0;
+
+ virtual void setRenderType(RenderType renderType) = 0;
+ virtual RenderType renderType() const = 0;
+
+ virtual void setRenderTypeQuality(int renderTypeQuality) = 0;
+ virtual int renderTypeQuality() const = 0;
+
+ virtual void setFiltering(QSGTexture::Filtering) = 0;
+ virtual QSGTexture::Filtering filtering() const = 0;
+
+ virtual void clear() = 0;
+
+ virtual void setViewport(const QRectF &viewport) = 0;
+ virtual QRectF viewport() const = 0;
+
+ void addTextLayout(QPointF position,
+ QTextLayout *layout,
+ int selectionStart = -1,
+ int selectionCount = -1,
+ int lineStart = 0,
+ int lineCount = -1)
+ {
+ doAddTextLayout(position, layout, selectionStart, selectionCount, lineStart, lineCount);
+ }
+
+ void addTextDocument(QPointF position,
+ QTextDocument *document,
+ int selectionStart = -1,
+ int selectionCount = -1)
+ {
+ doAddTextDocument(position, document, selectionStart, selectionCount);
+ }
+
+private:
+ virtual void doAddTextLayout(QPointF position,
+ QTextLayout *layout,
+ int selectionStart,
+ int selectionCount,
+ int lineStart,
+ int lineCount) = 0;
+ virtual void doAddTextDocument(QPointF position,
+ QTextDocument *document,
+ int selectionStart,
+ int selectionCount) = 0;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGTEXTNODE_H
diff --git a/src/quick/scenegraph/util/qsgtexturematerial.cpp b/src/quick/scenegraph/util/qsgtexturematerial.cpp
index 6a02aef992..c573284f47 100644
--- a/src/quick/scenegraph/util/qsgtexturematerial.cpp
+++ b/src/quick/scenegraph/util/qsgtexturematerial.cpp
@@ -3,7 +3,7 @@
#include "qsgtexturematerial_p.h"
#include <private/qsgtexture_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -13,21 +13,24 @@ inline static bool isPowerOfTwo(int x)
return x == (x & -x);
}
-QSGOpaqueTextureMaterialRhiShader::QSGOpaqueTextureMaterialRhiShader()
+QSGOpaqueTextureMaterialRhiShader::QSGOpaqueTextureMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.frag.qsb"), viewCount);
}
-bool QSGOpaqueTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *)
+bool QSGOpaqueTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
{
bool changed = false;
QByteArray *buf = state.uniformData();
+ const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
return changed;
@@ -142,7 +145,7 @@ QSGMaterialType *QSGOpaqueTextureMaterial::type() const
QSGMaterialShader *QSGOpaqueTextureMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QSGOpaqueTextureMaterialRhiShader;
+ return new QSGOpaqueTextureMaterialRhiShader(viewCount());
}
@@ -333,24 +336,26 @@ QSGMaterialType *QSGTextureMaterial::type() const
QSGMaterialShader *QSGTextureMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QSGTextureMaterialRhiShader;
+ return new QSGTextureMaterialRhiShader(viewCount());
}
-QSGTextureMaterialRhiShader::QSGTextureMaterialRhiShader()
+QSGTextureMaterialRhiShader::QSGTextureMaterialRhiShader(int viewCount)
+ : QSGOpaqueTextureMaterialRhiShader(viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.frag.qsb"), viewCount);
}
bool QSGTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
bool changed = false;
QByteArray *buf = state.uniformData();
+ const int shaderMatrixCount = newMaterial->viewCount();
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 64, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
changed = true;
}
diff --git a/src/quick/scenegraph/util/qsgtexturematerial_p.h b/src/quick/scenegraph/util/qsgtexturematerial_p.h
index 5472ea4aa6..9334d6126b 100644
--- a/src/quick/scenegraph/util/qsgtexturematerial_p.h
+++ b/src/quick/scenegraph/util/qsgtexturematerial_p.h
@@ -20,10 +20,10 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_PRIVATE_EXPORT QSGOpaqueTextureMaterialRhiShader : public QSGMaterialShader
+class Q_QUICK_EXPORT QSGOpaqueTextureMaterialRhiShader : public QSGMaterialShader
{
public:
- QSGOpaqueTextureMaterialRhiShader();
+ QSGOpaqueTextureMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
@@ -32,7 +32,7 @@ public:
class QSGTextureMaterialRhiShader : public QSGOpaqueTextureMaterialRhiShader
{
public:
- QSGTextureMaterialRhiShader();
+ QSGTextureMaterialRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
diff --git a/src/quick/scenegraph/util/qsgtransform.cpp b/src/quick/scenegraph/util/qsgtransform.cpp
new file mode 100644
index 0000000000..7efb014c33
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtransform.cpp
@@ -0,0 +1,10 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgtransform_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QMatrix4x4 QSGTransform::m_identity;
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qsgtransform_p.h b/src/quick/scenegraph/util/qsgtransform_p.h
new file mode 100644
index 0000000000..f19d7a0efd
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgtransform_p.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGTRANSFORM_P_H
+#define QSGTRANSFORM_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QSharedPointer>
+#include <QMatrix4x4>
+#include <QtQuick/qtquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_EXPORT QSGTransform
+{
+public:
+ void setMatrix(const QMatrix4x4 &matrix)
+ {
+ if (matrix.isIdentity())
+ m_matrixPtr.clear();
+ else
+ m_matrixPtr = QSharedPointer<QMatrix4x4>::create(matrix);
+ m_invertedPtr.clear();
+ }
+
+ QMatrix4x4 matrix() const
+ {
+ return m_matrixPtr ? *m_matrixPtr : m_identity;
+ }
+
+ bool isIdentity() const
+ {
+ return !m_matrixPtr;
+ }
+
+ bool operator==(const QMatrix4x4 &other) const
+ {
+ return m_matrixPtr ? (other == *m_matrixPtr) : other.isIdentity();
+ }
+
+ bool operator!=(const QMatrix4x4 &other) const
+ {
+ return !(*this == other);
+ }
+
+ bool operator==(const QSGTransform &other) const
+ {
+ return (m_matrixPtr == other.m_matrixPtr)
+ || (m_matrixPtr && other.m_matrixPtr && *m_matrixPtr == *other.m_matrixPtr);
+ }
+
+ bool operator!=(const QSGTransform &other) const
+ {
+ return !(*this == other);
+ }
+
+ int compareTo(const QSGTransform &other) const
+ {
+ int diff = 0;
+ if (m_matrixPtr != other.m_matrixPtr) {
+ if (m_matrixPtr.isNull()) {
+ diff = -1;
+ } else if (other.m_matrixPtr.isNull()) {
+ diff = 1;
+ } else {
+ const float *ptr1 = m_matrixPtr->constData();
+ const float *ptr2 = other.m_matrixPtr->constData();
+ for (int i = 0; i < 16 && !diff; i++) {
+ float d = ptr1[i] - ptr2[i];
+ if (d != 0)
+ diff = (d > 0) ? 1 : -1;
+ }
+ }
+ }
+ return diff;
+ }
+
+ const float *invertedData() const
+ {
+ if (!m_matrixPtr)
+ return m_identity.constData();
+ if (!m_invertedPtr)
+ m_invertedPtr = QSharedPointer<QMatrix4x4>::create(m_matrixPtr->inverted());
+ return m_invertedPtr->constData();
+ }
+
+private:
+ static QMatrix4x4 m_identity;
+ QSharedPointer<QMatrix4x4> m_matrixPtr;
+ mutable QSharedPointer<QMatrix4x4> m_invertedPtr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGTRANSFORM_P_H
diff --git a/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp b/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp
index 7d5250f5ea..0d2643effa 100644
--- a/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp
+++ b/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp
@@ -8,33 +8,35 @@ QT_BEGIN_NAMESPACE
class QSGVertexColorMaterialRhiShader : public QSGMaterialShader
{
public:
- QSGVertexColorMaterialRhiShader();
+ QSGVertexColorMaterialRhiShader(int viewCount);
- bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
+ bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
-QSGVertexColorMaterialRhiShader::QSGVertexColorMaterialRhiShader()
+QSGVertexColorMaterialRhiShader::QSGVertexColorMaterialRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.frag.qsb"), viewCount);
}
-bool QSGVertexColorMaterialRhiShader::updateUniformData(RenderState &state,
- QSGMaterial * /*newEffect*/,
- QSGMaterial * /*oldEffect*/)
+bool QSGVertexColorMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
{
bool changed = false;
QByteArray *buf = state.uniformData();
-
- if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 64, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
changed = true;
}
@@ -114,7 +116,7 @@ QSGMaterialType *QSGVertexColorMaterial::type() const
QSGMaterialShader *QSGVertexColorMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QSGVertexColorMaterialRhiShader;
+ return new QSGVertexColorMaterialRhiShader(viewCount());
}
QT_END_NAMESPACE