1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickshapenvprrenderer_p.h"
41#include <QOpenGLExtraFunctions>
42#include <QOpenGLFramebufferObject>
43#include <QOpenGLShaderProgram>
44#include <QOpenGLBuffer>
45#include <qmath.h>
46#include <private/qpainterpath_p.h>
47#include <private/qquickpath_p_p.h>
48
49QT_BEGIN_NAMESPACE
50
51void QQuickShapeNvprRenderer::beginSync(int totalCount)
52{
53 if (m_sp.count() != totalCount) {
54 m_sp.resize(asize: totalCount);
55 m_accDirty |= DirtyList;
56 }
57}
58
59void QQuickShapeNvprRenderer::setPath(int index, const QQuickPath *path)
60{
61 ShapePathGuiData &d(m_sp[index]);
62 convertPath(path, d: &d);
63 d.dirty |= DirtyPath;
64 m_accDirty |= DirtyPath;
65}
66
67void QQuickShapeNvprRenderer::setStrokeColor(int index, const QColor &color)
68{
69 ShapePathGuiData &d(m_sp[index]);
70 d.strokeColor = color;
71 d.dirty |= DirtyStyle;
72 m_accDirty |= DirtyStyle;
73}
74
75void QQuickShapeNvprRenderer::setStrokeWidth(int index, qreal w)
76{
77 ShapePathGuiData &d(m_sp[index]);
78 d.strokeWidth = w;
79 d.dirty |= DirtyStyle;
80 m_accDirty |= DirtyStyle;
81}
82
83void QQuickShapeNvprRenderer::setFillColor(int index, const QColor &color)
84{
85 ShapePathGuiData &d(m_sp[index]);
86 d.fillColor = color;
87 d.dirty |= DirtyStyle;
88 m_accDirty |= DirtyStyle;
89}
90
91void QQuickShapeNvprRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
92{
93 ShapePathGuiData &d(m_sp[index]);
94 d.fillRule = fillRule;
95 d.dirty |= DirtyFillRule;
96 m_accDirty |= DirtyFillRule;
97}
98
99void QQuickShapeNvprRenderer::setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit)
100{
101 ShapePathGuiData &d(m_sp[index]);
102 d.joinStyle = joinStyle;
103 d.miterLimit = miterLimit;
104 d.dirty |= DirtyStyle;
105 m_accDirty |= DirtyStyle;
106}
107
108void QQuickShapeNvprRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
109{
110 ShapePathGuiData &d(m_sp[index]);
111 d.capStyle = capStyle;
112 d.dirty |= DirtyStyle;
113 m_accDirty |= DirtyStyle;
114}
115
116void QQuickShapeNvprRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
117 qreal dashOffset, const QVector<qreal> &dashPattern)
118{
119 ShapePathGuiData &d(m_sp[index]);
120 d.dashActive = strokeStyle == QQuickShapePath::DashLine;
121 d.dashOffset = dashOffset;
122 d.dashPattern = dashPattern;
123 d.dirty |= DirtyDash;
124 m_accDirty |= DirtyDash;
125}
126
127void QQuickShapeNvprRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
128{
129 ShapePathGuiData &d(m_sp[index]);
130 if (gradient) {
131 d.fillGradient.stops = gradient->gradientStops(); // sorted
132 d.fillGradient.spread = gradient->spread();
133 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(object: gradient)) {
134 d.fillGradientActive = LinearGradient;
135 d.fillGradient.a = QPointF(g->x1(), g->y1());
136 d.fillGradient.b = QPointF(g->x2(), g->y2());
137 } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(object: gradient)) {
138 d.fillGradientActive = RadialGradient;
139 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
140 d.fillGradient.b = QPointF(g->focalX(), g->focalY());
141 d.fillGradient.v0 = g->centerRadius();
142 d.fillGradient.v1 = g->focalRadius();
143 } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(object: gradient)) {
144 d.fillGradientActive = ConicalGradient;
145 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
146 d.fillGradient.v0 = g->angle();
147 } else {
148 Q_UNREACHABLE();
149 }
150 } else {
151 d.fillGradientActive = NoGradient;
152 }
153 d.dirty |= DirtyFillGradient;
154 m_accDirty |= DirtyFillGradient;
155}
156
157void QQuickShapeNvprRenderer::endSync(bool)
158{
159}
160
161void QQuickShapeNvprRenderer::setNode(QQuickShapeNvprRenderNode *node)
162{
163 if (m_node != node) {
164 m_node = node;
165 m_accDirty |= DirtyList;
166 }
167}
168
169QDebug operator<<(QDebug debug, const QQuickShapeNvprRenderer::NvprPath &path)
170{
171 QDebugStateSaver saver(debug);
172 debug.space().noquote();
173 if (!path.str.isEmpty()) {
174 debug << "Path with SVG string" << path.str;
175 return debug;
176 }
177 debug << "Path with" << path.cmd.count() << "commands";
178 int ci = 0;
179 for (GLubyte cmd : path.cmd) {
180 static struct { GLubyte cmd; const char *s; int coordCount; } nameTabs[] = {
181 { GL_MOVE_TO_NV, .s: "moveTo", .coordCount: 2 },
182 { GL_LINE_TO_NV, .s: "lineTo", .coordCount: 2 },
183 { GL_QUADRATIC_CURVE_TO_NV, .s: "quadTo", .coordCount: 4 },
184 { GL_CUBIC_CURVE_TO_NV, .s: "cubicTo", .coordCount: 6 },
185 { GL_LARGE_CW_ARC_TO_NV, .s: "arcTo-large-CW", .coordCount: 5 },
186 { GL_LARGE_CCW_ARC_TO_NV, .s: "arcTo-large-CCW", .coordCount: 5 },
187 { GL_SMALL_CW_ARC_TO_NV, .s: "arcTo-small-CW", .coordCount: 5 },
188 { GL_SMALL_CCW_ARC_TO_NV, .s: "arcTo-small-CCW", .coordCount: 5 },
189 { GL_CLOSE_PATH_NV, .s: "closePath", .coordCount: 0 } };
190 for (const auto &nameTab : nameTabs) {
191 if (nameTab.cmd == cmd) {
192 QByteArray cs;
193 for (int j = 0; j < nameTab.coordCount; ++j) {
194 cs.append(a: QByteArray::number(path.coord[ci++]));
195 cs.append(c: ' ');
196 }
197 debug << "\n " << nameTab.s << " " << cs;
198 break;
199 }
200 }
201 }
202 return debug;
203}
204
205static inline void appendCoords(QVector<GLfloat> *v, QQuickCurve *c, QPointF *pos)
206{
207 QPointF p(c->hasRelativeX() ? pos->x() + c->relativeX() : c->x(),
208 c->hasRelativeY() ? pos->y() + c->relativeY() : c->y());
209 v->append(t: p.x());
210 v->append(t: p.y());
211 *pos = p;
212}
213
214static inline void appendControlCoords(QVector<GLfloat> *v, QQuickPathQuad *c, const QPointF &pos)
215{
216 QPointF p(c->hasRelativeControlX() ? pos.x() + c->relativeControlX() : c->controlX(),
217 c->hasRelativeControlY() ? pos.y() + c->relativeControlY() : c->controlY());
218 v->append(t: p.x());
219 v->append(t: p.y());
220}
221
222static inline void appendControl1Coords(QVector<GLfloat> *v, QQuickPathCubic *c, const QPointF &pos)
223{
224 QPointF p(c->hasRelativeControl1X() ? pos.x() + c->relativeControl1X() : c->control1X(),
225 c->hasRelativeControl1Y() ? pos.y() + c->relativeControl1Y() : c->control1Y());
226 v->append(t: p.x());
227 v->append(t: p.y());
228}
229
230static inline void appendControl2Coords(QVector<GLfloat> *v, QQuickPathCubic *c, const QPointF &pos)
231{
232 QPointF p(c->hasRelativeControl2X() ? pos.x() + c->relativeControl2X() : c->control2X(),
233 c->hasRelativeControl2Y() ? pos.y() + c->relativeControl2Y() : c->control2Y());
234 v->append(t: p.x());
235 v->append(t: p.y());
236}
237
238void QQuickShapeNvprRenderer::convertPath(const QQuickPath *path, ShapePathGuiData *d)
239{
240 d->path = NvprPath();
241 if (!path)
242 return;
243
244 const QList<QQuickPathElement *> &pp(QQuickPathPrivate::get(path)->_pathElements);
245 if (pp.isEmpty())
246 return;
247
248 QPointF startPos(path->startX(), path->startY());
249 QPointF pos(startPos);
250 if (!qFuzzyIsNull(d: pos.x()) || !qFuzzyIsNull(d: pos.y())) {
251 d->path.cmd.append(GL_MOVE_TO_NV);
252 d->path.coord.append(t: pos.x());
253 d->path.coord.append(t: pos.y());
254 }
255
256 for (QQuickPathElement *e : pp) {
257 if (QQuickPathMove *o = qobject_cast<QQuickPathMove *>(object: e)) {
258 d->path.cmd.append(GL_MOVE_TO_NV);
259 appendCoords(v: &d->path.coord, c: o, pos: &pos);
260 startPos = pos;
261 } else if (QQuickPathLine *o = qobject_cast<QQuickPathLine *>(object: e)) {
262 d->path.cmd.append(GL_LINE_TO_NV);
263 appendCoords(v: &d->path.coord, c: o, pos: &pos);
264 } else if (QQuickPathQuad *o = qobject_cast<QQuickPathQuad *>(object: e)) {
265 d->path.cmd.append(GL_QUADRATIC_CURVE_TO_NV);
266 appendControlCoords(v: &d->path.coord, c: o, pos);
267 appendCoords(v: &d->path.coord, c: o, pos: &pos);
268 } else if (QQuickPathCubic *o = qobject_cast<QQuickPathCubic *>(object: e)) {
269 d->path.cmd.append(GL_CUBIC_CURVE_TO_NV);
270 appendControl1Coords(v: &d->path.coord, c: o, pos);
271 appendControl2Coords(v: &d->path.coord, c: o, pos);
272 appendCoords(v: &d->path.coord, c: o, pos: &pos);
273 } else if (QQuickPathArc *o = qobject_cast<QQuickPathArc *>(object: e)) {
274 const bool sweepFlag = o->direction() == QQuickPathArc::Clockwise; // maps to CCW, not a typo
275 GLenum cmd;
276 if (o->useLargeArc())
277 cmd = sweepFlag ? GL_LARGE_CCW_ARC_TO_NV : GL_LARGE_CW_ARC_TO_NV;
278 else
279 cmd = sweepFlag ? GL_SMALL_CCW_ARC_TO_NV : GL_SMALL_CW_ARC_TO_NV;
280 d->path.cmd.append(t: cmd);
281 d->path.coord.append(t: o->radiusX());
282 d->path.coord.append(t: o->radiusY());
283 d->path.coord.append(t: o->xAxisRotation());
284 appendCoords(v: &d->path.coord, c: o, pos: &pos);
285 } else if (QQuickPathSvg *o = qobject_cast<QQuickPathSvg *>(object: e)) {
286 // PathSvg cannot be combined with other elements. But take at
287 // least startX and startY into account.
288 if (d->path.str.isEmpty())
289 d->path.str = QString(QStringLiteral("M %1 %2 ")).arg(a: pos.x()).arg(a: pos.y()).toUtf8();
290 d->path.str.append(a: o->path().toUtf8());
291 } else if (QQuickPathAngleArc *o = qobject_cast<QQuickPathAngleArc *>(object: e)) {
292 QRectF rect(o->centerX() - o->radiusX(), o->centerY() - o->radiusY(), o->radiusX() * 2, o->radiusY() * 2);
293 QPointF startPoint;
294 QPointF endPoint;
295 qt_find_ellipse_coords(r: rect, angle: o->startAngle(), length: -o->sweepAngle(), startPoint: &startPoint, endPoint: &endPoint);
296
297 // get to our starting position
298 if (o->moveToStart())
299 d->path.cmd.append(GL_MOVE_TO_NV);
300 else
301 d->path.cmd.append(GL_LINE_TO_NV); // ### should we check if startPoint == pos?
302 d->path.coord.append(t: startPoint.x());
303 d->path.coord.append(t: startPoint.y());
304
305 const bool sweepFlag = o->sweepAngle() > 0; // maps to CCW, not a typo
306 d->path.cmd.append(t: qAbs(t: o->sweepAngle()) > 180.0
307 ? (sweepFlag ? GL_LARGE_CCW_ARC_TO_NV : GL_LARGE_CW_ARC_TO_NV)
308 : (sweepFlag ? GL_SMALL_CCW_ARC_TO_NV : GL_SMALL_CW_ARC_TO_NV));
309 d->path.coord.append(t: o->radiusX());
310 d->path.coord.append(t: o->radiusY());
311 d->path.coord.append(t: 0); // xAxisRotation
312 d->path.coord.append(t: endPoint.x());
313 d->path.coord.append(t: endPoint.y());
314 pos = endPoint;
315 } else {
316 qWarning() << "Shape/NVPR: unsupported Path element" << e;
317 }
318 }
319
320 // For compatibility with QTriangulatingStroker. SVG and others would not
321 // implicitly close the path when end_pos == start_pos (start_pos being the
322 // last moveTo pos); that would still need an explicit 'z' or similar. We
323 // don't have an explicit close command, so just fake a close when the
324 // positions match.
325 if (pos == startPos)
326 d->path.cmd.append(GL_CLOSE_PATH_NV);
327}
328
329static inline QVector4D qsg_premultiply(const QColor &c, float globalOpacity)
330{
331 const float o = c.alphaF() * globalOpacity;
332 return QVector4D(c.redF() * o, c.greenF() * o, c.blueF() * o, o);
333}
334
335void QQuickShapeNvprRenderer::updateNode()
336{
337 // Called on the render thread with gui blocked -> update the node with its
338 // own copy of all relevant data.
339
340 if (!m_accDirty)
341 return;
342
343 const int count = m_sp.count();
344 const bool listChanged = m_accDirty & DirtyList;
345 if (listChanged)
346 m_node->m_sp.resize(asize: count);
347
348 for (int i = 0; i < count; ++i) {
349 ShapePathGuiData &src(m_sp[i]);
350 QQuickShapeNvprRenderNode::ShapePathRenderData &dst(m_node->m_sp[i]);
351
352 int dirty = src.dirty;
353 src.dirty = 0;
354 if (listChanged)
355 dirty |= DirtyPath | DirtyStyle | DirtyFillRule | DirtyDash | DirtyFillGradient;
356
357 // updateNode() can be called several times with different dirty
358 // states before render() gets invoked. So accumulate.
359 dst.dirty |= dirty;
360
361 if (dirty & DirtyPath)
362 dst.source = src.path;
363
364 if (dirty & DirtyStyle) {
365 dst.strokeWidth = src.strokeWidth;
366 dst.strokeColor = qsg_premultiply(c: src.strokeColor, globalOpacity: 1.0f);
367 dst.fillColor = qsg_premultiply(c: src.fillColor, globalOpacity: 1.0f);
368 switch (src.joinStyle) {
369 case QQuickShapePath::MiterJoin:
370 dst.joinStyle = GL_MITER_TRUNCATE_NV;
371 break;
372 case QQuickShapePath::BevelJoin:
373 dst.joinStyle = GL_BEVEL_NV;
374 break;
375 case QQuickShapePath::RoundJoin:
376 dst.joinStyle = GL_ROUND_NV;
377 break;
378 default:
379 Q_UNREACHABLE();
380 }
381 dst.miterLimit = src.miterLimit;
382 switch (src.capStyle) {
383 case QQuickShapePath::FlatCap:
384 dst.capStyle = GL_FLAT;
385 break;
386 case QQuickShapePath::SquareCap:
387 dst.capStyle = GL_SQUARE_NV;
388 break;
389 case QQuickShapePath::RoundCap:
390 dst.capStyle = GL_ROUND_NV;
391 break;
392 default:
393 Q_UNREACHABLE();
394 }
395 }
396
397 if (dirty & DirtyFillRule) {
398 switch (src.fillRule) {
399 case QQuickShapePath::OddEvenFill:
400 dst.fillRule = GL_INVERT;
401 break;
402 case QQuickShapePath::WindingFill:
403 dst.fillRule = GL_COUNT_UP_NV;
404 break;
405 default:
406 Q_UNREACHABLE();
407 }
408 }
409
410 if (dirty & DirtyDash) {
411 // Multiply by strokeWidth because the Shape API follows QPen
412 // meaning the input dash pattern and dash offset here are in width units.
413 dst.dashOffset = src.dashOffset * src.strokeWidth;
414 if (src.dashActive) {
415 if (src.dashPattern.isEmpty()) {
416 // default values for DashLine as defined in qpen.cpp
417 dst.dashPattern.resize(asize: 2);
418 dst.dashPattern[0] = 4 * src.strokeWidth; // dash
419 dst.dashPattern[1] = 2 * src.strokeWidth; // space
420 } else {
421 dst.dashPattern.resize(asize: src.dashPattern.count());
422 for (int i = 0; i < src.dashPattern.count(); ++i)
423 dst.dashPattern[i] = GLfloat(src.dashPattern[i]) * src.strokeWidth;
424
425 // QPen expects a dash pattern of even length and so should we
426 if (src.dashPattern.count() % 2 != 0) {
427 qWarning(msg: "QQuickShapeNvprRenderNode: dash pattern not of even length");
428 dst.dashPattern << src.strokeWidth;
429 }
430 }
431 } else {
432 dst.dashPattern.clear();
433 }
434 }
435
436 if (dirty & DirtyFillGradient) {
437 dst.fillGradientActive = src.fillGradientActive;
438 if (src.fillGradientActive)
439 dst.fillGradient = src.fillGradient;
440 }
441 }
442
443 m_node->markDirty(bits: QSGNode::DirtyMaterial);
444 m_accDirty = 0;
445}
446
447bool QQuickShapeNvprRenderNode::nvprInited = false;
448QQuickNvprFunctions QQuickShapeNvprRenderNode::nvpr;
449QQuickNvprMaterialManager QQuickShapeNvprRenderNode::mtlmgr;
450
451QQuickShapeNvprRenderNode::~QQuickShapeNvprRenderNode()
452{
453 releaseResources();
454}
455
456void QQuickShapeNvprRenderNode::releaseResources()
457{
458 for (ShapePathRenderData &d : m_sp) {
459 if (d.path) {
460 nvpr.deletePaths(d.path, 1);
461 d.path = 0;
462 }
463 if (d.fallbackFbo) {
464 delete d.fallbackFbo;
465 d.fallbackFbo = nullptr;
466 }
467 }
468
469 m_fallbackBlitter.destroy();
470}
471
472void QQuickNvprMaterialManager::create(QQuickNvprFunctions *nvpr)
473{
474 m_nvpr = nvpr;
475}
476
477void QQuickNvprMaterialManager::releaseResources()
478{
479 QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
480 for (MaterialDesc &mtl : m_materials) {
481 if (mtl.ppl) {
482 f->glDeleteProgramPipelines(n: 1, pipelines: &mtl.ppl);
483 mtl = MaterialDesc();
484 }
485 }
486}
487
488QQuickNvprMaterialManager::MaterialDesc *QQuickNvprMaterialManager::activateMaterial(Material m)
489{
490 QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
491 MaterialDesc &mtl(m_materials[m]);
492
493 if (!mtl.ppl) {
494 if (m == MatSolid) {
495 static const char *fragSrc =
496 "#version 310 es\n"
497 "precision highp float;\n"
498 "out vec4 fragColor;\n"
499 "uniform vec4 color;\n"
500 "uniform float opacity;\n"
501 "void main() {\n"
502 " fragColor = color * opacity;\n"
503 "}\n";
504 if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) {
505 qWarning(msg: "NVPR: Failed to create shader pipeline for solid fill");
506 return nullptr;
507 }
508 Q_ASSERT(mtl.ppl && mtl.prg);
509 mtl.uniLoc[0] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "color");
510 Q_ASSERT(mtl.uniLoc[0] >= 0);
511 mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity");
512 Q_ASSERT(mtl.uniLoc[1] >= 0);
513 } else if (m == MatLinearGradient) {
514 static const char *fragSrc =
515 "#version 310 es\n"
516 "precision highp float;\n"
517 "layout(location = 0) in vec2 uv;"
518 "uniform float opacity;\n"
519 "uniform sampler2D gradTab;\n"
520 "uniform vec2 gradStart;\n"
521 "uniform vec2 gradEnd;\n"
522 "out vec4 fragColor;\n"
523 "void main() {\n"
524 " vec2 gradVec = gradEnd - gradStart;\n"
525 " float gradTabIndex = dot(gradVec, uv - gradStart) / (gradVec.x * gradVec.x + gradVec.y * gradVec.y);\n"
526 " fragColor = texture(gradTab, vec2(gradTabIndex, 0.5)) * opacity;\n"
527 "}\n";
528 if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) {
529 qWarning(msg: "NVPR: Failed to create shader pipeline for linear gradient");
530 return nullptr;
531 }
532 Q_ASSERT(mtl.ppl && mtl.prg);
533 mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity");
534 Q_ASSERT(mtl.uniLoc[1] >= 0);
535 mtl.uniLoc[2] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "gradStart");
536 Q_ASSERT(mtl.uniLoc[2] >= 0);
537 mtl.uniLoc[3] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "gradEnd");
538 Q_ASSERT(mtl.uniLoc[3] >= 0);
539 } else if (m == MatRadialGradient) {
540 static const char *fragSrc =
541 "#version 310 es\n"
542 "precision highp float;\n"
543 "uniform sampler2D gradTab;\n"
544 "uniform float opacity;\n"
545 "uniform vec2 focalToCenter;\n"
546 "uniform float centerRadius;\n"
547 "uniform float focalRadius;\n"
548 "uniform vec2 translationPoint;\n"
549 "layout(location = 0) in vec2 uv;\n"
550 "out vec4 fragColor;\n"
551 "void main() {\n"
552 " vec2 coord = uv - translationPoint;\n"
553 " float rd = centerRadius - focalRadius;\n"
554 " float b = 2.0 * (rd * focalRadius + dot(coord, focalToCenter));\n"
555 " float fmp2_m_radius2 = -focalToCenter.x * focalToCenter.x - focalToCenter.y * focalToCenter.y + rd * rd;\n"
556 " float inverse_2_fmp2_m_radius2 = 1.0 / (2.0 * fmp2_m_radius2);\n"
557 " float det = b * b - 4.0 * fmp2_m_radius2 * ((focalRadius * focalRadius) - dot(coord, coord));\n"
558 " vec4 result = vec4(0.0);\n"
559 " if (det >= 0.0) {\n"
560 " float detSqrt = sqrt(det);\n"
561 " float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2);\n"
562 " if (focalRadius + w * (centerRadius - focalRadius) >= 0.0)\n"
563 " result = texture(gradTab, vec2(w, 0.5)) * opacity;\n"
564 " }\n"
565 " fragColor = result;\n"
566 "}\n";
567 if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) {
568 qWarning(msg: "NVPR: Failed to create shader pipeline for radial gradient");
569 return nullptr;
570 }
571 Q_ASSERT(mtl.ppl && mtl.prg);
572 mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity");
573 Q_ASSERT(mtl.uniLoc[1] >= 0);
574 mtl.uniLoc[2] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "focalToCenter");
575 Q_ASSERT(mtl.uniLoc[2] >= 0);
576 mtl.uniLoc[3] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "centerRadius");
577 Q_ASSERT(mtl.uniLoc[3] >= 0);
578 mtl.uniLoc[4] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "focalRadius");
579 Q_ASSERT(mtl.uniLoc[4] >= 0);
580 mtl.uniLoc[5] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "translationPoint");
581 Q_ASSERT(mtl.uniLoc[5] >= 0);
582 } else if (m == MatConicalGradient) {
583 static const char *fragSrc =
584 "#version 310 es\n"
585 "precision highp float;\n"
586 "#define INVERSE_2PI 0.1591549430918953358\n"
587 "uniform sampler2D gradTab;\n"
588 "uniform float opacity;\n"
589 "uniform float angle;\n"
590 "uniform vec2 translationPoint;\n"
591 "layout(location = 0) in vec2 uv;\n"
592 "out vec4 fragColor;\n"
593 "void main() {\n"
594 " vec2 coord = uv - translationPoint;\n"
595 " float t;\n"
596 " if (abs(coord.y) == abs(coord.x))\n"
597 " t = (atan(-coord.y + 0.002, coord.x) + angle) * INVERSE_2PI;\n"
598 " else\n"
599 " t = (atan(-coord.y, coord.x) + angle) * INVERSE_2PI;\n"
600 " fragColor = texture(gradTab, vec2(t - floor(t), 0.5)) * opacity;\n"
601 "}\n";
602 if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) {
603 qWarning(msg: "NVPR: Failed to create shader pipeline for conical gradient");
604 return nullptr;
605 }
606 Q_ASSERT(mtl.ppl && mtl.prg);
607 mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity");
608 Q_ASSERT(mtl.uniLoc[1] >= 0);
609 mtl.uniLoc[2] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "angle");
610 Q_ASSERT(mtl.uniLoc[2] >= 0);
611 mtl.uniLoc[3] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "translationPoint");
612 Q_ASSERT(mtl.uniLoc[3] >= 0);
613 } else {
614 Q_UNREACHABLE();
615 }
616 }
617
618 f->glBindProgramPipeline(pipeline: mtl.ppl);
619
620 return &mtl;
621}
622
623void QQuickShapeNvprRenderNode::updatePath(ShapePathRenderData *d)
624{
625 if (d->dirty & QQuickShapeNvprRenderer::DirtyPath) {
626 if (!d->path) {
627 d->path = nvpr.genPaths(1);
628 Q_ASSERT(d->path != 0);
629 }
630 if (d->source.str.isEmpty()) {
631 nvpr.pathCommands(d->path, d->source.cmd.count(), d->source.cmd.constData(),
632 d->source.coord.count(), GL_FLOAT, d->source.coord.constData());
633 } else {
634 nvpr.pathString(d->path, GL_PATH_FORMAT_SVG_NV, d->source.str.count(), d->source.str.constData());
635 }
636 }
637
638 if (d->dirty & QQuickShapeNvprRenderer::DirtyStyle) {
639 nvpr.pathParameterf(d->path, GL_PATH_STROKE_WIDTH_NV, d->strokeWidth);
640 nvpr.pathParameteri(d->path, GL_PATH_JOIN_STYLE_NV, d->joinStyle);
641 nvpr.pathParameteri(d->path, GL_PATH_MITER_LIMIT_NV, d->miterLimit);
642 nvpr.pathParameteri(d->path, GL_PATH_END_CAPS_NV, d->capStyle);
643 nvpr.pathParameteri(d->path, GL_PATH_DASH_CAPS_NV, d->capStyle);
644 }
645
646 if (d->dirty & QQuickShapeNvprRenderer::DirtyDash) {
647 nvpr.pathParameterf(d->path, GL_PATH_DASH_OFFSET_NV, d->dashOffset);
648 // count == 0 -> no dash
649 nvpr.pathDashArray(d->path, d->dashPattern.count(), d->dashPattern.constData());
650 }
651
652 if (d->dirty)
653 d->fallbackValid = false;
654}
655
656void QQuickShapeNvprRenderNode::renderStroke(ShapePathRenderData *d, int strokeStencilValue, int writeMask)
657{
658 QQuickNvprMaterialManager::MaterialDesc *mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatSolid);
659 f->glProgramUniform4f(program: mtl->prg, location: mtl->uniLoc[0],
660 v0: d->strokeColor.x(), v1: d->strokeColor.y(), v2: d->strokeColor.z(), v3: d->strokeColor.w());
661 f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[1], v0: inheritedOpacity());
662
663 nvpr.stencilThenCoverStrokePath(d->path, strokeStencilValue, writeMask, GL_CONVEX_HULL_NV);
664}
665
666void QQuickShapeNvprRenderNode::renderFill(ShapePathRenderData *d)
667{
668 QQuickNvprMaterialManager::MaterialDesc *mtl = nullptr;
669 if (d->fillGradientActive) {
670 QQuickShapeGradient::SpreadMode spread = d->fillGradient.spread;
671 if (d->fillGradientActive == QQuickAbstractPathRenderer::LinearGradient) {
672 mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatLinearGradient);
673 // uv = vec2(coeff[0] * x + coeff[1] * y + coeff[2], coeff[3] * x + coeff[4] * y + coeff[5])
674 // where x and y are in path coordinate space, which is just what
675 // we need since the gradient's start and stop are in that space too.
676 GLfloat coeff[6] = { 1, 0, 0,
677 0, 1, 0 };
678 nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff);
679 f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[2], v0: d->fillGradient.a.x(), v1: d->fillGradient.a.y());
680 f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[3], v0: d->fillGradient.b.x(), v1: d->fillGradient.b.y());
681 } else if (d->fillGradientActive == QQuickAbstractPathRenderer::RadialGradient) {
682 mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatRadialGradient);
683 // simply drive uv (location 0) with x and y, just like for the linear gradient
684 GLfloat coeff[6] = { 1, 0, 0,
685 0, 1, 0 };
686 nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff);
687
688 const QPointF centerPoint = d->fillGradient.a;
689 const QPointF focalPoint = d->fillGradient.b;
690 const QPointF focalToCenter = centerPoint - focalPoint;
691 const GLfloat centerRadius = d->fillGradient.v0;
692 const GLfloat focalRadius = d->fillGradient.v1;
693
694 f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[2], v0: focalToCenter.x(), v1: focalToCenter.y());
695 f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[3], v0: centerRadius);
696 f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[4], v0: focalRadius);
697 f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[5], v0: focalPoint.x(), v1: focalPoint.y());
698 } else if (d->fillGradientActive == QQuickAbstractPathRenderer::ConicalGradient) {
699 mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatConicalGradient);
700 // same old
701 GLfloat coeff[6] = { 1, 0, 0,
702 0, 1, 0 };
703 nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff);
704
705 const QPointF centerPoint = d->fillGradient.a;
706 const GLfloat angle = -qDegreesToRadians(degrees: d->fillGradient.v0);
707
708 f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[2], v0: angle);
709 f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[3], v0: centerPoint.x(), v1: centerPoint.y());
710
711 spread = QQuickShapeGradient::RepeatSpread;
712 } else {
713 Q_UNREACHABLE();
714 }
715 const QQuickShapeGradientCacheKey cacheKey(d->fillGradient.stops, spread);
716 QSGTexture *tx = QQuickShapeGradientOpenGLCache::currentCache()->get(grad: cacheKey);
717 tx->bind();
718 } else {
719 mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatSolid);
720 f->glProgramUniform4f(program: mtl->prg, location: mtl->uniLoc[0],
721 v0: d->fillColor.x(), v1: d->fillColor.y(), v2: d->fillColor.z(), v3: d->fillColor.w());
722 }
723 f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[1], v0: inheritedOpacity());
724
725 const int writeMask = 0xFF;
726 nvpr.stencilThenCoverFillPath(d->path, d->fillRule, writeMask, GL_BOUNDING_BOX_NV);
727}
728
729void QQuickShapeNvprRenderNode::renderOffscreenFill(ShapePathRenderData *d)
730{
731 if (d->fallbackValid && d->fallbackFbo)
732 return;
733
734 GLfloat bb[4];
735 nvpr.getPathParameterfv(d->path, GL_PATH_STROKE_BOUNDING_BOX_NV, bb);
736 QSize sz = QSizeF(bb[2] - bb[0] + 1, bb[3] - bb[1] + 1).toSize();
737 d->fallbackSize = QSize(qMax(a: 32, b: sz.width()), qMax(a: 32, b: sz.height()));
738 d->fallbackTopLeft = QPointF(bb[0], bb[1]);
739
740 if (d->fallbackFbo && d->fallbackFbo->size() != d->fallbackSize) {
741 delete d->fallbackFbo;
742 d->fallbackFbo = nullptr;
743 }
744 if (!d->fallbackFbo)
745 d->fallbackFbo = new QOpenGLFramebufferObject(d->fallbackSize, QOpenGLFramebufferObject::CombinedDepthStencil);
746 if (!d->fallbackFbo->bind())
747 return;
748
749 GLint prevViewport[4];
750 f->glGetIntegerv(GL_VIEWPORT, params: prevViewport);
751
752 f->glViewport(x: 0, y: 0, width: d->fallbackSize.width(), height: d->fallbackSize.height());
753 f->glDisable(GL_DEPTH_TEST);
754 f->glClearColor(red: 0, green: 0, blue: 0, alpha: 0);
755 f->glClearStencil(s: 0);
756 f->glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
757 f->glStencilFunc(GL_NOTEQUAL, ref: 0, mask: 0xFF);
758 f->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
759
760 QMatrix4x4 mv;
761 mv.translate(x: -d->fallbackTopLeft.x(), y: -d->fallbackTopLeft.y());
762 nvpr.matrixLoadf(GL_PATH_MODELVIEW_NV, mv.constData());
763 QMatrix4x4 proj;
764 proj.ortho(left: 0, right: d->fallbackSize.width(), bottom: d->fallbackSize.height(), top: 0, nearPlane: 1, farPlane: -1);
765 nvpr.matrixLoadf(GL_PATH_PROJECTION_NV, proj.constData());
766
767 renderFill(d);
768
769 d->fallbackFbo->release();
770 f->glEnable(GL_DEPTH_TEST);
771 f->glViewport(x: prevViewport[0], y: prevViewport[1], width: prevViewport[2], height: prevViewport[3]);
772
773 d->fallbackValid = true;
774}
775
776void QQuickShapeNvprRenderNode::setupStencilForCover(bool stencilClip, int sv)
777{
778 if (!stencilClip) {
779 // Assume stencil buffer is cleared to 0 for each frame.
780 // Within the frame dppass=GL_ZERO for glStencilOp ensures stencil is reset and so no need to clear.
781 f->glStencilFunc(GL_NOTEQUAL, ref: 0, mask: 0xFF);
782 f->glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
783 } else {
784 f->glStencilFunc(GL_LESS, ref: sv, mask: 0xFF); // pass if (sv & 0xFF) < (stencil_value & 0xFF)
785 f->glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // dppass: replace with the original value (clip's stencil ref value)
786 }
787}
788
789void QQuickShapeNvprRenderNode::render(const RenderState *state)
790{
791 f = QOpenGLContext::currentContext()->extraFunctions();
792
793 if (!nvprInited) {
794 if (!nvpr.create()) {
795 qWarning(msg: "NVPR init failed");
796 return;
797 }
798 mtlmgr.create(nvpr: &nvpr);
799 nvprInited = true;
800 }
801
802 f->glUseProgram(program: 0);
803 f->glStencilMask(mask: ~0);
804 f->glEnable(GL_STENCIL_TEST);
805
806 const bool stencilClip = state->stencilEnabled();
807 // when true, the stencil buffer already has a clip path with a ref value of sv
808 const int sv = state->stencilValue();
809 const bool hasScissor = state->scissorEnabled();
810
811 if (hasScissor) {
812 // scissor rect is already set, just enable scissoring
813 f->glEnable(GL_SCISSOR_TEST);
814 }
815
816 // Depth test against the opaque batches rendered before.
817 f->glEnable(GL_DEPTH_TEST);
818 f->glDepthFunc(GL_LESS);
819 nvpr.pathCoverDepthFunc(GL_LESS);
820 nvpr.pathStencilDepthOffset(-0.05f, -1);
821
822 bool reloadMatrices = true;
823
824 for (ShapePathRenderData &d : m_sp) {
825 updatePath(d: &d);
826
827 const bool hasFill = d.hasFill();
828 const bool hasStroke = d.hasStroke();
829
830 if (hasFill && stencilClip) {
831 // Fall back to a texture when complex clipping is in use and we have
832 // to fill. Reconciling glStencilFillPath's and the scenegraph's clip
833 // stencil semantics has not succeeded so far...
834 if (hasScissor)
835 f->glDisable(GL_SCISSOR_TEST);
836 renderOffscreenFill(d: &d);
837 reloadMatrices = true;
838 if (hasScissor)
839 f->glEnable(GL_SCISSOR_TEST);
840 }
841
842 if (reloadMatrices) {
843 reloadMatrices = false;
844 nvpr.matrixLoadf(GL_PATH_MODELVIEW_NV, matrix()->constData());
845 nvpr.matrixLoadf(GL_PATH_PROJECTION_NV, state->projectionMatrix()->constData());
846 }
847
848 // Fill!
849 if (hasFill) {
850 if (!stencilClip) {
851 setupStencilForCover(stencilClip: false, sv: 0);
852 renderFill(d: &d);
853 } else {
854 if (!m_fallbackBlitter.isCreated())
855 m_fallbackBlitter.create();
856 f->glStencilFunc(GL_EQUAL, ref: sv, mask: 0xFF);
857 f->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
858 QMatrix4x4 mv = *matrix();
859 mv.translate(x: d.fallbackTopLeft.x(), y: d.fallbackTopLeft.y());
860 m_fallbackBlitter.texturedQuad(textureId: d.fallbackFbo->texture(), size: d.fallbackFbo->size(),
861 proj: *state->projectionMatrix(), modelview: mv,
862 opacity: inheritedOpacity());
863 }
864 }
865
866 // Stroke!
867 if (hasStroke) {
868 const int strokeStencilValue = 0x80;
869 const int writeMask = 0x80;
870
871 setupStencilForCover(stencilClip, sv);
872 if (stencilClip) {
873 // for the stencil step (eff. read mask == 0xFF & ~writeMask)
874 nvpr.pathStencilFunc(GL_EQUAL, sv, 0xFF);
875 // With stencilCLip == true the read mask for the stencil test before the stencil step is 0x7F.
876 // This assumes the clip stencil value is <= 127.
877 if (sv >= strokeStencilValue)
878 qWarning(msg: "Shape/NVPR: stencil clip ref value %d too large; expect rendering errors", sv);
879 }
880
881 renderStroke(d: &d, strokeStencilValue, writeMask);
882 }
883
884 if (stencilClip)
885 nvpr.pathStencilFunc(GL_ALWAYS, 0, ~0);
886
887 d.dirty = 0;
888 }
889
890 f->glBindProgramPipeline(pipeline: 0);
891}
892
893QSGRenderNode::StateFlags QQuickShapeNvprRenderNode::changedStates() const
894{
895 return BlendState | StencilState | DepthState | ScissorState;
896}
897
898QSGRenderNode::RenderingFlags QQuickShapeNvprRenderNode::flags() const
899{
900 return DepthAwareRendering; // avoid hitting the less optimal no-opaque-batch path in the renderer
901}
902
903bool QQuickShapeNvprRenderNode::isSupported()
904{
905 static const bool nvprDisabled = qEnvironmentVariableIntValue(varName: "QT_NO_NVPR") != 0;
906 return !nvprDisabled && QQuickNvprFunctions::isSupported();
907}
908
909bool QQuickNvprBlitter::create()
910{
911 if (isCreated())
912 destroy();
913
914 m_program = new QOpenGLShaderProgram;
915 if (QOpenGLContext::currentContext()->format().profile() == QSurfaceFormat::CoreProfile) {
916 m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/shapes/shaders/blit_core.vert"));
917 m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/shapes/shaders/blit_core.frag"));
918 } else {
919 m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/shapes/shaders/blit.vert"));
920 m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/shapes/shaders/blit.frag"));
921 }
922 m_program->bindAttributeLocation(name: "qt_Vertex", location: 0);
923 m_program->bindAttributeLocation(name: "qt_MultiTexCoord0", location: 1);
924 if (!m_program->link())
925 return false;
926
927 m_matrixLoc = m_program->uniformLocation(name: "qt_Matrix");
928 m_opacityLoc = m_program->uniformLocation(name: "qt_Opacity");
929
930 m_buffer = new QOpenGLBuffer;
931 if (!m_buffer->create())
932 return false;
933 m_buffer->bind();
934 m_buffer->allocate(count: 4 * sizeof(GLfloat) * 6);
935 m_buffer->release();
936
937 return true;
938}
939
940void QQuickNvprBlitter::destroy()
941{
942 if (m_program) {
943 delete m_program;
944 m_program = nullptr;
945 }
946 if (m_buffer) {
947 delete m_buffer;
948 m_buffer = nullptr;
949 }
950}
951
952void QQuickNvprBlitter::texturedQuad(GLuint textureId, const QSize &size,
953 const QMatrix4x4 &proj, const QMatrix4x4 &modelview,
954 float opacity)
955{
956 QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
957
958 m_program->bind();
959
960 QMatrix4x4 m = proj * modelview;
961 m_program->setUniformValue(location: m_matrixLoc, value: m);
962 m_program->setUniformValue(location: m_opacityLoc, value: opacity);
963
964 m_buffer->bind();
965
966 if (size != m_prevSize) {
967 m_prevSize = size;
968
969 QPointF p0(size.width() - 1, size.height() - 1);
970 QPointF p1(0, 0);
971 QPointF p2(0, size.height() - 1);
972 QPointF p3(size.width() - 1, 0);
973
974 GLfloat vertices[6 * 4] = {
975 GLfloat(p0.x()), GLfloat(p0.y()), 1, 0,
976 GLfloat(p1.x()), GLfloat(p1.y()), 0, 1,
977 GLfloat(p2.x()), GLfloat(p2.y()), 0, 0,
978
979 GLfloat(p0.x()), GLfloat(p0.y()), 1, 0,
980 GLfloat(p3.x()), GLfloat(p3.y()), 1, 1,
981 GLfloat(p1.x()), GLfloat(p1.y()), 0, 1,
982 };
983
984 m_buffer->write(offset: 0, data: vertices, count: sizeof(vertices));
985 }
986
987 m_program->enableAttributeArray(location: 0);
988 m_program->enableAttributeArray(location: 1);
989 f->glVertexAttribPointer(indx: 0, size: 2, GL_FLOAT, GL_FALSE, stride: 4 * sizeof(GLfloat), ptr: nullptr);
990 f->glVertexAttribPointer(indx: 1, size: 2, GL_FLOAT, GL_FALSE, stride: 4 * sizeof(GLfloat), ptr: (const void *) (2 * sizeof(GLfloat)));
991
992 f->glBindTexture(GL_TEXTURE_2D, texture: textureId);
993
994 f->glDrawArrays(GL_TRIANGLES, first: 0, count: 6);
995
996 f->glBindTexture(GL_TEXTURE_2D, texture: 0);
997 m_buffer->release();
998 m_program->release();
999}
1000
1001QT_END_NAMESPACE
1002

source code of qtdeclarative/src/quickshapes/qquickshapenvprrenderer.cpp