aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquickpathitemnvprrenderer.cpp
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2016-12-03 21:30:12 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2016-12-19 17:03:46 +0000
commitc9023c28764e70cd1c6f9cfc3506e6185299548e (patch)
tree1578cf3ea1f97b024f036b923cb1e02fee0b92cc /src/quick/items/qquickpathitemnvprrenderer.cpp
parent25277124d8b7581a9390702ad3d4bcd4be8dedd7 (diff)
Add QQuickPathItem and its backend infra
The generic backend uses the triangulator from QtGui, but is in fact OpenGL-only for now due to materials. The NVPR backend uses GL_NV_path_rendering on NVIDIA hardware with OpenGL 4.3+ or OpenGL ES 3.1+. The software backend simply uses QPainter. With the generic backend each PathItem is backed by a non-visual root node and 0, 1 or 2 child geometry nodes, depending on the presence of visible stroking and filling. The potentially expensive triangulation happens on updatePolish(), on the gui thread. This is proven to provide much smoother results when compared to doing the geometry generation on the render thread in updatePaintNode(), in particular on power-limited embedded devices. The NVPR backend uses a QSGRenderNode in DepthAware mode so that the batch renderer can continue to rely on the depth buffer and use opaque batches. Due to not relying on slow CPU-side triangulation, this backend uses 5-10 times less CPU, even when properties of the path or its elements are animated. The path itself is specified with the PathView's Path, PathLine, PathArc, PathQuad, etc. types. This allows for consistency with PathView and the 2D Canvas and avoids a naming mess in the API. However, there won't be a 100% symmetry: backends like NVPR will not rely on QPainterPath but process the path elements on their own (as QPainterPath is essentially useless with these APIs), which can lead to differences in the supported path elements. The supported common set is currently Move, Line, Quad, Cubic, Arc. The patch introduces PathMove, which is essentially PathLine but maps to moveTo instead of lineTo. More types may get added later (e.g. NVPR can do a wide variety of optimized rounded rects, but this requires directly specifying a GL_ROUNDED_RECTx_NV command, thus neededing a dedicated Path type on our side too) For filling with gradients only linear gradients are supported at the moment. In addition to the declarative API, a more lightweight, QObject-less JS-callable API should be considered as well for the future. Change-Id: I335ad64b425ee279505d60e3e57ac6841e1cbd24 Reviewed-by: Andy Nichols <andy.nichols@qt.io>
Diffstat (limited to 'src/quick/items/qquickpathitemnvprrenderer.cpp')
-rw-r--r--src/quick/items/qquickpathitemnvprrenderer.cpp567
1 files changed, 567 insertions, 0 deletions
diff --git a/src/quick/items/qquickpathitemnvprrenderer.cpp b/src/quick/items/qquickpathitemnvprrenderer.cpp
new file mode 100644
index 0000000000..d07a63c86d
--- /dev/null
+++ b/src/quick/items/qquickpathitemnvprrenderer.cpp
@@ -0,0 +1,567 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickpathitemnvprrenderer_p.h"
+#include <QOpenGLExtraFunctions>
+#include <private/qquickpath_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+void QQuickPathItemNvprRenderer::beginSync()
+{
+ // nothing to do here
+}
+
+void QQuickPathItemNvprRenderer::setPath(const QQuickPath *path)
+{
+ convertPath(path);
+ m_dirty |= DirtyPath;
+}
+
+void QQuickPathItemNvprRenderer::setStrokeColor(const QColor &color)
+{
+ m_strokeColor = color;
+ m_dirty |= DirtyStyle;
+}
+
+void QQuickPathItemNvprRenderer::setStrokeWidth(qreal w)
+{
+ m_strokeWidth = w;
+ m_dirty |= DirtyStyle;
+}
+
+void QQuickPathItemNvprRenderer::setFillColor(const QColor &color)
+{
+ m_fillColor = color;
+ m_dirty |= DirtyStyle;
+}
+
+void QQuickPathItemNvprRenderer::setFillRule(QQuickPathItem::FillRule fillRule)
+{
+ m_fillRule = fillRule;
+ m_dirty |= DirtyFillRule;
+}
+
+void QQuickPathItemNvprRenderer::setJoinStyle(QQuickPathItem::JoinStyle joinStyle, int miterLimit)
+{
+ m_joinStyle = joinStyle;
+ m_miterLimit = miterLimit;
+ m_dirty |= DirtyStyle;
+}
+
+void QQuickPathItemNvprRenderer::setCapStyle(QQuickPathItem::CapStyle capStyle)
+{
+ m_capStyle = capStyle;
+ m_dirty |= DirtyStyle;
+}
+
+void QQuickPathItemNvprRenderer::setStrokeStyle(QQuickPathItem::StrokeStyle strokeStyle,
+ qreal dashOffset, const QVector<qreal> &dashPattern)
+{
+ m_dashActive = strokeStyle == QQuickPathItem::DashLine;
+ m_dashOffset = dashOffset;
+ m_dashPattern = dashPattern;
+ m_dirty |= DirtyDash;
+}
+
+void QQuickPathItemNvprRenderer::setFillGradient(QQuickPathGradient *gradient)
+{
+ m_fillGradientActive = gradient != nullptr;
+ if (gradient) {
+ m_fillGradient.stops = gradient->sortedGradientStops();
+ m_fillGradient.spread = gradient->spread();
+ if (QQuickPathLinearGradient *g = qobject_cast<QQuickPathLinearGradient *>(gradient)) {
+ m_fillGradient.start = QPointF(g->x1(), g->y1());
+ m_fillGradient.end = QPointF(g->x2(), g->y2());
+ } else {
+ Q_UNREACHABLE();
+ }
+ }
+ m_dirty |= DirtyFillGradient;
+}
+
+void QQuickPathItemNvprRenderer::endSync()
+{
+ // nothing to do here
+}
+
+void QQuickPathItemNvprRenderer::setNode(QQuickPathItemNvprRenderNode *node)
+{
+ if (m_node != node) {
+ m_node = node;
+ // Scenegraph nodes can be destroyed and then replaced by new ones over
+ // time; hence it is important to mark everything dirty for
+ // updatePathRenderNode(). We can assume the renderer has a full sync
+ // of the data at this point.
+ m_dirty = DirtyAll;
+ }
+}
+
+QDebug operator<<(QDebug debug, const QQuickPathItemNvprRenderer::NvprPath &path)
+{
+ QDebugStateSaver saver(debug);
+ debug.space().noquote();
+ debug << "Path with" << path.cmd.count() << "commands";
+ int ci = 0;
+ for (GLubyte cmd : path.cmd) {
+ static struct { GLubyte cmd; const char *s; int coordCount; } nameTab[] = {
+ { GL_MOVE_TO_NV, "moveTo", 2 },
+ { GL_LINE_TO_NV, "lineTo", 2 },
+ { GL_QUADRATIC_CURVE_TO_NV, "quadTo", 4 },
+ { GL_CUBIC_CURVE_TO_NV, "cubicTo", 6 },
+ { GL_LARGE_CW_ARC_TO_NV, "arcTo-large-CW", 5 },
+ { GL_LARGE_CCW_ARC_TO_NV, "arcTo-large-CCW", 5 },
+ { GL_SMALL_CW_ARC_TO_NV, "arcTo-small-CW", 5 },
+ { GL_SMALL_CCW_ARC_TO_NV, "arcTo-small-CCW", 5 },
+ { GL_CLOSE_PATH_NV, "closePath", 0 } };
+ for (size_t i = 0; i < sizeof(nameTab) / sizeof(nameTab[0]); ++i) {
+ if (nameTab[i].cmd == cmd) {
+ QByteArray cs;
+ for (int j = 0; j < nameTab[i].coordCount; ++j) {
+ cs.append(QByteArray::number(path.coord[ci++]));
+ cs.append(' ');
+ }
+ debug << "\n " << nameTab[i].s << " " << cs;
+ break;
+ }
+ }
+ }
+ return debug;
+}
+
+static inline void appendCoords(QVector<GLfloat> *v, QQuickCurve *c, QPointF *pos)
+{
+ QPointF p(c->hasRelativeX() ? pos->x() + c->relativeX() : c->x(),
+ c->hasRelativeY() ? pos->y() + c->relativeY() : c->y());
+ v->append(p.x());
+ v->append(p.y());
+ *pos = p;
+}
+
+static inline void appendControlCoords(QVector<GLfloat> *v, QQuickPathQuad *c, const QPointF &pos)
+{
+ QPointF p(c->hasRelativeControlX() ? pos.x() + c->relativeControlX() : c->controlX(),
+ c->hasRelativeControlY() ? pos.y() + c->relativeControlY() : c->controlY());
+ v->append(p.x());
+ v->append(p.y());
+}
+
+static inline void appendControl1Coords(QVector<GLfloat> *v, QQuickPathCubic *c, const QPointF &pos)
+{
+ QPointF p(c->hasRelativeControl1X() ? pos.x() + c->relativeControl1X() : c->control1X(),
+ c->hasRelativeControl1Y() ? pos.y() + c->relativeControl1Y() : c->control1Y());
+ v->append(p.x());
+ v->append(p.y());
+}
+
+static inline void appendControl2Coords(QVector<GLfloat> *v, QQuickPathCubic *c, const QPointF &pos)
+{
+ QPointF p(c->hasRelativeControl2X() ? pos.x() + c->relativeControl2X() : c->control2X(),
+ c->hasRelativeControl2Y() ? pos.y() + c->relativeControl2Y() : c->control2Y());
+ v->append(p.x());
+ v->append(p.y());
+}
+
+void QQuickPathItemNvprRenderer::convertPath(const QQuickPath *path)
+{
+ m_path = NvprPath();
+ if (!path)
+ return;
+
+ const QList<QQuickPathElement *> &pp(QQuickPathPrivate::get(path)->_pathElements);
+ if (pp.isEmpty())
+ return;
+
+ QPointF pos(path->startX(), path->startY());
+ m_path.cmd.append(GL_MOVE_TO_NV);
+ m_path.coord.append(pos.x());
+ m_path.coord.append(pos.y());
+
+ for (QQuickPathElement *e : pp) {
+ if (QQuickPathMove *o = qobject_cast<QQuickPathMove *>(e)) {
+ m_path.cmd.append(GL_MOVE_TO_NV);
+ appendCoords(&m_path.coord, o, &pos);
+ } else if (QQuickPathLine *o = qobject_cast<QQuickPathLine *>(e)) {
+ m_path.cmd.append(GL_LINE_TO_NV);
+ appendCoords(&m_path.coord, o, &pos);
+ } else if (QQuickPathQuad *o = qobject_cast<QQuickPathQuad *>(e)) {
+ m_path.cmd.append(GL_QUADRATIC_CURVE_TO_NV);
+ appendControlCoords(&m_path.coord, o, pos);
+ appendCoords(&m_path.coord, o, &pos);
+ } else if (QQuickPathCubic *o = qobject_cast<QQuickPathCubic *>(e)) {
+ m_path.cmd.append(GL_CUBIC_CURVE_TO_NV);
+ appendControl1Coords(&m_path.coord, o, pos);
+ appendControl2Coords(&m_path.coord, o, pos);
+ appendCoords(&m_path.coord, o, &pos);
+ } else if (QQuickPathArc *o = qobject_cast<QQuickPathArc *>(e)) {
+ const bool sweepFlag = o->direction() == QQuickPathArc::Clockwise; // maps to CCW, not a typo
+ GLenum cmd;
+ if (o->useLargeArc())
+ cmd = sweepFlag ? GL_LARGE_CCW_ARC_TO_NV : GL_LARGE_CW_ARC_TO_NV;
+ else
+ cmd = sweepFlag ? GL_SMALL_CCW_ARC_TO_NV : GL_SMALL_CW_ARC_TO_NV;
+ m_path.cmd.append(cmd);
+ m_path.coord.append(o->radiusX());
+ m_path.coord.append(o->radiusY());
+ m_path.coord.append(0.0f); // X axis rotation
+ appendCoords(&m_path.coord, o, &pos);
+ } else {
+ qWarning() << "PathItem/NVPR: unsupported Path element" << e;
+ }
+ }
+
+ if (qFuzzyCompare(pos.x(), path->startX()) && qFuzzyCompare(pos.y(), path->startY()))
+ m_path.cmd.append(GL_CLOSE_PATH_NV);
+}
+
+static inline QVector4D qsg_premultiply(const QColor &c, float globalOpacity)
+{
+ const float o = c.alphaF() * globalOpacity;
+ return QVector4D(c.redF() * o, c.greenF() * o, c.blueF() * o, o);
+}
+
+void QQuickPathItemNvprRenderer::updatePathRenderNode()
+{
+ // Called on the render thread with gui blocked -> update the node with its
+ // own copy of all relevant data.
+
+ if (!m_dirty)
+ return;
+
+ // updatePathRenderNode() can be called several times with different dirty
+ // state before render() gets invoked. So accumulate.
+ m_node->m_dirty |= m_dirty;
+
+ if (m_dirty & DirtyPath)
+ m_node->m_source = m_path;
+
+ if (m_dirty & DirtyStyle) {
+ m_node->m_strokeWidth = m_strokeWidth;
+ m_node->m_strokeColor = qsg_premultiply(m_strokeColor, 1.0f);
+ m_node->m_fillColor = qsg_premultiply(m_fillColor, 1.0f);
+ switch (m_joinStyle) {
+ case QQuickPathItem::MiterJoin:
+ m_node->m_joinStyle = GL_MITER_TRUNCATE_NV;
+ break;
+ case QQuickPathItem::BevelJoin:
+ m_node->m_joinStyle = GL_BEVEL_NV;
+ break;
+ case QQuickPathItem::RoundJoin:
+ m_node->m_joinStyle = GL_ROUND_NV;
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ m_node->m_miterLimit = m_miterLimit;
+ switch (m_capStyle) {
+ case QQuickPathItem::FlatCap:
+ m_node->m_capStyle = GL_FLAT;
+ break;
+ case QQuickPathItem::SquareCap:
+ m_node->m_capStyle = GL_SQUARE_NV;
+ break;
+ case QQuickPathItem::RoundCap:
+ m_node->m_capStyle = GL_ROUND_NV;
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ }
+
+ if (m_dirty & DirtyFillRule) {
+ switch (m_fillRule) {
+ case QQuickPathItem::OddEvenFill:
+ m_node->m_fillRule = GL_COUNT_UP_NV;
+ break;
+ case QQuickPathItem::WindingFill:
+ m_node->m_fillRule = GL_INVERT;
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ }
+
+ if (m_dirty & DirtyDash) {
+ m_node->m_dashOffset = m_dashOffset;
+ if (m_dashActive) {
+ m_node->m_dashPattern.resize(m_dashPattern.count());
+ // Multiply by strokeWidth because the PathItem API follows QPen
+ // meaning the input dash pattern here is in width units.
+ for (int i = 0; i < m_dashPattern.count(); ++i)
+ m_node->m_dashPattern[i] = GLfloat(m_dashPattern[i]) * m_strokeWidth;
+ } else {
+ m_node->m_dashPattern.clear();
+ }
+ }
+
+ if (m_dirty & DirtyFillGradient) {
+ m_node->m_fillGradientActive = m_fillGradientActive;
+ if (m_fillGradientActive)
+ m_node->m_fillGradient = m_fillGradient;
+ }
+
+ m_node->markDirty(QSGNode::DirtyMaterial);
+ m_dirty = 0;
+}
+
+bool QQuickPathItemNvprRenderNode::nvprInited = false;
+QQuickNvprFunctions QQuickPathItemNvprRenderNode::nvpr;
+QQuickNvprMaterialManager QQuickPathItemNvprRenderNode::mtlmgr;
+
+QQuickPathItemNvprRenderNode::QQuickPathItemNvprRenderNode(QQuickPathItem *item)
+ : m_item(item)
+{
+}
+
+QQuickPathItemNvprRenderNode::~QQuickPathItemNvprRenderNode()
+{
+ releaseResources();
+}
+
+void QQuickPathItemNvprRenderNode::releaseResources()
+{
+ if (m_path) {
+ nvpr.deletePaths(m_path, 1);
+ m_path = 0;
+ }
+}
+
+void QQuickNvprMaterialManager::create(QQuickNvprFunctions *nvpr)
+{
+ m_nvpr = nvpr;
+}
+
+void QQuickNvprMaterialManager::releaseResources()
+{
+ QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
+ for (MaterialDesc &mtl : m_materials) {
+ if (mtl.ppl) {
+ f->glDeleteProgramPipelines(1, &mtl.ppl);
+ mtl = MaterialDesc();
+ }
+ }
+}
+
+QQuickNvprMaterialManager::MaterialDesc *QQuickNvprMaterialManager::activateMaterial(Material m)
+{
+ QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
+ MaterialDesc &mtl(m_materials[m]);
+
+ if (!mtl.ppl) {
+ if (m == MatSolid) {
+ static const char *fragSrc =
+ "#version 310 es\n"
+ "precision highp float;\n"
+ "out vec4 fragColor;\n"
+ "uniform vec4 color;\n"
+ "uniform float opacity;\n"
+ "void main() {\n"
+ " fragColor = color * opacity;\n"
+ "}\n";
+ if (!m_nvpr->createFragmentOnlyPipeline(fragSrc, &mtl.ppl, &mtl.prg)) {
+ qWarning("NVPR: Failed to create shader pipeline for solid fill");
+ return nullptr;
+ }
+ Q_ASSERT(mtl.ppl && mtl.prg);
+ mtl.uniLoc[0] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "color");
+ Q_ASSERT(mtl.uniLoc[0] >= 0);
+ mtl.uniLoc[1] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "opacity");
+ Q_ASSERT(mtl.uniLoc[1] >= 0);
+ } else if (m == MatLinearGradient) {
+ static const char *fragSrc =
+ "#version 310 es\n"
+ "precision highp float;\n"
+ "layout(location = 0) in vec2 uv;"
+ "uniform float opacity;\n"
+ "uniform sampler2D gradTab;\n"
+ "uniform vec2 gradStart;\n"
+ "uniform vec2 gradEnd;\n"
+ "out vec4 fragColor;\n"
+ "void main() {\n"
+ " vec2 gradVec = gradEnd - gradStart;\n"
+ " float gradTabIndex = dot(gradVec, uv - gradStart) / (gradVec.x * gradVec.x + gradVec.y * gradVec.y);\n"
+ " fragColor = texture(gradTab, vec2(gradTabIndex, 0.5)) * opacity;\n"
+ "}\n";
+ if (!m_nvpr->createFragmentOnlyPipeline(fragSrc, &mtl.ppl, &mtl.prg)) {
+ qWarning("NVPR: Failed to create shader pipeline for linear gradient");
+ return nullptr;
+ }
+ Q_ASSERT(mtl.ppl && mtl.prg);
+ mtl.uniLoc[1] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "opacity");
+ Q_ASSERT(mtl.uniLoc[1] >= 0);
+ mtl.uniLoc[2] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "gradStart");
+ Q_ASSERT(mtl.uniLoc[2] >= 0);
+ mtl.uniLoc[3] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "gradEnd");
+ Q_ASSERT(mtl.uniLoc[3] >= 0);
+ } else {
+ Q_UNREACHABLE();
+ }
+ }
+
+ f->glBindProgramPipeline(mtl.ppl);
+
+ return &mtl;
+}
+
+void QQuickPathItemNvprRenderNode::updatePath()
+{
+ if (m_dirty & QQuickPathItemNvprRenderer::DirtyPath) {
+ if (!m_path) {
+ m_path = nvpr.genPaths(1);
+ Q_ASSERT(m_path != 0);
+ }
+ nvpr.pathCommands(m_path, m_source.cmd.count(), m_source.cmd.constData(),
+ m_source.coord.count(), GL_FLOAT, m_source.coord.constData());
+ }
+
+ if (m_dirty & QQuickPathItemNvprRenderer::DirtyStyle) {
+ nvpr.pathParameterf(m_path, GL_PATH_STROKE_WIDTH_NV, m_strokeWidth);
+ nvpr.pathParameteri(m_path, GL_PATH_JOIN_STYLE_NV, m_joinStyle);
+ nvpr.pathParameteri(m_path, GL_PATH_MITER_LIMIT_NV, m_miterLimit);
+ nvpr.pathParameteri(m_path, GL_PATH_END_CAPS_NV, m_capStyle);
+ nvpr.pathParameteri(m_path, GL_PATH_DASH_CAPS_NV, m_capStyle);
+ }
+
+ if (m_dirty & QQuickPathItemNvprRenderer::DirtyDash) {
+ nvpr.pathParameterf(m_path, GL_PATH_DASH_OFFSET_NV, m_dashOffset);
+ // count == 0 -> no dash
+ nvpr.pathDashArray(m_path, m_dashPattern.count(), m_dashPattern.constData());
+ }
+}
+
+void QQuickPathItemNvprRenderNode::render(const RenderState *state)
+{
+ if (!nvprInited) {
+ if (!nvpr.create()) {
+ qWarning("NVPR init failed");
+ return;
+ }
+ mtlmgr.create(&nvpr);
+ nvprInited = true;
+ }
+
+ updatePath();
+
+ QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
+ f->glUseProgram(0);
+ QQuickNvprMaterialManager::MaterialDesc *mtl;
+ if (m_fillGradientActive)
+ mtl = mtlmgr.activateMaterial(QQuickNvprMaterialManager::MatLinearGradient);
+ else
+ mtl = mtlmgr.activateMaterial(QQuickNvprMaterialManager::MatSolid);
+ if (!mtl)
+ return;
+
+ // Assume stencil buffer is cleared to 0 for each frame.
+ // Within the frame dppass=GL_ZERO for glStencilOp ensures stencil is reset and so no need to clear.
+ f->glStencilMask(~0);
+ f->glEnable(GL_STENCIL_TEST);
+ f->glStencilFunc(GL_NOTEQUAL, 0, 0xFF);
+ f->glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+
+ // Depth test against the opaque batches rendered before.
+ f->glEnable(GL_DEPTH_TEST);
+ f->glDepthFunc(GL_LESS);
+ nvpr.pathCoverDepthFunc(GL_LESS);
+ nvpr.pathStencilDepthOffset(-0.05f, -1);
+
+ nvpr.matrixLoadf(GL_PATH_MODELVIEW_NV, matrix()->constData());
+ nvpr.matrixLoadf(GL_PATH_PROJECTION_NV, state->projectionMatrix()->constData());
+
+ if (state->scissorEnabled()) {
+ // scissor rect is already set, just enable scissoring
+ f->glEnable(GL_SCISSOR_TEST);
+ }
+
+ if (!qFuzzyIsNull(m_fillColor.w()) || m_fillGradientActive) {
+ if (m_fillGradientActive) {
+ QSGTexture *tx = QQuickPathItemGradientCache::currentCache()->get(m_fillGradient);
+ tx->bind();
+ // uv = vec2(coeff[0] * x + coeff[1] * y + coeff[2], coeff[3] * x + coeff[4] * y + coeff[5])
+ // where x and y are in path coordinate space, which is just what
+ // we need since the gradient's start and stop are in that space too.
+ GLfloat coeff[6] = { 1, 0, 0,
+ 0, 1, 0 };
+ nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff);
+ f->glProgramUniform2f(mtl->prg, mtl->uniLoc[2], m_fillGradient.start.x(), m_fillGradient.start.y());
+ f->glProgramUniform2f(mtl->prg, mtl->uniLoc[3], m_fillGradient.end.x(), m_fillGradient.end.y());
+ } else {
+ f->glProgramUniform4f(mtl->prg, mtl->uniLoc[0],
+ m_fillColor.x(), m_fillColor.y(), m_fillColor.z(), m_fillColor.w());
+ }
+ f->glProgramUniform1f(mtl->prg, mtl->uniLoc[1], inheritedOpacity());
+ nvpr.stencilThenCoverFillPath(m_path, m_fillRule, 0xFF, GL_BOUNDING_BOX_NV);
+ }
+
+ if (!qFuzzyIsNull(m_strokeWidth) && !qFuzzyIsNull(m_strokeColor.w())) {
+ if (m_fillGradientActive)
+ mtl = mtlmgr.activateMaterial(QQuickNvprMaterialManager::MatSolid);
+ f->glProgramUniform4f(mtl->prg, mtl->uniLoc[0],
+ m_strokeColor.x(), m_strokeColor.y(), m_strokeColor.z(), m_strokeColor.w());
+ f->glProgramUniform1f(mtl->prg, mtl->uniLoc[1], inheritedOpacity());
+ nvpr.stencilThenCoverStrokePath(m_path, 0x1, ~0, GL_CONVEX_HULL_NV);
+ }
+
+ f->glBindProgramPipeline(0);
+
+ m_dirty = 0;
+}
+
+QSGRenderNode::StateFlags QQuickPathItemNvprRenderNode::changedStates() const
+{
+ return BlendState | StencilState | DepthState | ScissorState;
+}
+
+QSGRenderNode::RenderingFlags QQuickPathItemNvprRenderNode::flags() const
+{
+ return DepthAwareRendering; // avoid hitting the less optimal no-opaque-batch path in the renderer
+}
+
+QRectF QQuickPathItemNvprRenderNode::rect() const
+{
+ return QRect(0, 0, m_item->width(), m_item->height());
+}
+
+bool QQuickPathItemNvprRenderNode::isSupported()
+{
+ static const bool nvprDisabled = qEnvironmentVariableIntValue("QT_NO_NVPR") != 0;
+ return !nvprDisabled && QQuickNvprFunctions::isSupported();
+}
+
+QT_END_NAMESPACE