Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickshapegenericrenderer.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include <QtGui/private/qtriangulator_p.h>
7#include <QtGui/private/qtriangulatingstroker_p.h>
8#include <rhi/qrhi.h>
9#include <QSGVertexColorMaterial>
10#include <QSGTextureProvider>
11#include <private/qsgplaintexture_p.h>
12
13#include <QtQuick/private/qsggradientcache_p.h>
14
15#if QT_CONFIG(thread)
16#include <QThreadPool>
17#endif
18
20
21struct ColoredVertex // must match QSGGeometry::ColoredPoint2D
22{
23 float x, y;
25 void set(float nx, float ny, QQuickShapeGenericRenderer::Color4ub ncolor)
26 {
27 x = nx; y = ny; color = ncolor;
28 }
29};
30
32{
33 float r, g, b, a;
34 c.getRgbF(&r, &g, &b, &a);
36 uchar(qRound(r * a * 255)),
37 uchar(qRound(g * a * 255)),
38 uchar(qRound(b * a * 255)),
39 uchar(qRound(a * 255))
40 };
41 return color;
42}
43
44QQuickShapeGenericStrokeFillNode::QQuickShapeGenericStrokeFillNode(QQuickWindow *window)
45 : m_material(nullptr)
46{
47 setFlag(QSGNode::OwnsGeometry, true);
48 setFlag(QSGNode::UsePreprocess, true);
49 setGeometry(new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0, 0));
50 activateMaterial(window, MatSolidColor);
52 qsgnode_set_description(this, QLatin1String("stroke-fill"));
53#endif
54}
55
57{
58 switch (m) {
59 case MatSolidColor:
60 // Use vertexcolor material. Items with different colors remain batchable
61 // this way, at the expense of having to provide per-vertex color values.
62 m_material.reset(QQuickShapeGenericMaterialFactory::createVertexColor(window));
63 break;
64 case MatLinearGradient:
65 m_material.reset(QQuickShapeGenericMaterialFactory::createLinearGradient(window, this));
66 break;
67 case MatRadialGradient:
68 m_material.reset(QQuickShapeGenericMaterialFactory::createRadialGradient(window, this));
69 break;
70 case MatConicalGradient:
71 m_material.reset(QQuickShapeGenericMaterialFactory::createConicalGradient(window, this));
72 break;
73 case MatTextureFill:
74 m_material.reset(QQuickShapeGenericMaterialFactory::createTextureFill(window, this));
75 break;
76 default:
77 qWarning("Unknown material %d", m);
78 return;
79 }
80
81 if (material() != m_material.data())
82 setMaterial(m_material.data());
83}
84
86{
87 if (m_fillTextureProvider != nullptr) {
88 if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(m_fillTextureProvider->texture()))
89 texture->updateTexture();
90 }
91}
92
93void QQuickShapeGenericStrokeFillNode::handleTextureChanged()
94{
95 markDirty(QSGNode::DirtyMaterial);
96}
97
98void QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed()
99{
100 m_fillTextureProvider = nullptr;
101 markDirty(QSGNode::DirtyMaterial);
102}
103
105{
106 for (ShapePathData &d : m_sp) {
107 if (d.pendingFill)
108 d.pendingFill->orphaned = true;
109 if (d.pendingStroke)
110 d.pendingStroke->orphaned = true;
111 }
112}
113
114// sync, and so triangulation too, happens on the gui thread
115// - except when async is set, in which case triangulation is moved to worker threads
116
117void QQuickShapeGenericRenderer::beginSync(int totalCount, bool *countChanged)
118{
119 for (int i = totalCount; i < m_sp.size(); i++) // Handle removal of paths
120 setFillTextureProvider(i, nullptr); // deref window
121
122 if (m_sp.size() != totalCount) {
123 m_sp.resize(totalCount);
124 m_accDirty |= DirtyList;
125 *countChanged = true;
126 } else {
127 *countChanged = false;
128 }
129 for (ShapePathData &d : m_sp)
130 d.syncDirty = 0;
131}
132
133void QQuickShapeGenericRenderer::setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints)
134{
135 ShapePathData &d(m_sp[index]);
136 d.path = path;
137 d.syncDirty |= DirtyFillGeom | DirtyStrokeGeom;
138}
139
140void QQuickShapeGenericRenderer::setStrokeColor(int index, const QColor &color)
141{
142 ShapePathData &d(m_sp[index]);
143 const bool wasTransparent = d.strokeColor.a == 0;
144 d.strokeColor = colorToColor4ub(color);
145 const bool isTransparent = d.strokeColor.a == 0;
146 d.syncDirty |= DirtyColor;
147 if (wasTransparent && !isTransparent)
148 d.syncDirty |= DirtyStrokeGeom;
149}
150
152{
153 ShapePathData &d(m_sp[index]);
154 d.strokeWidth = w;
155 if (w > 0.0f)
156 d.pen.setWidthF(w);
157 d.syncDirty |= DirtyStrokeGeom;
158}
159
161{
162 ShapePathData &d(m_sp[index]);
163 d.pen.setCosmetic(c);
164 d.syncDirty |= DirtyStrokeGeom;
165 // as long as the stroke is cosmetic,
166 // QQuickShape::itemChange triggers re-triangulation whenever scale changes
167}
168
169void QQuickShapeGenericRenderer::setFillColor(int index, const QColor &color)
170{
171 ShapePathData &d(m_sp[index]);
172 const bool wasTransparent = d.fillColor.a == 0;
173 d.fillColor = colorToColor4ub(color);
174 const bool isTransparent = d.fillColor.a == 0;
175 d.syncDirty |= DirtyColor;
176 if (wasTransparent && !isTransparent)
177 d.syncDirty |= DirtyFillGeom;
178}
179
180void QQuickShapeGenericRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
181{
182 ShapePathData &d(m_sp[index]);
183 d.fillRule = Qt::FillRule(fillRule);
184 d.syncDirty |= DirtyFillGeom;
185}
186
187void QQuickShapeGenericRenderer::setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit)
188{
189 ShapePathData &d(m_sp[index]);
190 d.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
191 d.pen.setMiterLimit(miterLimit);
192 d.syncDirty |= DirtyStrokeGeom;
193}
194
195void QQuickShapeGenericRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
196{
197 ShapePathData &d(m_sp[index]);
198 d.pen.setCapStyle(Qt::PenCapStyle(capStyle));
199 d.syncDirty |= DirtyStrokeGeom;
200}
201
202void QQuickShapeGenericRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
203 qreal dashOffset, const QList<qreal> &dashPattern)
204{
205 ShapePathData &d(m_sp[index]);
206 d.pen.setStyle(Qt::PenStyle(strokeStyle));
207 if (strokeStyle == QQuickShapePath::DashLine) {
208 d.pen.setDashPattern(dashPattern);
209 d.pen.setDashOffset(dashOffset);
210 }
211 d.syncDirty |= DirtyStrokeGeom;
212}
213
215 copyGenericGradient(const QQuickShapeGradient *gradient, QSGGradientCache::GradientDesc *dst)
216{
217 Q_ASSERT(dst != nullptr);
218 if (gradient == nullptr)
219 return QQuickAbstractPathRenderer::NoGradient;
220
221 dst->stops = gradient->gradientStops(); // sorted
222 dst->spread = QGradient::Spread(gradient->spread());
223 if (const QQuickShapeLinearGradient *g = qobject_cast<const QQuickShapeLinearGradient *>(gradient)) {
224 dst->a = QPointF(g->x1(), g->y1());
225 dst->b = QPointF(g->x2(), g->y2());
226
227 return QQuickAbstractPathRenderer::LinearGradient;
228 } else if (const QQuickShapeRadialGradient *g = qobject_cast<const QQuickShapeRadialGradient *>(gradient)) {
229 dst->a = QPointF(g->centerX(), g->centerY());
230 dst->b = QPointF(g->focalX(), g->focalY());
231 dst->v0 = g->centerRadius();
232 dst->v1 = g->focalRadius();
233
234 return QQuickAbstractPathRenderer::RadialGradient;
235 } else if (const QQuickShapeConicalGradient *g = qobject_cast<const QQuickShapeConicalGradient *>(gradient)) {
236 dst->a = QPointF(g->centerX(), g->centerY());
237 dst->v0 = g->angle();
238
239 return QQuickAbstractPathRenderer::ConicalGradient;
240 }
241
242 Q_UNREACHABLE_RETURN(QQuickAbstractPathRenderer::NoGradient);
243}
244
245void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
246{
247 ShapePathData &d(m_sp[index]);
248 d.fillGradientActive = copyGenericGradient(gradient, &d.fillGradient);
249 d.syncDirty |= DirtyFillGradient;
250}
251
252void QQuickShapeGenericRenderer::setStrokeGradient(int index, QQuickShapeGradient *gradient)
253{
254 ShapePathData &d(m_sp[index]);
255 d.strokeGradientActive = copyGenericGradient(gradient, &d.strokeGradient);
256 d.syncDirty |= DirtyStrokeGradient;
257}
258
259void QQuickShapeGenericRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
260{
261 ShapePathData &d(m_sp[index]);
262 if ((d.fillTextureProviderItem == nullptr) != (textureProviderItem == nullptr))
263 d.syncDirty |= DirtyFillGeom;
264 if (d.fillTextureProviderItem != nullptr)
265 QQuickItemPrivate::get(d.fillTextureProviderItem)->derefWindow();
266 d.fillTextureProviderItem = textureProviderItem;
267 if (d.fillTextureProviderItem != nullptr)
268 QQuickItemPrivate::get(d.fillTextureProviderItem)->refWindow(m_item->window());
269 d.syncDirty |= DirtyFillTexture;
270}
271
273{
274 for (auto &pathData : m_sp) {
275 if (pathData.fillTextureProviderItem != nullptr) {
276 if (window == nullptr)
277 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
278 else
279 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(window);
280 }
281 }
282}
283
284
285void QQuickShapeGenericRenderer::setFillTransform(int index, const QSGTransform &transform)
286{
287 ShapePathData &d(m_sp[index]);
288 d.fillTransform = transform;
289 d.syncDirty |= DirtyFillTransform;
290}
291
293{
294 ShapePathData &d(m_sp[index]);
295 d.triangulationScale = scale;
296 d.syncDirty |= DirtyStrokeGeom;
297}
298
300{
301 QQuickShapeGenericRenderer::triangulateFill(path, fillColor, &fillVertices, &fillIndices, &indexType,
302 supportsElementIndexUint, triangulationScale);
303 // Emit done signal on main thread, since connected to lambda there
304 QMetaObject::invokeMethod(qApp, [this]() { emit done(this); }, Qt::QueuedConnection);
305}
306
308{
309 QQuickShapeGenericRenderer::triangulateStroke(path, pen, strokeColor, &strokeVertices, clipSize, triangulationScale);
310 // Emit done signal on main thread, since connected to lambda there
311 QMetaObject::invokeMethod(qApp, [this]() { emit done(this); }, Qt::QueuedConnection);
312}
313
314void QQuickShapeGenericRenderer::setAsyncCallback(void (*callback)(void *), void *data)
315{
316 m_asyncCallback = callback;
317 m_asyncCallbackData = data;
318}
319
320#if QT_CONFIG(thread)
321static QThreadPool *pathWorkThreadPool = nullptr;
322
323static void deletePathWorkThreadPool()
324{
325 delete pathWorkThreadPool;
326 pathWorkThreadPool = nullptr;
327}
328#endif
329
331{
332#if !QT_CONFIG(thread)
333 // Force synchronous mode for the no-thread configuration due
334 // to lack of QThreadPool.
335 async = false;
336#endif
337
338 bool didKickOffAsync = false;
339
340 for (int i = 0; i < m_sp.size(); ++i) {
341 ShapePathData &d(m_sp[i]);
342 if (!d.syncDirty)
343 continue;
344
345 m_accDirty |= d.syncDirty;
346
347 // Use a shadow dirty flag in order to avoid losing state in case there are
348 // multiple syncs with different dirty flags before we get to updateNode()
349 // on the render thread (with the gui thread blocked). For our purposes
350 // here syncDirty is still required since geometry regeneration must only
351 // happen when there was an actual change in this particular sync round.
352 d.effectiveDirty |= d.syncDirty;
353
354 if (d.path.isEmpty()) {
355 d.fillVertices.clear();
356 d.fillIndices.clear();
357 d.strokeVertices.clear();
358 continue;
359 }
360
361#if QT_CONFIG(thread)
362 if (async && !pathWorkThreadPool) {
363 qAddPostRoutine(deletePathWorkThreadPool);
364 pathWorkThreadPool = new QThreadPool;
365 const int idealCount = QThread::idealThreadCount();
366 pathWorkThreadPool->setMaxThreadCount(idealCount > 0 ? idealCount * 2 : 4);
367 }
368#endif
369
370 // RHI backend might not have had a chance of being initialized yet
371 // (we are called in the mainthread while renderthread might be in the
372 // process of starting up and setting the RHI backend.
373 // m_rhiBackendInitialized is set in updateNode() on the render thread
374 // when both threads are locked.
375 // Until then, we cannot assume we have support for uint32_t indices
376 // but only uint16_t ones.
377 const bool supportsElementIndexUint = m_rhiBackendInitialized ? m_supportsElementIndexUint : true;
378 if ((d.syncDirty & DirtyFillGeom) && d.fillColor.a) {
379 d.path.setFillRule(d.fillRule);
380 if (m_api == QSGRendererInterface::Unknown)
381 m_api = m_item->window()->rendererInterface()->graphicsApi();
382 if (async) {
384 r->setAutoDelete(false);
385 if (d.pendingFill)
386 d.pendingFill->orphaned = true;
387 d.pendingFill = r;
388 r->path = d.path;
389 r->fillColor = d.fillColor;
390 r->supportsElementIndexUint = supportsElementIndexUint;
391 r->triangulationScale = d.triangulationScale;
392 // Unlikely in practice but in theory m_sp could be
393 // resized. Therefore, capture 'i' instead of 'd'.
394 QObject::connect(r, &QQuickShapeFillRunnable::done, qApp, [this, i](QQuickShapeFillRunnable *r) {
395 // Bail out when orphaned (meaning either another run was
396 // started after this one, or the renderer got destroyed).
397 if (!r->orphaned && i < m_sp.size()) {
398 ShapePathData &d(m_sp[i]);
399 d.fillVertices = r->fillVertices;
400 d.fillIndices = r->fillIndices;
401 d.indexType = r->indexType;
402 d.pendingFill = nullptr;
403 d.effectiveDirty |= DirtyFillGeom;
404 maybeUpdateAsyncItem();
405 }
406 r->deleteLater();
407 });
408 didKickOffAsync = true;
409#if QT_CONFIG(thread)
410 // qtVectorPathForPath() initializes a unique_ptr without locking.
411 // Do that before starting the threads as otherwise we get a race condition.
412 qtVectorPathForPath(r->path);
413 pathWorkThreadPool->start(r);
414#endif
415 } else {
416 triangulateFill(d.path, d.fillColor, &d.fillVertices, &d.fillIndices, &d.indexType,
417 supportsElementIndexUint,
418 d.triangulationScale);
419 }
420 }
421
422 if ((d.syncDirty & DirtyStrokeGeom) && d.strokeWidth > 0.0f && (d.strokeColor.a || d.strokeGradientActive)) {
423 if (async) {
425 r->setAutoDelete(false);
426 if (d.pendingStroke)
427 d.pendingStroke->orphaned = true;
428 d.pendingStroke = r;
429 r->path = d.path;
430 r->pen = d.pen;
431 r->strokeColor = d.strokeColor;
432 r->clipSize = QSize(m_item->width(), m_item->height());
433 r->triangulationScale = d.triangulationScale;
434 QObject::connect(r, &QQuickShapeStrokeRunnable::done, qApp, [this, i](QQuickShapeStrokeRunnable *r) {
435 if (!r->orphaned && i < m_sp.size()) {
436 ShapePathData &d(m_sp[i]);
437 d.strokeVertices = r->strokeVertices;
438 d.pendingStroke = nullptr;
439 d.effectiveDirty |= DirtyStrokeGeom;
440 maybeUpdateAsyncItem();
441 }
442 r->deleteLater();
443 });
444 didKickOffAsync = true;
445#if QT_CONFIG(thread)
446 // qtVectorPathForPath() initializes a unique_ptr without locking.
447 // Do that before starting the threads as otherwise we get a race condition.
448 qtVectorPathForPath(r->path);
449 pathWorkThreadPool->start(r);
450#endif
451 } else {
452 triangulateStroke(d.path, d.pen, d.strokeColor, &d.strokeVertices,
453 QSize(m_item->width(), m_item->height()), d.triangulationScale);
454 }
455 }
456 }
457
458 if (!didKickOffAsync && async && m_asyncCallback)
459 m_asyncCallback(m_asyncCallbackData);
460}
461
462void QQuickShapeGenericRenderer::maybeUpdateAsyncItem()
463{
464 for (const ShapePathData &d : std::as_const(m_sp)) {
465 if (d.pendingFill || d.pendingStroke)
466 return;
467 }
468 m_accDirty |= DirtyFillGeom | DirtyStrokeGeom;
469 m_item->update();
470 if (m_asyncCallback)
471 m_asyncCallback(m_asyncCallbackData);
472}
473
474// the stroke/fill triangulation functions may be invoked either on the gui
475// thread or some worker thread and must thus be self-contained.
476void QQuickShapeGenericRenderer::triangulateFill(const QPainterPath &path,
477 const Color4ub &fillColor,
478 VertexContainerType *fillVertices,
479 IndexContainerType *fillIndices,
480 QSGGeometry::Type *indexType,
481 bool supportsElementIndexUint,
482 qreal triangulationScale)
483{
484 const QVectorPath &vp = qtVectorPathForPath(path);
485
486 QTriangleSet ts = qTriangulate(vp, QTransform::fromScale(triangulationScale, triangulationScale), 1, supportsElementIndexUint);
487 const int vertexCount = ts.vertices.size() / 2; // just a qreal vector with x,y hence the / 2
488 fillVertices->resize(vertexCount);
489 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(fillVertices->data());
490 const qreal *vsrc = ts.vertices.constData();
491 for (int i = 0; i < vertexCount; ++i)
492 vdst[i].set(vsrc[i * 2] / triangulationScale, vsrc[i * 2 + 1] / triangulationScale, fillColor);
493
494 size_t indexByteSize;
495 if (ts.indices.type() == QVertexIndexVector::UnsignedShort || vertexCount <= 0xffff) {
496 *indexType = QSGGeometry::UnsignedShortType;
497 const int indexCount = ts.indices.size();
498
499 // fillIndices is still QList<quint32>. Just resize to N/2 and pack
500 // the N quint16s into it. To ensure that there is enough space to hold the full buffer,
501 // we round the number up. Any redundant padding indexes will be ignored as the renderer
502 // processes geometry in triplets.
503 fillIndices->resize((indexCount + 1) / 2);
504
505 // If indices are 32-bit, we convert to 16-bit and return early.
506 if (ts.indices.type() != QVertexIndexVector::UnsignedShort) {
507 quint16 *dst = reinterpret_cast<quint16 *>(fillIndices->data());
508 const quint32 *src = reinterpret_cast<const quint32 *>(ts.indices.data());
509 for (int i = 0; i < indexCount; ++i)
510 dst[i] = quint16(src[i]);
511 return; // Early exit to skip memcpy()
512 }
513
514 indexByteSize = indexCount * sizeof(quint16);
515 } else {
516 *indexType = QSGGeometry::UnsignedIntType;
517 fillIndices->resize(ts.indices.size());
518 indexByteSize = ts.indices.size() * sizeof(quint32);
519 }
520
521 Q_ASSERT(fillIndices->size() * sizeof(quint32) >= indexByteSize);
522 memcpy(fillIndices->data(), ts.indices.data(), indexByteSize);
523}
524
525void QQuickShapeGenericRenderer::triangulateStroke(const QPainterPath &path,
526 const QPen &pen,
527 const Color4ub &strokeColor,
528 VertexContainerType *strokeVertices,
529 const QSize &clipSize,
530 qreal triangulationScale)
531{
532 const QVectorPath &vp = qtVectorPathForPath(path);
533 const QRectF clip(QPointF(0, 0), clipSize);
534 const qreal inverseScale = 1.0 / triangulationScale;
535
536 QTriangulatingStroker stroker;
537 stroker.setInvScale(inverseScale);
538
539 if (pen.style() == Qt::SolidLine) {
540 stroker.process(vp, pen, clip, {});
541 } else {
542 QDashedStrokeProcessor dashStroker;
543 dashStroker.setInvScale(inverseScale);
544 dashStroker.process(vp, pen, clip, {});
545 QVectorPath dashStroke(dashStroker.points(), dashStroker.elementCount(),
546 dashStroker.elementTypes(), 0);
547 stroker.process(dashStroke, pen, clip, {});
548 }
549
550 if (!stroker.vertexCount()) {
551 strokeVertices->clear();
552 return;
553 }
554
555 const int vertexCount = stroker.vertexCount() / 2; // just a float vector with x,y hence the / 2
556 strokeVertices->resize(vertexCount);
557 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(strokeVertices->data());
558 const float *vsrc = stroker.vertices();
559 for (int i = 0; i < vertexCount; ++i)
560 vdst[i].set(vsrc[i * 2], vsrc[i * 2 + 1], strokeColor);
561}
562
564{
565 if (m_rootNode != node) {
566 m_rootNode = node;
567 m_accDirty |= DirtyList;
568 }
569}
570
571// on the render thread with gui blocked
573{
574 if (!m_rootNode || !m_accDirty)
575 return;
576
577 // Is it now safe to query the rhi backend
578 if (!m_rhiBackendInitialized) {
579 auto findRHIBackend = [](QQuickItem *item) -> QRhi * {
580 if (auto *w = item->window()) {
581 if (auto *rhi = QQuickWindowPrivate::get(w)->rhi)
582 return rhi;
583 }
584 return nullptr;
585 };
586 auto *rhi = findRHIBackend(m_item);
587 if (rhi != nullptr) {
588 m_rhiBackendInitialized = true;
589 m_supportsElementIndexUint = rhi->isFeatureSupported(QRhi::ElementIndexUint);
590 }
591 }
592
593// [ m_rootNode ]
594// / / /
595// #0 [ fill ] [ stroke ] [ next ]
596// / / |
597// #1 [ fill ] [ stroke ] [ next ]
598// / / |
599// #2 [ fill ] [ stroke ] [ next ]
600// ...
601// ...
602
603 QQuickShapeGenericNode **nodePtr = &m_rootNode;
604 QQuickShapeGenericNode *prevNode = nullptr;
605
606 for (ShapePathData &d : m_sp) {
607 if (!*nodePtr) {
608 Q_ASSERT(prevNode);
609 *nodePtr = new QQuickShapeGenericNode;
610 prevNode->m_next = *nodePtr;
611 prevNode->appendChildNode(*nodePtr);
612 }
613
614 QQuickShapeGenericNode *node = *nodePtr;
615
616 if (m_accDirty & DirtyList) {
617 d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient
618 | DirtyFillTransform | DirtyFillTexture | DirtyStrokeGradient;
619 }
620
621 if (!d.effectiveDirty) {
622 prevNode = node;
623 nodePtr = &node->m_next;
624 continue;
625 }
626
627 if (d.fillColor.a == 0) {
628 delete node->m_fillNode;
629 node->m_fillNode = nullptr;
630 } else if (!node->m_fillNode) {
631 node->m_fillNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
632 if (node->m_strokeNode)
633 node->removeChildNode(node->m_strokeNode);
634 node->appendChildNode(node->m_fillNode);
635 if (node->m_strokeNode)
636 node->appendChildNode(node->m_strokeNode);
637 d.effectiveDirty |= DirtyFillGeom;
638 }
639
640 if (d.strokeWidth <= 0.0f || d.strokeColor.a == 0) {
641 delete node->m_strokeNode;
642 node->m_strokeNode = nullptr;
643 } else if (!node->m_strokeNode) {
644 node->m_strokeNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
645 node->appendChildNode(node->m_strokeNode);
646 d.effectiveDirty |= DirtyStrokeGeom;
647 }
648
649 updateFillNode(&d, node);
650 updateStrokeNode(&d, node);
651
652 d.effectiveDirty = 0;
653
654 prevNode = node;
655 nodePtr = &node->m_next;
656 }
657
658 if (*nodePtr && prevNode) {
659 prevNode->removeChildNode(*nodePtr);
660 delete *nodePtr;
661 *nodePtr = nullptr;
662 }
663
664 if (m_sp.isEmpty()) {
665 delete m_rootNode->m_fillNode;
666 m_rootNode->m_fillNode = nullptr;
667 delete m_rootNode->m_strokeNode;
668 m_rootNode->m_strokeNode = nullptr;
669 delete m_rootNode->m_next;
670 m_rootNode->m_next = nullptr;
671 }
672
673 m_accDirty = 0;
674}
675
676void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuickShapeGenericStrokeFillNode *n)
677{
678 if (d->fillGradientActive) {
679 if (d->effectiveDirty & DirtyFillGradient)
680 n->m_gradient = d->fillGradient;
681 }
682
683 if (d->effectiveDirty & DirtyFillTexture) {
684 bool needsUpdate = d->fillTextureProviderItem == nullptr && n->m_fillTextureProvider != nullptr;
685 if (!needsUpdate
686 && d->fillTextureProviderItem != nullptr
687 && n->m_fillTextureProvider != d->fillTextureProviderItem->textureProvider()) {
688 needsUpdate = true;
689 }
690
691 if (needsUpdate) {
692 if (n->m_fillTextureProvider != nullptr) {
693 QObject::disconnect(n->m_fillTextureProvider, &QSGTextureProvider::textureChanged,
694 n, &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
695 QObject::disconnect(n->m_fillTextureProvider, &QSGTextureProvider::destroyed,
696 n, &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
697 }
698
699 n->m_fillTextureProvider = d->fillTextureProviderItem == nullptr
700 ? nullptr
701 : d->fillTextureProviderItem->textureProvider();
702
703 if (n->m_fillTextureProvider != nullptr) {
704 QObject::connect(n->m_fillTextureProvider, &QSGTextureProvider::textureChanged,
705 n, &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
706 QObject::connect(n->m_fillTextureProvider, &QSGTextureProvider::destroyed,
707 n, &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
708 }
709 }
710 }
711 if (d->effectiveDirty & DirtyFillTransform)
712 n->m_fillTransform = d->fillTransform;
713}
714
715void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGenericNode *node)
716{
717 if (!node->m_fillNode)
718 return;
720 return;
721
722 // Make a copy of the data that will be accessed by the material on
723 // the render thread. This must be done even when we bail out below.
725 updateShadowDataInNode(d, n);
726
727 QSGGeometry *g = n->geometry();
728 if (d->fillVertices.isEmpty()) {
729 if (g->vertexCount() || g->indexCount()) {
730 g->allocate(0, 0);
731 n->markDirty(QSGNode::DirtyGeometry);
732 }
733 return;
734 }
735
736 if (d->fillGradientActive) {
738 switch (d->fillGradientActive) {
739 case LinearGradient:
741 break;
742 case RadialGradient:
744 break;
745 case ConicalGradient:
747 break;
748 default:
749 Q_UNREACHABLE_RETURN();
750 }
751 n->activateMaterial(m_item->window(), gradMat);
752 if (d->effectiveDirty & (DirtyFillGradient | DirtyFillTransform)) {
753 // Gradients are implemented via a texture-based material.
754 n->markDirty(QSGNode::DirtyMaterial);
755 // stop here if only the gradient or filltransform changed; no need to touch the geometry
756 if (!(d->effectiveDirty & DirtyFillGeom))
757 return;
758 }
759 } else if (d->fillTextureProviderItem != nullptr) {
760 n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatTextureFill);
761 if (d->effectiveDirty & DirtyFillTexture)
762 n->markDirty(QSGNode::DirtyMaterial);
763 } else {
764 n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatSolidColor);
765 // fast path for updating only color values when no change in vertex positions
766 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyFillGeom) && d->fillTextureProviderItem == nullptr) {
767 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
768 for (int i = 0; i < g->vertexCount(); ++i)
769 vdst[i].set(vdst[i].x, vdst[i].y, d->fillColor);
770 n->markDirty(QSGNode::DirtyGeometry);
771 return;
772 }
773 }
774
775 const int indexCount = d->indexType == QSGGeometry::UnsignedShortType
776 ? d->fillIndices.size() * 2 : d->fillIndices.size();
777 if (g->indexType() != d->indexType) {
778 g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(),
779 d->fillVertices.size(), indexCount, d->indexType);
780 n->setGeometry(g);
781 } else {
782 g->allocate(d->fillVertices.size(), indexCount);
783 }
784 g->setDrawingMode(QSGGeometry::DrawTriangles);
785
786 memcpy(g->vertexData(), d->fillVertices.constData(), g->vertexCount() * g->sizeOfVertex());
787 memcpy(g->indexData(), d->fillIndices.constData(), g->indexCount() * g->sizeOfIndex());
788
789 n->markDirty(QSGNode::DirtyGeometry);
790}
791
792void QQuickShapeGenericRenderer::updateStrokeNode(ShapePathData *d, QQuickShapeGenericNode *node)
793{
794 if (!node->m_strokeNode)
795 return;
796 if (!(d->effectiveDirty & (DirtyStrokeGeom | DirtyColor | DirtyStrokeGradient)))
797 return;
798
800 if (d->strokeGradientActive) {
801 if (d->effectiveDirty & DirtyStrokeGradient)
802 n->m_gradient = d->strokeGradient;
803 }
804
805 QSGGeometry *g = n->geometry();
806 if (d->strokeVertices.isEmpty()) {
807 if (g->vertexCount() || g->indexCount()) {
808 g->allocate(0, 0);
809 n->markDirty(QSGNode::DirtyGeometry);
810 }
811 return;
812 }
813
814 n->markDirty(QSGNode::DirtyGeometry);
815
816 // Async loading runs update once, bails out above, then updates again once
817 // ready. Set the material dirty then. This is in-line with fill where the
818 // first activateMaterial() achieves the same.
819 if (!g->vertexCount())
820 n->markDirty(QSGNode::DirtyMaterial);
821
822 if (d->strokeGradientActive) {
824 switch (d->strokeGradientActive) {
825 case LinearGradient:
827 break;
828 case RadialGradient:
830 break;
831 case ConicalGradient:
833 break;
834 default:
835 Q_UNREACHABLE_RETURN();
836 }
837 n->activateMaterial(m_item->window(), gradMat);
838 if (d->effectiveDirty & DirtyStrokeGradient) {
839 // Gradients are implemented via a texture-based material.
840 n->markDirty(QSGNode::DirtyMaterial);
841 // stop here if only the gradient changed; no need to touch the geometry
842 if (!(d->effectiveDirty & DirtyStrokeGeom))
843 return;
844 }
845 } else {
846 n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatSolidColor);
847 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyStrokeGeom)) {
848 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
849 for (int i = 0; i < g->vertexCount(); ++i)
850 vdst[i].set(vdst[i].x, vdst[i].y, d->strokeColor);
851 return;
852 }
853 }
854
855 g->allocate(d->strokeVertices.size(), 0);
856 g->setDrawingMode(QSGGeometry::DrawTriangleStrip);
857 memcpy(g->vertexData(), d->strokeVertices.constData(), g->vertexCount() * g->sizeOfVertex());
858}
859
860QSGMaterial *QQuickShapeGenericMaterialFactory::createVertexColor(QQuickWindow *window)
861{
862 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
863
864 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
865 return new QSGVertexColorMaterial;
866
867 qWarning("Vertex-color material: Unsupported graphics API %d", api);
868 return nullptr;
869}
870
871QSGMaterial *QQuickShapeGenericMaterialFactory::createLinearGradient(QQuickWindow *window,
873{
874 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
875
876 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
878
879 qWarning("Linear gradient material: Unsupported graphics API %d", api);
880 return nullptr;
881}
882
883QSGMaterial *QQuickShapeGenericMaterialFactory::createRadialGradient(QQuickWindow *window,
885{
886 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
887
888 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
890
891 qWarning("Radial gradient material: Unsupported graphics API %d", api);
892 return nullptr;
893}
894
895QSGMaterial *QQuickShapeGenericMaterialFactory::createConicalGradient(QQuickWindow *window,
897{
898 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
899
900 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
902
903 qWarning("Conical gradient material: Unsupported graphics API %d", api);
904 return nullptr;
905}
906
907QSGMaterial *QQuickShapeGenericMaterialFactory::createTextureFill(QQuickWindow *window,
909{
910 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
911
912 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
914
915 qWarning("Texture fill material: Unsupported graphics API %d", api);
916 return nullptr;
917}
918
920{
921 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.vert.qsb"), viewCount);
922 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.frag.qsb"), viewCount);
923}
924
926 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
927{
928 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
929 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
930 bool changed = false;
931 QByteArray *buf = state.uniformData();
932 Q_ASSERT(buf->size() >= 84 + 64);
933 const int shaderMatrixCount = newMaterial->viewCount();
934 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
935
936 if (state.isMatrixDirty()) {
937 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
938 const QMatrix4x4 m = state.combinedMatrix();
939 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
940 changed = true;
941 }
942 }
943
945
946 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
947 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
948 m_fillTransform = node->m_fillTransform;
949 changed = true;
950 }
951
952 if (!oldMaterial || m_gradA.x() != node->m_gradient.a.x() || m_gradA.y() != node->m_gradient.a.y()) {
953 m_gradA = QVector2D(node->m_gradient.a.x(), node->m_gradient.a.y());
954 Q_ASSERT(sizeof(m_gradA) == 8);
955 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_gradA, 8);
956 changed = true;
957 }
958
959 if (!oldMaterial || m_gradB.x() != node->m_gradient.b.x() || m_gradB.y() != node->m_gradient.b.y()) {
960 m_gradB = QVector2D(node->m_gradient.b.x(), node->m_gradient.b.y());
961 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_gradB, 8);
962 changed = true;
963 }
964
965 if (state.isOpacityDirty()) {
966 const float opacity = state.opacity();
967 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, &opacity, 4);
968 changed = true;
969 }
970
971 return changed;
972}
973
974void QQuickShapeLinearGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
975 QSGMaterial *newMaterial, QSGMaterial *)
976{
977 if (binding != 1)
978 return;
979
980 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
982 const QSGGradientCacheKey cacheKey(node->m_gradient.stops, QGradient::Spread(node->m_gradient.spread));
983 QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
984 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
985 *texture = t;
986}
987
989{
990 static QSGMaterialType type;
991 return &type;
992}
993
994int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const
995{
996 Q_ASSERT(other && type() == other->type());
997 const QQuickShapeLinearGradientMaterial *m = static_cast<const QQuickShapeLinearGradientMaterial *>(other);
998
1001 Q_ASSERT(a && b);
1002 if (a == b)
1003 return 0;
1004
1005 const QSGGradientCache::GradientDesc *ga = &a->m_gradient;
1006 const QSGGradientCache::GradientDesc *gb = &b->m_gradient;
1007
1008 if (int d = ga->spread - gb->spread)
1009 return d;
1010
1011 if (int d = ga->a.x() - gb->a.x())
1012 return d;
1013 if (int d = ga->a.y() - gb->a.y())
1014 return d;
1015 if (int d = ga->b.x() - gb->b.x())
1016 return d;
1017 if (int d = ga->b.y() - gb->b.y())
1018 return d;
1019
1020 if (int d = ga->stops.size() - gb->stops.size())
1021 return d;
1022
1023 for (int i = 0; i < ga->stops.size(); ++i) {
1024 if (int d = ga->stops[i].first - gb->stops[i].first)
1025 return d;
1026 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1027 return d;
1028 }
1029
1030 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1031 return d;
1032
1033 return 0;
1034}
1035
1036QSGMaterialShader *QQuickShapeLinearGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1037{
1038 Q_UNUSED(renderMode);
1039 return new QQuickShapeLinearGradientRhiShader(viewCount());
1040}
1041
1043{
1044 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.vert.qsb"), viewCount);
1045 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.frag.qsb"), viewCount);
1046}
1047
1049 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1050{
1051 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1052 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
1053 bool changed = false;
1054 QByteArray *buf = state.uniformData();
1055 Q_ASSERT(buf->size() >= 92 + 64);
1056 const int shaderMatrixCount = newMaterial->viewCount();
1057 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
1058
1059 if (state.isMatrixDirty()) {
1060 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1061 const QMatrix4x4 m = state.combinedMatrix();
1062 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
1063 changed = true;
1064 }
1065 }
1066
1068
1069 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1070 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
1071 m_fillTransform = node->m_fillTransform;
1072 changed = true;
1073 }
1074
1075 const QPointF centerPoint = node->m_gradient.a;
1076 const QPointF focalPoint = node->m_gradient.b;
1077 const QPointF focalToCenter = centerPoint - focalPoint;
1078 const float centerRadius = node->m_gradient.v0;
1079 const float focalRadius = node->m_gradient.v1;
1080
1081 if (!oldMaterial || m_focalPoint.x() != focalPoint.x() || m_focalPoint.y() != focalPoint.y()) {
1082 m_focalPoint = QVector2D(focalPoint.x(), focalPoint.y());
1083 Q_ASSERT(sizeof(m_focalPoint) == 8);
1084 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_focalPoint, 8);
1085 changed = true;
1086 }
1087
1088 if (!oldMaterial || m_focalToCenter.x() != focalToCenter.x() || m_focalToCenter.y() != focalToCenter.y()) {
1089 m_focalToCenter = QVector2D(focalToCenter.x(), focalToCenter.y());
1090 Q_ASSERT(sizeof(m_focalToCenter) == 8);
1091 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_focalToCenter, 8);
1092 changed = true;
1093 }
1094
1095 if (!oldMaterial || m_centerRadius != centerRadius) {
1096 m_centerRadius = centerRadius;
1097 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, &m_centerRadius, 4);
1098 changed = true;
1099 }
1100
1101 if (!oldMaterial || m_focalRadius != focalRadius) {
1102 m_focalRadius = focalRadius;
1103 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4, &m_focalRadius, 4);
1104 changed = true;
1105 }
1106
1107 if (state.isOpacityDirty()) {
1108 const float opacity = state.opacity();
1109 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4 + 4, &opacity, 4);
1110 changed = true;
1111 }
1112
1113 return changed;
1114}
1115
1116void QQuickShapeRadialGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1117 QSGMaterial *newMaterial, QSGMaterial *)
1118{
1119 if (binding != 1)
1120 return;
1121
1122 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
1124 const QSGGradientCacheKey cacheKey(node->m_gradient.stops, QGradient::Spread(node->m_gradient.spread));
1125 QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
1126 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1127 *texture = t;
1128}
1129
1131{
1132 static QSGMaterialType type;
1133 return &type;
1134}
1135
1136int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const
1137{
1138 Q_ASSERT(other && type() == other->type());
1139 const QQuickShapeRadialGradientMaterial *m = static_cast<const QQuickShapeRadialGradientMaterial *>(other);
1140
1143 Q_ASSERT(a && b);
1144 if (a == b)
1145 return 0;
1146
1147 const QSGGradientCache::GradientDesc *ga = &a->m_gradient;
1148 const QSGGradientCache::GradientDesc *gb = &b->m_gradient;
1149
1150 if (int d = ga->spread - gb->spread)
1151 return d;
1152
1153 if (int d = ga->a.x() - gb->a.x())
1154 return d;
1155 if (int d = ga->a.y() - gb->a.y())
1156 return d;
1157 if (int d = ga->b.x() - gb->b.x())
1158 return d;
1159 if (int d = ga->b.y() - gb->b.y())
1160 return d;
1161
1162 if (int d = ga->v0 - gb->v0)
1163 return d;
1164 if (int d = ga->v1 - gb->v1)
1165 return d;
1166
1167 if (int d = ga->stops.size() - gb->stops.size())
1168 return d;
1169
1170 for (int i = 0; i < ga->stops.size(); ++i) {
1171 if (int d = ga->stops[i].first - gb->stops[i].first)
1172 return d;
1173 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1174 return d;
1175 }
1176
1177 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1178 return d;
1179
1180 return 0;
1181}
1182
1183QSGMaterialShader *QQuickShapeRadialGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1184{
1185 Q_UNUSED(renderMode);
1186 return new QQuickShapeRadialGradientRhiShader(viewCount());
1187}
1188
1190{
1191 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.vert.qsb"), viewCount);
1192 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.frag.qsb"), viewCount);
1193}
1194
1196 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1197{
1198 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1199 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
1200 bool changed = false;
1201 QByteArray *buf = state.uniformData();
1202 Q_ASSERT(buf->size() >= 80 + 64);
1203 const int shaderMatrixCount = newMaterial->viewCount();
1204 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
1205
1206 if (state.isMatrixDirty()) {
1207 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1208 const QMatrix4x4 m = state.combinedMatrix();
1209 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
1210 changed = true;
1211 }
1212 }
1213
1215
1216 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1217 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
1218 m_fillTransform = node->m_fillTransform;
1219 changed = true;
1220 }
1221
1222 const QPointF centerPoint = node->m_gradient.a;
1223 const float angle = -qDegreesToRadians(node->m_gradient.v0);
1224
1225 if (!oldMaterial || m_centerPoint.x() != centerPoint.x() || m_centerPoint.y() != centerPoint.y()) {
1226 m_centerPoint = QVector2D(centerPoint.x(), centerPoint.y());
1227 Q_ASSERT(sizeof(m_centerPoint) == 8);
1228 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_centerPoint, 8);
1229 changed = true;
1230 }
1231
1232 if (!oldMaterial || m_angle != angle) {
1233 m_angle = angle;
1234 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_angle, 4);
1235 changed = true;
1236 }
1237
1238 if (state.isOpacityDirty()) {
1239 const float opacity = state.opacity();
1240 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 4, &opacity, 4);
1241 changed = true;
1242 }
1243
1244 return changed;
1245}
1246
1247void QQuickShapeConicalGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1248 QSGMaterial *newMaterial, QSGMaterial *)
1249{
1250 if (binding != 1)
1251 return;
1252
1253 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
1255 const QSGGradientCacheKey cacheKey(node->m_gradient.stops, QGradient::Spread(node->m_gradient.spread));
1256 QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
1257 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1258 *texture = t;
1259}
1260
1262{
1263 static QSGMaterialType type;
1264 return &type;
1265}
1266
1267int QQuickShapeConicalGradientMaterial::compare(const QSGMaterial *other) const
1268{
1269 Q_ASSERT(other && type() == other->type());
1270 const QQuickShapeConicalGradientMaterial *m = static_cast<const QQuickShapeConicalGradientMaterial *>(other);
1271
1274 Q_ASSERT(a && b);
1275 if (a == b)
1276 return 0;
1277
1278 const QSGGradientCache::GradientDesc *ga = &a->m_gradient;
1279 const QSGGradientCache::GradientDesc *gb = &b->m_gradient;
1280
1281 if (int d = ga->a.x() - gb->a.x())
1282 return d;
1283 if (int d = ga->a.y() - gb->a.y())
1284 return d;
1285
1286 if (int d = ga->v0 - gb->v0)
1287 return d;
1288
1289 if (int d = ga->stops.size() - gb->stops.size())
1290 return d;
1291
1292 for (int i = 0; i < ga->stops.size(); ++i) {
1293 if (int d = ga->stops[i].first - gb->stops[i].first)
1294 return d;
1295 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1296 return d;
1297 }
1298
1299 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1300 return d;
1301
1302 return 0;
1303}
1304
1305QSGMaterialShader *QQuickShapeConicalGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1306{
1307 Q_UNUSED(renderMode);
1308 return new QQuickShapeConicalGradientRhiShader(viewCount());
1309}
1310
1312{
1313 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.vert.qsb"), viewCount);
1314 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.frag.qsb"), viewCount);
1315}
1316
1318 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1319{
1320 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1321 QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
1322 bool changed = false;
1323 QByteArray *buf = state.uniformData();
1324 const int shaderMatrixCount = newMaterial->viewCount();
1325 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
1326 Q_ASSERT(buf->size() >= 64 * shaderMatrixCount + 64 + 8 + 4);
1327
1328 if (state.isMatrixDirty()) {
1329 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1330 const QMatrix4x4 m = state.combinedMatrix();
1331 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
1332 changed = true;
1333 }
1334 }
1335
1337
1338 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1339 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
1340 m_fillTransform = node->m_fillTransform;
1341 changed = true;
1342 }
1343
1344 const QSizeF boundsSize = node->m_fillTextureProvider != nullptr && node->m_fillTextureProvider->texture() != nullptr
1345 ? node->m_fillTextureProvider->texture()->textureSize()
1346 : QSizeF(0, 0);
1347
1348
1349 const QVector2D boundsVector(boundsSize.width() / state.devicePixelRatio(),
1350 boundsSize.height() / state.devicePixelRatio());
1351 if (!oldMaterial || m_boundsSize != boundsVector) {
1352 m_boundsSize = boundsVector;
1353 Q_ASSERT(sizeof(m_boundsSize) == 8);
1354 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_boundsSize, 8);
1355 changed = true;
1356 }
1357
1358 if (state.isOpacityDirty()) {
1359 const float opacity = state.opacity();
1360 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &opacity, 4);
1361 changed = true;
1362 }
1363
1364 return changed;
1365}
1366
1367void QQuickShapeTextureFillRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1368 QSGMaterial *newMaterial, QSGMaterial *)
1369{
1370 if (binding != 1)
1371 return;
1372
1373 QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
1375 if (node->m_fillTextureProvider != nullptr) {
1376 QSGTexture *providedTexture = node->m_fillTextureProvider->texture();
1377 if (providedTexture != nullptr) {
1378 if (providedTexture->isAtlasTexture()) {
1379 // Create a non-atlas copy to make texture coordinate wrapping work. This
1380 // texture copy is owned by the QSGTexture so memory is managed with the original
1381 // texture provider.
1382 QSGTexture *newTexture = providedTexture->removedFromAtlas(state.resourceUpdateBatch());
1383 if (newTexture != nullptr)
1384 providedTexture = newTexture;
1385 }
1386
1387 providedTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1388 *texture = providedTexture;
1389 return;
1390 }
1391 }
1392
1393 if (m->dummyTexture() == nullptr) {
1394 QSGPlainTexture *dummyTexture = new QSGPlainTexture;
1395 dummyTexture->setFiltering(QSGTexture::Nearest);
1396 dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
1397 dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
1398 QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
1399 img.fill(0);
1400 dummyTexture->setImage(img);
1401 dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1402
1403 m->setDummyTexture(dummyTexture);
1404 }
1405
1406 *texture = m->dummyTexture();
1407}
1408
1410{
1411 delete m_dummyTexture;
1412}
1413
1415{
1416 static QSGMaterialType type;
1417 return &type;
1418}
1419
1420int QQuickShapeTextureFillMaterial::compare(const QSGMaterial *other) const
1421{
1422 Q_ASSERT(other && type() == other->type());
1423 const QQuickShapeTextureFillMaterial *m = static_cast<const QQuickShapeTextureFillMaterial *>(other);
1424
1427 Q_ASSERT(a && b);
1428 if (a == b)
1429 return 0;
1430
1431 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1432 return d;
1433
1434 const qintptr diff = qintptr(a->m_fillTextureProvider) - qintptr(b->m_fillTextureProvider);
1435 return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
1436}
1437
1438QSGMaterialShader *QQuickShapeTextureFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1439{
1440 Q_UNUSED(renderMode);
1441 return new QQuickShapeTextureFillRhiShader(viewCount());
1442}
1443
1444QT_END_NAMESPACE
1445
1446#include "moc_qquickshapegenericrenderer_p.cpp"
QSGMaterialType * type() const override
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
QQuickShapeConicalGradientMaterial(QQuickShapeGenericStrokeFillNode *node)
int compare(const QSGMaterial *other) const override
Compares this material to other and returns 0 if they are equal; -1 if this material should sort befo...
QSGMaterialShader * createShader(QSGRendererInterface::RenderMode renderMode) const override
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
QQuickShapeGenericStrokeFillNode * node() const
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to prepare use of sampled images in the shader,...
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to get the contents of the shader program's uniform buffer...
QQuickShapeGenericRenderer::Color4ub fillColor
QQuickShapeGenericRenderer::VertexContainerType fillVertices
QQuickShapeGenericRenderer::IndexContainerType fillIndices
QQuickShapeGenericStrokeFillNode * m_fillNode
QQuickShapeGenericStrokeFillNode * m_strokeNode
void setAsyncCallback(void(*)(void *), void *) override
void setFillGradient(int index, QQuickShapeGradient *gradient) override
void setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle, qreal dashOffset, const QList< qreal > &dashPattern) override
void setFillTransform(int index, const QSGTransform &transform) override
void setCosmeticStroke(int index, bool c) override
void setStrokeGradient(int index, QQuickShapeGradient *gradient) override
QList< QSGGeometry::ColoredPoint2D > VertexContainerType
void setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints pathHints={}) override
void setStrokeColor(int index, const QColor &color) override
void setFillRule(int index, QQuickShapePath::FillRule fillRule) override
void setFillTextureProvider(int index, QQuickItem *textureProviderItem) override
void setFillColor(int index, const QColor &color) override
void setTriangulationScale(int index, qreal scale) override
void setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit) override
void setStrokeWidth(int index, qreal w) override
void setRootNode(QQuickShapeGenericNode *node)
void handleSceneChange(QQuickWindow *window) override
void beginSync(int totalCount, bool *countChanged) override
void setCapStyle(int index, QQuickShapePath::CapStyle capStyle) override
void preprocess() override
Override this function to do processing on the node before it is rendered.
void activateMaterial(QQuickWindow *window, Material m)
QQuickShapeLinearGradientMaterial(QQuickShapeGenericStrokeFillNode *node)
QSGMaterialShader * createShader(QSGRendererInterface::RenderMode renderMode) const override
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
int compare(const QSGMaterial *other) const override
Compares this material to other and returns 0 if they are equal; -1 if this material should sort befo...
QSGMaterialType * type() const override
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
QQuickShapeGenericStrokeFillNode * node() const
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to prepare use of sampled images in the shader,...
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to get the contents of the shader program's uniform buffer...
QQuickShapeGenericStrokeFillNode * node() const
QQuickShapeRadialGradientMaterial(QQuickShapeGenericStrokeFillNode *node)
QSGMaterialType * type() const override
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
QSGMaterialShader * createShader(QSGRendererInterface::RenderMode renderMode) const override
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
int compare(const QSGMaterial *other) const override
Compares this material to other and returns 0 if they are equal; -1 if this material should sort befo...
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to get the contents of the shader program's uniform buffer...
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to prepare use of sampled images in the shader,...
QQuickShapeGenericRenderer::Color4ub strokeColor
QQuickShapeGenericRenderer::VertexContainerType strokeVertices
QQuickShapeTextureFillMaterial(QQuickShapeGenericStrokeFillNode *node)
QSGMaterialShader * createShader(QSGRendererInterface::RenderMode renderMode) const override
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
int compare(const QSGMaterial *other) const override
Compares this material to other and returns 0 if they are equal; -1 if this material should sort befo...
QQuickShapeGenericStrokeFillNode * node() const
QSGMaterialType * type() const override
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to get the contents of the shader program's uniform buffer...
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to prepare use of sampled images in the shader,...
Combined button and popup list for selecting options.
static QQuickShapeGenericRenderer::Color4ub colorToColor4ub(const QColor &c)
static QQuickAbstractPathRenderer::FillGradientType copyGenericGradient(const QQuickShapeGradient *gradient, QSGGradientCache::GradientDesc *dst)
#define QSG_RUNTIME_DESCRIPTION
Definition qsgnode.h:17
void set(float nx, float ny, QQuickShapeGenericRenderer::Color4ub ncolor)