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 "qquickshapegenericrenderer_p.h"
41#include <QtGui/private/qtriangulator_p.h>
42#include <QtGui/private/qtriangulatingstroker_p.h>
43#include <QSGVertexColorMaterial>
44
45#if QT_CONFIG(thread)
46#include <QThreadPool>
47#endif
48
49#if QT_CONFIG(opengl)
50#include <QOpenGLContext>
51#include <QOffscreenSurface>
52#include <QtGui/private/qopenglextensions_p.h>
53#endif
54
55QT_BEGIN_NAMESPACE
56
57static const qreal TRI_SCALE = 1;
58
59struct ColoredVertex // must match QSGGeometry::ColoredPoint2D
60{
61 float x, y;
62 QQuickShapeGenericRenderer::Color4ub color;
63 void set(float nx, float ny, QQuickShapeGenericRenderer::Color4ub ncolor)
64 {
65 x = nx; y = ny; color = ncolor;
66 }
67};
68
69static inline QQuickShapeGenericRenderer::Color4ub colorToColor4ub(const QColor &c)
70{
71 QQuickShapeGenericRenderer::Color4ub color = {
72 .r: uchar(qRound(d: c.redF() * c.alphaF() * 255)),
73 .g: uchar(qRound(d: c.greenF() * c.alphaF() * 255)),
74 .b: uchar(qRound(d: c.blueF() * c.alphaF() * 255)),
75 .a: uchar(qRound(d: c.alphaF() * 255))
76 };
77 return color;
78}
79
80QQuickShapeGenericStrokeFillNode::QQuickShapeGenericStrokeFillNode(QQuickWindow *window)
81 : m_material(nullptr)
82{
83 setFlag(QSGNode::OwnsGeometry, true);
84 setGeometry(new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0, 0));
85 activateMaterial(window, m: MatSolidColor);
86#ifdef QSG_RUNTIME_DESCRIPTION
87 qsgnode_set_description(node: this, description: QLatin1String("stroke-fill"));
88#endif
89}
90
91void QQuickShapeGenericStrokeFillNode::activateMaterial(QQuickWindow *window, Material m)
92{
93 switch (m) {
94 case MatSolidColor:
95 // Use vertexcolor material. Items with different colors remain batchable
96 // this way, at the expense of having to provide per-vertex color values.
97 m_material.reset(other: QQuickShapeGenericMaterialFactory::createVertexColor(window));
98 break;
99 case MatLinearGradient:
100 m_material.reset(other: QQuickShapeGenericMaterialFactory::createLinearGradient(window, node: this));
101 break;
102 case MatRadialGradient:
103 m_material.reset(other: QQuickShapeGenericMaterialFactory::createRadialGradient(window, node: this));
104 break;
105 case MatConicalGradient:
106 m_material.reset(other: QQuickShapeGenericMaterialFactory::createConicalGradient(window, node: this));
107 break;
108 default:
109 qWarning(msg: "Unknown material %d", m);
110 return;
111 }
112
113 if (material() != m_material.data())
114 setMaterial(m_material.data());
115}
116
117static bool q_supportsElementIndexUint(QSGRendererInterface::GraphicsApi api)
118{
119 static bool elementIndexUint = true;
120#if QT_CONFIG(opengl)
121 if (api == QSGRendererInterface::OpenGL) {
122 static bool elementIndexUintChecked = false;
123 if (!elementIndexUintChecked) {
124 elementIndexUintChecked = true;
125 QOpenGLContext *context = QOpenGLContext::currentContext();
126 const bool needsTempContext = !context;
127 QScopedPointer<QOpenGLContext> dummyContext;
128 QScopedPointer<QOffscreenSurface> dummySurface;
129 bool ok = true;
130 if (needsTempContext) {
131 dummyContext.reset(other: new QOpenGLContext);
132 dummyContext->create();
133 context = dummyContext.data();
134 dummySurface.reset(other: new QOffscreenSurface);
135 dummySurface->setFormat(context->format());
136 dummySurface->create();
137 ok = context->makeCurrent(surface: dummySurface.data());
138 }
139 if (ok) {
140 elementIndexUint = static_cast<QOpenGLExtensions *>(context->functions())->hasOpenGLExtension(
141 extension: QOpenGLExtensions::ElementIndexUint);
142
143 if (needsTempContext) {
144 // Must not let the temprary context be destroyed while current and
145 // the associated surface already gone, because some implementations
146 // (Mesa on drm) do not like that.
147 context->doneCurrent();
148 }
149 }
150 }
151 }
152#else
153 Q_UNUSED(api);
154#endif
155 return elementIndexUint;
156}
157
158QQuickShapeGenericRenderer::~QQuickShapeGenericRenderer()
159{
160 for (ShapePathData &d : m_sp) {
161 if (d.pendingFill)
162 d.pendingFill->orphaned = true;
163 if (d.pendingStroke)
164 d.pendingStroke->orphaned = true;
165 }
166}
167
168// sync, and so triangulation too, happens on the gui thread
169// - except when async is set, in which case triangulation is moved to worker threads
170
171void QQuickShapeGenericRenderer::beginSync(int totalCount)
172{
173 if (m_sp.count() != totalCount) {
174 m_sp.resize(asize: totalCount);
175 m_accDirty |= DirtyList;
176 }
177 for (ShapePathData &d : m_sp)
178 d.syncDirty = 0;
179}
180
181void QQuickShapeGenericRenderer::setPath(int index, const QQuickPath *path)
182{
183 ShapePathData &d(m_sp[index]);
184 d.path = path ? path->path() : QPainterPath();
185 d.syncDirty |= DirtyFillGeom | DirtyStrokeGeom;
186}
187
188void QQuickShapeGenericRenderer::setStrokeColor(int index, const QColor &color)
189{
190 ShapePathData &d(m_sp[index]);
191 d.strokeColor = colorToColor4ub(c: color);
192 d.syncDirty |= DirtyColor;
193}
194
195void QQuickShapeGenericRenderer::setStrokeWidth(int index, qreal w)
196{
197 ShapePathData &d(m_sp[index]);
198 d.strokeWidth = w;
199 if (w >= 0.0f)
200 d.pen.setWidthF(w);
201 d.syncDirty |= DirtyStrokeGeom;
202}
203
204void QQuickShapeGenericRenderer::setFillColor(int index, const QColor &color)
205{
206 ShapePathData &d(m_sp[index]);
207 const bool wasTransparent = d.fillColor.a == 0;
208 d.fillColor = colorToColor4ub(c: color);
209 const bool isTransparent = d.fillColor.a == 0;
210 d.syncDirty |= DirtyColor;
211 if (wasTransparent && !isTransparent)
212 d.syncDirty |= DirtyFillGeom;
213}
214
215void QQuickShapeGenericRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
216{
217 ShapePathData &d(m_sp[index]);
218 d.fillRule = Qt::FillRule(fillRule);
219 d.syncDirty |= DirtyFillGeom;
220}
221
222void QQuickShapeGenericRenderer::setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit)
223{
224 ShapePathData &d(m_sp[index]);
225 d.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
226 d.pen.setMiterLimit(miterLimit);
227 d.syncDirty |= DirtyStrokeGeom;
228}
229
230void QQuickShapeGenericRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
231{
232 ShapePathData &d(m_sp[index]);
233 d.pen.setCapStyle(Qt::PenCapStyle(capStyle));
234 d.syncDirty |= DirtyStrokeGeom;
235}
236
237void QQuickShapeGenericRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
238 qreal dashOffset, const QVector<qreal> &dashPattern)
239{
240 ShapePathData &d(m_sp[index]);
241 d.pen.setStyle(Qt::PenStyle(strokeStyle));
242 if (strokeStyle == QQuickShapePath::DashLine) {
243 d.pen.setDashPattern(dashPattern);
244 d.pen.setDashOffset(dashOffset);
245 }
246 d.syncDirty |= DirtyStrokeGeom;
247}
248
249void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
250{
251 ShapePathData &d(m_sp[index]);
252 if (gradient) {
253 d.fillGradient.stops = gradient->gradientStops(); // sorted
254 d.fillGradient.spread = gradient->spread();
255 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(object: gradient)) {
256 d.fillGradientActive = LinearGradient;
257 d.fillGradient.a = QPointF(g->x1(), g->y1());
258 d.fillGradient.b = QPointF(g->x2(), g->y2());
259 } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(object: gradient)) {
260 d.fillGradientActive = RadialGradient;
261 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
262 d.fillGradient.b = QPointF(g->focalX(), g->focalY());
263 d.fillGradient.v0 = g->centerRadius();
264 d.fillGradient.v1 = g->focalRadius();
265 } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(object: gradient)) {
266 d.fillGradientActive = ConicalGradient;
267 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
268 d.fillGradient.v0 = g->angle();
269 } else {
270 Q_UNREACHABLE();
271 }
272 } else {
273 d.fillGradientActive = NoGradient;
274 }
275 d.syncDirty |= DirtyFillGradient;
276}
277
278void QQuickShapeFillRunnable::run()
279{
280 QQuickShapeGenericRenderer::triangulateFill(path, fillColor, fillVertices: &fillVertices, fillIndices: &fillIndices, indexType: &indexType, supportsElementIndexUint);
281 emit done(self: this);
282}
283
284void QQuickShapeStrokeRunnable::run()
285{
286 QQuickShapeGenericRenderer::triangulateStroke(path, pen, strokeColor, strokeVertices: &strokeVertices, clipSize);
287 emit done(self: this);
288}
289
290void QQuickShapeGenericRenderer::setAsyncCallback(void (*callback)(void *), void *data)
291{
292 m_asyncCallback = callback;
293 m_asyncCallbackData = data;
294}
295
296#if QT_CONFIG(thread)
297static QThreadPool *pathWorkThreadPool = nullptr;
298
299static void deletePathWorkThreadPool()
300{
301 delete pathWorkThreadPool;
302 pathWorkThreadPool = nullptr;
303}
304#endif
305
306void QQuickShapeGenericRenderer::endSync(bool async)
307{
308#if !QT_CONFIG(thread)
309 // Force synchronous mode for the no-thread configuration due
310 // to lack of QThreadPool.
311 async = false;
312#endif
313
314 bool didKickOffAsync = false;
315
316 for (int i = 0; i < m_sp.count(); ++i) {
317 ShapePathData &d(m_sp[i]);
318 if (!d.syncDirty)
319 continue;
320
321 m_accDirty |= d.syncDirty;
322
323 // Use a shadow dirty flag in order to avoid losing state in case there are
324 // multiple syncs with different dirty flags before we get to updateNode()
325 // on the render thread (with the gui thread blocked). For our purposes
326 // here syncDirty is still required since geometry regeneration must only
327 // happen when there was an actual change in this particular sync round.
328 d.effectiveDirty |= d.syncDirty;
329
330 if (d.path.isEmpty()) {
331 d.fillVertices.clear();
332 d.fillIndices.clear();
333 d.strokeVertices.clear();
334 continue;
335 }
336
337#if QT_CONFIG(thread)
338 if (async && !pathWorkThreadPool) {
339 qAddPostRoutine(deletePathWorkThreadPool);
340 pathWorkThreadPool = new QThreadPool;
341 const int idealCount = QThread::idealThreadCount();
342 pathWorkThreadPool->setMaxThreadCount(idealCount > 0 ? idealCount * 2 : 4);
343 }
344#endif
345 if ((d.syncDirty & DirtyFillGeom) && d.fillColor.a) {
346 d.path.setFillRule(d.fillRule);
347 if (m_api == QSGRendererInterface::Unknown)
348 m_api = m_item->window()->rendererInterface()->graphicsApi();
349 if (async) {
350 QQuickShapeFillRunnable *r = new QQuickShapeFillRunnable;
351 r->setAutoDelete(false);
352 if (d.pendingFill)
353 d.pendingFill->orphaned = true;
354 d.pendingFill = r;
355 r->path = d.path;
356 r->fillColor = d.fillColor;
357 r->supportsElementIndexUint = q_supportsElementIndexUint(api: m_api);
358 // Unlikely in practice but in theory m_sp could be
359 // resized. Therefore, capture 'i' instead of 'd'.
360 QObject::connect(sender: r, signal: &QQuickShapeFillRunnable::done, qApp, slot: [this, i](QQuickShapeFillRunnable *r) {
361 // Bail out when orphaned (meaning either another run was
362 // started after this one, or the renderer got destroyed).
363 if (!r->orphaned && i < m_sp.count()) {
364 ShapePathData &d(m_sp[i]);
365 d.fillVertices = r->fillVertices;
366 d.fillIndices = r->fillIndices;
367 d.indexType = r->indexType;
368 d.pendingFill = nullptr;
369 d.effectiveDirty |= DirtyFillGeom;
370 maybeUpdateAsyncItem();
371 }
372 r->deleteLater();
373 });
374 didKickOffAsync = true;
375#if QT_CONFIG(thread)
376 // qtVectorPathForPath() initializes a unique_ptr without locking.
377 // Do that before starting the threads as otherwise we get a race condition.
378 qtVectorPathForPath(path: r->path);
379 pathWorkThreadPool->start(runnable: r);
380#endif
381 } else {
382 triangulateFill(path: d.path, fillColor: d.fillColor, fillVertices: &d.fillVertices, fillIndices: &d.fillIndices, indexType: &d.indexType, supportsElementIndexUint: q_supportsElementIndexUint(api: m_api));
383 }
384 }
385
386 if ((d.syncDirty & DirtyStrokeGeom) && d.strokeWidth >= 0.0f && d.strokeColor.a) {
387 if (async) {
388 QQuickShapeStrokeRunnable *r = new QQuickShapeStrokeRunnable;
389 r->setAutoDelete(false);
390 if (d.pendingStroke)
391 d.pendingStroke->orphaned = true;
392 d.pendingStroke = r;
393 r->path = d.path;
394 r->pen = d.pen;
395 r->strokeColor = d.strokeColor;
396 r->clipSize = QSize(m_item->width(), m_item->height());
397 QObject::connect(sender: r, signal: &QQuickShapeStrokeRunnable::done, qApp, slot: [this, i](QQuickShapeStrokeRunnable *r) {
398 if (!r->orphaned && i < m_sp.count()) {
399 ShapePathData &d(m_sp[i]);
400 d.strokeVertices = r->strokeVertices;
401 d.pendingStroke = nullptr;
402 d.effectiveDirty |= DirtyStrokeGeom;
403 maybeUpdateAsyncItem();
404 }
405 r->deleteLater();
406 });
407 didKickOffAsync = true;
408#if QT_CONFIG(thread)
409 // qtVectorPathForPath() initializes a unique_ptr without locking.
410 // Do that before starting the threads as otherwise we get a race condition.
411 qtVectorPathForPath(path: r->path);
412 pathWorkThreadPool->start(runnable: r);
413#endif
414 } else {
415 triangulateStroke(path: d.path, pen: d.pen, strokeColor: d.strokeColor, strokeVertices: &d.strokeVertices,
416 clipSize: QSize(m_item->width(), m_item->height()));
417 }
418 }
419 }
420
421 if (!didKickOffAsync && async && m_asyncCallback)
422 m_asyncCallback(m_asyncCallbackData);
423}
424
425void QQuickShapeGenericRenderer::maybeUpdateAsyncItem()
426{
427 for (const ShapePathData &d : qAsConst(t&: m_sp)) {
428 if (d.pendingFill || d.pendingStroke)
429 return;
430 }
431 m_accDirty |= DirtyFillGeom | DirtyStrokeGeom;
432 m_item->update();
433 if (m_asyncCallback)
434 m_asyncCallback(m_asyncCallbackData);
435}
436
437// the stroke/fill triangulation functions may be invoked either on the gui
438// thread or some worker thread and must thus be self-contained.
439void QQuickShapeGenericRenderer::triangulateFill(const QPainterPath &path,
440 const Color4ub &fillColor,
441 VertexContainerType *fillVertices,
442 IndexContainerType *fillIndices,
443 QSGGeometry::Type *indexType,
444 bool supportsElementIndexUint)
445{
446 const QVectorPath &vp = qtVectorPathForPath(path);
447
448 QTriangleSet ts = qTriangulate(path: vp, matrix: QTransform::fromScale(dx: TRI_SCALE, dy: TRI_SCALE), lod: 1, allowUintIndices: supportsElementIndexUint);
449 const int vertexCount = ts.vertices.count() / 2; // just a qreal vector with x,y hence the / 2
450 fillVertices->resize(asize: vertexCount);
451 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(fillVertices->data());
452 const qreal *vsrc = ts.vertices.constData();
453 for (int i = 0; i < vertexCount; ++i)
454 vdst[i].set(nx: vsrc[i * 2] / TRI_SCALE, ny: vsrc[i * 2 + 1] / TRI_SCALE, ncolor: fillColor);
455
456 size_t indexByteSize;
457 if (ts.indices.type() == QVertexIndexVector::UnsignedShort) {
458 *indexType = QSGGeometry::UnsignedShortType;
459 // fillIndices is still QVector<quint32>. Just resize to N/2 and pack
460 // the N quint16s into it.
461 fillIndices->resize(asize: ts.indices.size() / 2);
462 indexByteSize = ts.indices.size() * sizeof(quint16);
463 } else {
464 *indexType = QSGGeometry::UnsignedIntType;
465 fillIndices->resize(asize: ts.indices.size());
466 indexByteSize = ts.indices.size() * sizeof(quint32);
467 }
468 memcpy(dest: fillIndices->data(), src: ts.indices.data(), n: indexByteSize);
469}
470
471void QQuickShapeGenericRenderer::triangulateStroke(const QPainterPath &path,
472 const QPen &pen,
473 const Color4ub &strokeColor,
474 VertexContainerType *strokeVertices,
475 const QSize &clipSize)
476{
477 const QVectorPath &vp = qtVectorPathForPath(path);
478 const QRectF clip(QPointF(0, 0), clipSize);
479 const qreal inverseScale = 1.0 / TRI_SCALE;
480
481 QTriangulatingStroker stroker;
482 stroker.setInvScale(inverseScale);
483
484 if (pen.style() == Qt::SolidLine) {
485 stroker.process(path: vp, pen, clip, hints: {});
486 } else {
487 QDashedStrokeProcessor dashStroker;
488 dashStroker.setInvScale(inverseScale);
489 dashStroker.process(path: vp, pen, clip, hints: {});
490 QVectorPath dashStroke(dashStroker.points(), dashStroker.elementCount(),
491 dashStroker.elementTypes(), 0);
492 stroker.process(path: dashStroke, pen, clip, hints: {});
493 }
494
495 if (!stroker.vertexCount()) {
496 strokeVertices->clear();
497 return;
498 }
499
500 const int vertexCount = stroker.vertexCount() / 2; // just a float vector with x,y hence the / 2
501 strokeVertices->resize(asize: vertexCount);
502 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(strokeVertices->data());
503 const float *vsrc = stroker.vertices();
504 for (int i = 0; i < vertexCount; ++i)
505 vdst[i].set(nx: vsrc[i * 2], ny: vsrc[i * 2 + 1], ncolor: strokeColor);
506}
507
508void QQuickShapeGenericRenderer::setRootNode(QQuickShapeGenericNode *node)
509{
510 if (m_rootNode != node) {
511 m_rootNode = node;
512 m_accDirty |= DirtyList;
513 }
514}
515
516// on the render thread with gui blocked
517void QQuickShapeGenericRenderer::updateNode()
518{
519 if (!m_rootNode || !m_accDirty)
520 return;
521
522// [ m_rootNode ]
523// / / /
524// #0 [ fill ] [ stroke ] [ next ]
525// / / |
526// #1 [ fill ] [ stroke ] [ next ]
527// / / |
528// #2 [ fill ] [ stroke ] [ next ]
529// ...
530// ...
531
532 QQuickShapeGenericNode **nodePtr = &m_rootNode;
533 QQuickShapeGenericNode *prevNode = nullptr;
534
535 for (ShapePathData &d : m_sp) {
536 if (!*nodePtr) {
537 Q_ASSERT(prevNode);
538 *nodePtr = new QQuickShapeGenericNode;
539 prevNode->m_next = *nodePtr;
540 prevNode->appendChildNode(node: *nodePtr);
541 }
542
543 QQuickShapeGenericNode *node = *nodePtr;
544
545 if (m_accDirty & DirtyList)
546 d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient;
547
548 if (!d.effectiveDirty) {
549 prevNode = node;
550 nodePtr = &node->m_next;
551 continue;
552 }
553
554 if (d.fillColor.a == 0) {
555 delete node->m_fillNode;
556 node->m_fillNode = nullptr;
557 } else if (!node->m_fillNode) {
558 node->m_fillNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
559 if (node->m_strokeNode)
560 node->removeChildNode(node: node->m_strokeNode);
561 node->appendChildNode(node: node->m_fillNode);
562 if (node->m_strokeNode)
563 node->appendChildNode(node: node->m_strokeNode);
564 d.effectiveDirty |= DirtyFillGeom;
565 }
566
567 if (d.strokeWidth < 0.0f || d.strokeColor.a == 0) {
568 delete node->m_strokeNode;
569 node->m_strokeNode = nullptr;
570 } else if (!node->m_strokeNode) {
571 node->m_strokeNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
572 node->appendChildNode(node: node->m_strokeNode);
573 d.effectiveDirty |= DirtyStrokeGeom;
574 }
575
576 updateFillNode(d: &d, node);
577 updateStrokeNode(d: &d, node);
578
579 d.effectiveDirty = 0;
580
581 prevNode = node;
582 nodePtr = &node->m_next;
583 }
584
585 if (*nodePtr && prevNode) {
586 prevNode->removeChildNode(node: *nodePtr);
587 delete *nodePtr;
588 *nodePtr = nullptr;
589 }
590
591 m_accDirty = 0;
592}
593
594void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuickShapeGenericStrokeFillNode *n)
595{
596 if (d->fillGradientActive) {
597 if (d->effectiveDirty & DirtyFillGradient)
598 n->m_fillGradient = d->fillGradient;
599 }
600}
601
602void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGenericNode *node)
603{
604 if (!node->m_fillNode)
605 return;
606 if (!(d->effectiveDirty & (DirtyFillGeom | DirtyColor | DirtyFillGradient)))
607 return;
608
609 // Make a copy of the data that will be accessed by the material on
610 // the render thread. This must be done even when we bail out below.
611 QQuickShapeGenericStrokeFillNode *n = node->m_fillNode;
612 updateShadowDataInNode(d, n);
613
614 QSGGeometry *g = n->geometry();
615 if (d->fillVertices.isEmpty()) {
616 if (g->vertexCount() || g->indexCount()) {
617 g->allocate(vertexCount: 0, indexCount: 0);
618 n->markDirty(bits: QSGNode::DirtyGeometry);
619 }
620 return;
621 }
622
623 if (d->fillGradientActive) {
624 QQuickShapeGenericStrokeFillNode::Material gradMat;
625 switch (d->fillGradientActive) {
626 case LinearGradient:
627 gradMat = QQuickShapeGenericStrokeFillNode::MatLinearGradient;
628 break;
629 case RadialGradient:
630 gradMat = QQuickShapeGenericStrokeFillNode::MatRadialGradient;
631 break;
632 case ConicalGradient:
633 gradMat = QQuickShapeGenericStrokeFillNode::MatConicalGradient;
634 break;
635 default:
636 Q_UNREACHABLE();
637 return;
638 }
639 n->activateMaterial(window: m_item->window(), m: gradMat);
640 if (d->effectiveDirty & DirtyFillGradient) {
641 // Gradients are implemented via a texture-based material.
642 n->markDirty(bits: QSGNode::DirtyMaterial);
643 // stop here if only the gradient changed; no need to touch the geometry
644 if (!(d->effectiveDirty & DirtyFillGeom))
645 return;
646 }
647 } else {
648 n->activateMaterial(window: m_item->window(), m: QQuickShapeGenericStrokeFillNode::MatSolidColor);
649 // fast path for updating only color values when no change in vertex positions
650 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyFillGeom)) {
651 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
652 for (int i = 0; i < g->vertexCount(); ++i)
653 vdst[i].set(nx: vdst[i].x, ny: vdst[i].y, ncolor: d->fillColor);
654 n->markDirty(bits: QSGNode::DirtyGeometry);
655 return;
656 }
657 }
658
659 const int indexCount = d->indexType == QSGGeometry::UnsignedShortType
660 ? d->fillIndices.count() * 2 : d->fillIndices.count();
661 if (g->indexType() != d->indexType) {
662 g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(),
663 d->fillVertices.count(), indexCount, d->indexType);
664 n->setGeometry(g);
665 } else {
666 g->allocate(vertexCount: d->fillVertices.count(), indexCount);
667 }
668 g->setDrawingMode(QSGGeometry::DrawTriangles);
669 memcpy(dest: g->vertexData(), src: d->fillVertices.constData(), n: g->vertexCount() * g->sizeOfVertex());
670 memcpy(dest: g->indexData(), src: d->fillIndices.constData(), n: g->indexCount() * g->sizeOfIndex());
671
672 n->markDirty(bits: QSGNode::DirtyGeometry);
673}
674
675void QQuickShapeGenericRenderer::updateStrokeNode(ShapePathData *d, QQuickShapeGenericNode *node)
676{
677 if (!node->m_strokeNode)
678 return;
679 if (!(d->effectiveDirty & (DirtyStrokeGeom | DirtyColor)))
680 return;
681
682 QQuickShapeGenericStrokeFillNode *n = node->m_strokeNode;
683 QSGGeometry *g = n->geometry();
684 if (d->strokeVertices.isEmpty()) {
685 if (g->vertexCount() || g->indexCount()) {
686 g->allocate(vertexCount: 0, indexCount: 0);
687 n->markDirty(bits: QSGNode::DirtyGeometry);
688 }
689 return;
690 }
691
692 n->markDirty(bits: QSGNode::DirtyGeometry);
693
694 // Async loading runs update once, bails out above, then updates again once
695 // ready. Set the material dirty then. This is in-line with fill where the
696 // first activateMaterial() achieves the same.
697 if (!g->vertexCount())
698 n->markDirty(bits: QSGNode::DirtyMaterial);
699
700 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyStrokeGeom)) {
701 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
702 for (int i = 0; i < g->vertexCount(); ++i)
703 vdst[i].set(nx: vdst[i].x, ny: vdst[i].y, ncolor: d->strokeColor);
704 return;
705 }
706
707 g->allocate(vertexCount: d->strokeVertices.count(), indexCount: 0);
708 g->setDrawingMode(QSGGeometry::DrawTriangleStrip);
709 memcpy(dest: g->vertexData(), src: d->strokeVertices.constData(), n: g->vertexCount() * g->sizeOfVertex());
710}
711
712QSGMaterial *QQuickShapeGenericMaterialFactory::createVertexColor(QQuickWindow *window)
713{
714 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
715
716 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
717 return new QSGVertexColorMaterial;
718
719 qWarning(msg: "Vertex-color material: Unsupported graphics API %d", api);
720 return nullptr;
721}
722
723QSGMaterial *QQuickShapeGenericMaterialFactory::createLinearGradient(QQuickWindow *window,
724 QQuickShapeGenericStrokeFillNode *node)
725{
726 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
727
728 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
729 return new QQuickShapeLinearGradientMaterial(node);
730
731 qWarning(msg: "Linear gradient material: Unsupported graphics API %d", api);
732 return nullptr;
733}
734
735QSGMaterial *QQuickShapeGenericMaterialFactory::createRadialGradient(QQuickWindow *window,
736 QQuickShapeGenericStrokeFillNode *node)
737{
738 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
739
740 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
741 return new QQuickShapeRadialGradientMaterial(node);
742
743 qWarning(msg: "Radial gradient material: Unsupported graphics API %d", api);
744 return nullptr;
745}
746
747QSGMaterial *QQuickShapeGenericMaterialFactory::createConicalGradient(QQuickWindow *window,
748 QQuickShapeGenericStrokeFillNode *node)
749{
750 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
751
752 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
753 return new QQuickShapeConicalGradientMaterial(node);
754
755 qWarning(msg: "Conical gradient material: Unsupported graphics API %d", api);
756 return nullptr;
757}
758
759#if QT_CONFIG(opengl)
760
761QQuickShapeLinearGradientShader::QQuickShapeLinearGradientShader()
762{
763 setShaderSourceFile(type: QOpenGLShader::Vertex,
764 QStringLiteral(":/qt-project.org/shapes/shaders/lineargradient.vert"));
765 setShaderSourceFile(type: QOpenGLShader::Fragment,
766 QStringLiteral(":/qt-project.org/shapes/shaders/lineargradient.frag"));
767}
768
769void QQuickShapeLinearGradientShader::initialize()
770{
771 m_opacityLoc = program()->uniformLocation(name: "opacity");
772 m_matrixLoc = program()->uniformLocation(name: "matrix");
773 m_gradStartLoc = program()->uniformLocation(name: "gradStart");
774 m_gradEndLoc = program()->uniformLocation(name: "gradEnd");
775}
776
777void QQuickShapeLinearGradientShader::updateState(const RenderState &state, QSGMaterial *mat, QSGMaterial *)
778{
779 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(mat);
780
781 if (state.isOpacityDirty())
782 program()->setUniformValue(location: m_opacityLoc, value: state.opacity());
783
784 if (state.isMatrixDirty())
785 program()->setUniformValue(location: m_matrixLoc, value: state.combinedMatrix());
786
787 QQuickShapeGenericStrokeFillNode *node = m->node();
788 program()->setUniformValue(location: m_gradStartLoc, value: QVector2D(node->m_fillGradient.a));
789 program()->setUniformValue(location: m_gradEndLoc, value: QVector2D(node->m_fillGradient.b));
790
791 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
792 QSGTexture *tx = QQuickShapeGradientOpenGLCache::currentCache()->get(grad: cacheKey);
793 tx->bind();
794}
795
796char const *const *QQuickShapeLinearGradientShader::attributeNames() const
797{
798 static const char *const attr[] = { "vertexCoord", "vertexColor", nullptr };
799 return attr;
800}
801
802#endif // QT_CONFIG(opengl)
803
804QQuickShapeLinearGradientRhiShader::QQuickShapeLinearGradientRhiShader()
805{
806 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.vert.qsb"));
807 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.frag.qsb"));
808}
809
810bool QQuickShapeLinearGradientRhiShader::updateUniformData(RenderState &state,
811 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
812{
813 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
814 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
815 bool changed = false;
816 QByteArray *buf = state.uniformData();
817 Q_ASSERT(buf->size() >= 84);
818
819 if (state.isMatrixDirty()) {
820 const QMatrix4x4 m = state.combinedMatrix();
821 memcpy(dest: buf->data(), src: m.constData(), n: 64);
822 changed = true;
823 }
824
825 QQuickShapeGenericStrokeFillNode *node = m->node();
826
827 if (!oldMaterial || m_gradA.x() != node->m_fillGradient.a.x() || m_gradA.y() != node->m_fillGradient.a.y()) {
828 m_gradA = QVector2D(node->m_fillGradient.a.x(), node->m_fillGradient.a.y());
829 Q_ASSERT(sizeof(m_gradA) == 8);
830 memcpy(dest: buf->data() + 64, src: &m_gradA, n: 8);
831 changed = true;
832 }
833
834 if (!oldMaterial || m_gradB.x() != node->m_fillGradient.b.x() || m_gradB.y() != node->m_fillGradient.b.y()) {
835 m_gradB = QVector2D(node->m_fillGradient.b.x(), node->m_fillGradient.b.y());
836 memcpy(dest: buf->data() + 72, src: &m_gradB, n: 8);
837 changed = true;
838 }
839
840 if (state.isOpacityDirty()) {
841 const float opacity = state.opacity();
842 memcpy(dest: buf->data() + 80, src: &opacity, n: 4);
843 changed = true;
844 }
845
846 return changed;
847}
848
849void QQuickShapeLinearGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
850 QSGMaterial *newMaterial, QSGMaterial *)
851{
852 if (binding != 1)
853 return;
854
855 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
856 QQuickShapeGenericStrokeFillNode *node = m->node();
857 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
858 QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
859 t->updateRhiTexture(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
860 *texture = t;
861}
862
863QSGMaterialType *QQuickShapeLinearGradientMaterial::type() const
864{
865 static QSGMaterialType type;
866 return &type;
867}
868
869int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const
870{
871 Q_ASSERT(other && type() == other->type());
872 const QQuickShapeLinearGradientMaterial *m = static_cast<const QQuickShapeLinearGradientMaterial *>(other);
873
874 QQuickShapeGenericStrokeFillNode *a = node();
875 QQuickShapeGenericStrokeFillNode *b = m->node();
876 Q_ASSERT(a && b);
877 if (a == b)
878 return 0;
879
880 const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
881 const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
882
883 if (int d = ga->spread - gb->spread)
884 return d;
885
886 if (int d = ga->a.x() - gb->a.x())
887 return d;
888 if (int d = ga->a.y() - gb->a.y())
889 return d;
890 if (int d = ga->b.x() - gb->b.x())
891 return d;
892 if (int d = ga->b.y() - gb->b.y())
893 return d;
894
895 if (int d = ga->stops.count() - gb->stops.count())
896 return d;
897
898 for (int i = 0; i < ga->stops.count(); ++i) {
899 if (int d = ga->stops[i].first - gb->stops[i].first)
900 return d;
901 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
902 return d;
903 }
904
905 return 0;
906}
907
908QSGMaterialShader *QQuickShapeLinearGradientMaterial::createShader() const
909{
910 if (flags().testFlag(flag: RhiShaderWanted))
911 return new QQuickShapeLinearGradientRhiShader;
912#if QT_CONFIG(opengl)
913 else
914 return new QQuickShapeLinearGradientShader;
915#else
916 return nullptr;
917#endif
918}
919
920#if QT_CONFIG(opengl)
921
922QQuickShapeRadialGradientShader::QQuickShapeRadialGradientShader()
923{
924 setShaderSourceFile(type: QOpenGLShader::Vertex,
925 QStringLiteral(":/qt-project.org/shapes/shaders/radialgradient.vert"));
926 setShaderSourceFile(type: QOpenGLShader::Fragment,
927 QStringLiteral(":/qt-project.org/shapes/shaders/radialgradient.frag"));
928}
929
930void QQuickShapeRadialGradientShader::initialize()
931{
932 QOpenGLShaderProgram *prog = program();
933 m_opacityLoc = prog->uniformLocation(name: "opacity");
934 m_matrixLoc = prog->uniformLocation(name: "matrix");
935 m_translationPointLoc = prog->uniformLocation(name: "translationPoint");
936 m_focalToCenterLoc = prog->uniformLocation(name: "focalToCenter");
937 m_centerRadiusLoc = prog->uniformLocation(name: "centerRadius");
938 m_focalRadiusLoc = prog->uniformLocation(name: "focalRadius");
939}
940
941void QQuickShapeRadialGradientShader::updateState(const RenderState &state, QSGMaterial *mat, QSGMaterial *)
942{
943 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(mat);
944
945 if (state.isOpacityDirty())
946 program()->setUniformValue(location: m_opacityLoc, value: state.opacity());
947
948 if (state.isMatrixDirty())
949 program()->setUniformValue(location: m_matrixLoc, value: state.combinedMatrix());
950
951 QQuickShapeGenericStrokeFillNode *node = m->node();
952
953 const QPointF centerPoint = node->m_fillGradient.a;
954 const QPointF focalPoint = node->m_fillGradient.b;
955 const QPointF focalToCenter = centerPoint - focalPoint;
956 const GLfloat centerRadius = node->m_fillGradient.v0;
957 const GLfloat focalRadius = node->m_fillGradient.v1;
958
959 program()->setUniformValue(location: m_translationPointLoc, point: focalPoint);
960 program()->setUniformValue(location: m_centerRadiusLoc, value: centerRadius);
961 program()->setUniformValue(location: m_focalRadiusLoc, value: focalRadius);
962 program()->setUniformValue(location: m_focalToCenterLoc, point: focalToCenter);
963
964 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
965 QSGTexture *tx = QQuickShapeGradientOpenGLCache::currentCache()->get(grad: cacheKey);
966 tx->bind();
967}
968
969char const *const *QQuickShapeRadialGradientShader::attributeNames() const
970{
971 static const char *const attr[] = { "vertexCoord", "vertexColor", nullptr };
972 return attr;
973}
974
975#endif // QT_CONFIG(opengl)
976
977QQuickShapeRadialGradientRhiShader::QQuickShapeRadialGradientRhiShader()
978{
979 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.vert.qsb"));
980 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.frag.qsb"));
981}
982
983bool QQuickShapeRadialGradientRhiShader::updateUniformData(RenderState &state,
984 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
985{
986 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
987 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
988 bool changed = false;
989 QByteArray *buf = state.uniformData();
990 Q_ASSERT(buf->size() >= 92);
991
992 if (state.isMatrixDirty()) {
993 const QMatrix4x4 m = state.combinedMatrix();
994 memcpy(dest: buf->data(), src: m.constData(), n: 64);
995 changed = true;
996 }
997
998 QQuickShapeGenericStrokeFillNode *node = m->node();
999
1000 const QPointF centerPoint = node->m_fillGradient.a;
1001 const QPointF focalPoint = node->m_fillGradient.b;
1002 const QPointF focalToCenter = centerPoint - focalPoint;
1003 const float centerRadius = node->m_fillGradient.v0;
1004 const float focalRadius = node->m_fillGradient.v1;
1005
1006 if (!oldMaterial || m_focalPoint.x() != focalPoint.x() || m_focalPoint.y() != focalPoint.y()) {
1007 m_focalPoint = QVector2D(focalPoint.x(), focalPoint.y());
1008 Q_ASSERT(sizeof(m_focalPoint) == 8);
1009 memcpy(dest: buf->data() + 64, src: &m_focalPoint, n: 8);
1010 changed = true;
1011 }
1012
1013 if (!oldMaterial || m_focalToCenter.x() != focalToCenter.x() || m_focalToCenter.y() != focalToCenter.y()) {
1014 m_focalToCenter = QVector2D(focalToCenter.x(), focalToCenter.y());
1015 Q_ASSERT(sizeof(m_focalToCenter) == 8);
1016 memcpy(dest: buf->data() + 72, src: &m_focalToCenter, n: 8);
1017 changed = true;
1018 }
1019
1020 if (!oldMaterial || m_centerRadius != centerRadius) {
1021 m_centerRadius = centerRadius;
1022 memcpy(dest: buf->data() + 80, src: &m_centerRadius, n: 4);
1023 changed = true;
1024 }
1025
1026 if (!oldMaterial || m_focalRadius != focalRadius) {
1027 m_focalRadius = focalRadius;
1028 memcpy(dest: buf->data() + 84, src: &m_focalRadius, n: 4);
1029 changed = true;
1030 }
1031
1032 if (state.isOpacityDirty()) {
1033 const float opacity = state.opacity();
1034 memcpy(dest: buf->data() + 88, src: &opacity, n: 4);
1035 changed = true;
1036 }
1037
1038 return changed;
1039}
1040
1041void QQuickShapeRadialGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1042 QSGMaterial *newMaterial, QSGMaterial *)
1043{
1044 if (binding != 1)
1045 return;
1046
1047 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
1048 QQuickShapeGenericStrokeFillNode *node = m->node();
1049 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
1050 QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
1051 t->updateRhiTexture(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
1052 *texture = t;
1053}
1054
1055QSGMaterialType *QQuickShapeRadialGradientMaterial::type() const
1056{
1057 static QSGMaterialType type;
1058 return &type;
1059}
1060
1061int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const
1062{
1063 Q_ASSERT(other && type() == other->type());
1064 const QQuickShapeRadialGradientMaterial *m = static_cast<const QQuickShapeRadialGradientMaterial *>(other);
1065
1066 QQuickShapeGenericStrokeFillNode *a = node();
1067 QQuickShapeGenericStrokeFillNode *b = m->node();
1068 Q_ASSERT(a && b);
1069 if (a == b)
1070 return 0;
1071
1072 const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
1073 const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
1074
1075 if (int d = ga->spread - gb->spread)
1076 return d;
1077
1078 if (int d = ga->a.x() - gb->a.x())
1079 return d;
1080 if (int d = ga->a.y() - gb->a.y())
1081 return d;
1082 if (int d = ga->b.x() - gb->b.x())
1083 return d;
1084 if (int d = ga->b.y() - gb->b.y())
1085 return d;
1086
1087 if (int d = ga->v0 - gb->v0)
1088 return d;
1089 if (int d = ga->v1 - gb->v1)
1090 return d;
1091
1092 if (int d = ga->stops.count() - gb->stops.count())
1093 return d;
1094
1095 for (int i = 0; i < ga->stops.count(); ++i) {
1096 if (int d = ga->stops[i].first - gb->stops[i].first)
1097 return d;
1098 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1099 return d;
1100 }
1101
1102 return 0;
1103}
1104
1105QSGMaterialShader *QQuickShapeRadialGradientMaterial::createShader() const
1106{
1107 if (flags().testFlag(flag: RhiShaderWanted))
1108 return new QQuickShapeRadialGradientRhiShader;
1109#if QT_CONFIG(opengl)
1110 else
1111 return new QQuickShapeRadialGradientShader;
1112#else
1113 return nullptr;
1114#endif
1115}
1116
1117#if QT_CONFIG(opengl)
1118
1119QQuickShapeConicalGradientShader::QQuickShapeConicalGradientShader()
1120{
1121 setShaderSourceFile(type: QOpenGLShader::Vertex,
1122 QStringLiteral(":/qt-project.org/shapes/shaders/conicalgradient.vert"));
1123 setShaderSourceFile(type: QOpenGLShader::Fragment,
1124 QStringLiteral(":/qt-project.org/shapes/shaders/conicalgradient.frag"));
1125}
1126
1127void QQuickShapeConicalGradientShader::initialize()
1128{
1129 QOpenGLShaderProgram *prog = program();
1130 m_opacityLoc = prog->uniformLocation(name: "opacity");
1131 m_matrixLoc = prog->uniformLocation(name: "matrix");
1132 m_angleLoc = prog->uniformLocation(name: "angle");
1133 m_translationPointLoc = prog->uniformLocation(name: "translationPoint");
1134}
1135
1136void QQuickShapeConicalGradientShader::updateState(const RenderState &state, QSGMaterial *mat, QSGMaterial *)
1137{
1138 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(mat);
1139
1140 if (state.isOpacityDirty())
1141 program()->setUniformValue(location: m_opacityLoc, value: state.opacity());
1142
1143 if (state.isMatrixDirty())
1144 program()->setUniformValue(location: m_matrixLoc, value: state.combinedMatrix());
1145
1146 QQuickShapeGenericStrokeFillNode *node = m->node();
1147
1148 const QPointF centerPoint = node->m_fillGradient.a;
1149 const GLfloat angle = -qDegreesToRadians(degrees: node->m_fillGradient.v0);
1150
1151 program()->setUniformValue(location: m_angleLoc, value: angle);
1152 program()->setUniformValue(location: m_translationPointLoc, point: centerPoint);
1153
1154 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, QQuickShapeGradient::RepeatSpread);
1155 QSGTexture *tx = QQuickShapeGradientOpenGLCache::currentCache()->get(grad: cacheKey);
1156 tx->bind();
1157}
1158
1159char const *const *QQuickShapeConicalGradientShader::attributeNames() const
1160{
1161 static const char *const attr[] = { "vertexCoord", "vertexColor", nullptr };
1162 return attr;
1163}
1164
1165#endif // QT_CONFIG(opengl)
1166
1167QQuickShapeConicalGradientRhiShader::QQuickShapeConicalGradientRhiShader()
1168{
1169 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.vert.qsb"));
1170 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.frag.qsb"));
1171}
1172
1173bool QQuickShapeConicalGradientRhiShader::updateUniformData(RenderState &state,
1174 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1175{
1176 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1177 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
1178 bool changed = false;
1179 QByteArray *buf = state.uniformData();
1180 Q_ASSERT(buf->size() >= 80);
1181
1182 if (state.isMatrixDirty()) {
1183 const QMatrix4x4 m = state.combinedMatrix();
1184 memcpy(dest: buf->data(), src: m.constData(), n: 64);
1185 changed = true;
1186 }
1187
1188 QQuickShapeGenericStrokeFillNode *node = m->node();
1189
1190 const QPointF centerPoint = node->m_fillGradient.a;
1191 const float angle = -qDegreesToRadians(degrees: node->m_fillGradient.v0);
1192
1193 if (!oldMaterial || m_centerPoint.x() != centerPoint.x() || m_centerPoint.y() != centerPoint.y()) {
1194 m_centerPoint = QVector2D(centerPoint.x(), centerPoint.y());
1195 Q_ASSERT(sizeof(m_centerPoint) == 8);
1196 memcpy(dest: buf->data() + 64, src: &m_centerPoint, n: 8);
1197 changed = true;
1198 }
1199
1200 if (!oldMaterial || m_angle != angle) {
1201 m_angle = angle;
1202 memcpy(dest: buf->data() + 72, src: &m_angle, n: 4);
1203 changed = true;
1204 }
1205
1206 if (state.isOpacityDirty()) {
1207 const float opacity = state.opacity();
1208 memcpy(dest: buf->data() + 76, src: &opacity, n: 4);
1209 changed = true;
1210 }
1211
1212 return changed;
1213}
1214
1215void QQuickShapeConicalGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1216 QSGMaterial *newMaterial, QSGMaterial *)
1217{
1218 if (binding != 1)
1219 return;
1220
1221 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
1222 QQuickShapeGenericStrokeFillNode *node = m->node();
1223 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
1224 QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
1225 t->updateRhiTexture(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
1226 *texture = t;
1227}
1228
1229QSGMaterialType *QQuickShapeConicalGradientMaterial::type() const
1230{
1231 static QSGMaterialType type;
1232 return &type;
1233}
1234
1235int QQuickShapeConicalGradientMaterial::compare(const QSGMaterial *other) const
1236{
1237 Q_ASSERT(other && type() == other->type());
1238 const QQuickShapeConicalGradientMaterial *m = static_cast<const QQuickShapeConicalGradientMaterial *>(other);
1239
1240 QQuickShapeGenericStrokeFillNode *a = node();
1241 QQuickShapeGenericStrokeFillNode *b = m->node();
1242 Q_ASSERT(a && b);
1243 if (a == b)
1244 return 0;
1245
1246 const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
1247 const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
1248
1249 if (int d = ga->a.x() - gb->a.x())
1250 return d;
1251 if (int d = ga->a.y() - gb->a.y())
1252 return d;
1253
1254 if (int d = ga->v0 - gb->v0)
1255 return d;
1256
1257 if (int d = ga->stops.count() - gb->stops.count())
1258 return d;
1259
1260 for (int i = 0; i < ga->stops.count(); ++i) {
1261 if (int d = ga->stops[i].first - gb->stops[i].first)
1262 return d;
1263 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1264 return d;
1265 }
1266
1267 return 0;
1268}
1269
1270QSGMaterialShader *QQuickShapeConicalGradientMaterial::createShader() const
1271{
1272 if (flags().testFlag(flag: RhiShaderWanted))
1273 return new QQuickShapeConicalGradientRhiShader;
1274#if QT_CONFIG(opengl)
1275 else
1276 return new QQuickShapeConicalGradientShader;
1277#else
1278 return nullptr;
1279#endif
1280}
1281
1282QT_END_NAMESPACE
1283

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