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::setVisible(int index, bool visible)
181{
182 ShapePathData &d(m_sp[index]);
183 if (d.visible == visible)
184 return;
185 d.visible = visible;
186 d.syncDirty |= DirtyVisible;
187 if (visible) {
188 d.syncDirty |= d.hiddenGeomDirty;
189 d.hiddenGeomDirty = 0;
190 }
191}
192
193void QQuickShapeGenericRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
194{
195 ShapePathData &d(m_sp[index]);
196 d.fillRule = Qt::FillRule(fillRule);
197 d.syncDirty |= DirtyFillGeom;
198}
199
200void QQuickShapeGenericRenderer::setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit)
201{
202 ShapePathData &d(m_sp[index]);
203 d.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
204 d.pen.setMiterLimit(miterLimit);
205 d.syncDirty |= DirtyStrokeGeom;
206}
207
208void QQuickShapeGenericRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
209{
210 ShapePathData &d(m_sp[index]);
211 d.pen.setCapStyle(Qt::PenCapStyle(capStyle));
212 d.syncDirty |= DirtyStrokeGeom;
213}
214
215void QQuickShapeGenericRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
216 qreal dashOffset, const QList<qreal> &dashPattern)
217{
218 ShapePathData &d(m_sp[index]);
219 d.pen.setStyle(Qt::PenStyle(strokeStyle));
220 if (strokeStyle == QQuickShapePath::DashLine) {
221 d.pen.setDashPattern(dashPattern);
222 d.pen.setDashOffset(dashOffset);
223 }
224 d.syncDirty |= DirtyStrokeGeom;
225}
226
228 copyGenericGradient(const QQuickShapeGradient *gradient, QSGGradientCache::GradientDesc *dst)
229{
230 Q_ASSERT(dst != nullptr);
231 if (gradient == nullptr)
232 return QQuickAbstractPathRenderer::NoGradient;
233
234 dst->stops = gradient->gradientStops(); // sorted
235 dst->spread = QGradient::Spread(gradient->spread());
236 if (const QQuickShapeLinearGradient *g = qobject_cast<const QQuickShapeLinearGradient *>(gradient)) {
237 dst->a = QPointF(g->x1(), g->y1());
238 dst->b = QPointF(g->x2(), g->y2());
239
240 return QQuickAbstractPathRenderer::LinearGradient;
241 } else if (const QQuickShapeRadialGradient *g = qobject_cast<const QQuickShapeRadialGradient *>(gradient)) {
242 dst->a = QPointF(g->centerX(), g->centerY());
243 dst->b = QPointF(g->focalX(), g->focalY());
244 dst->v0 = g->centerRadius();
245 dst->v1 = g->focalRadius();
246
247 return QQuickAbstractPathRenderer::RadialGradient;
248 } else if (const QQuickShapeConicalGradient *g = qobject_cast<const QQuickShapeConicalGradient *>(gradient)) {
249 dst->a = QPointF(g->centerX(), g->centerY());
250 dst->v0 = g->angle();
251
252 return QQuickAbstractPathRenderer::ConicalGradient;
253 }
254
255 Q_UNREACHABLE_RETURN(QQuickAbstractPathRenderer::NoGradient);
256}
257
258void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
259{
260 ShapePathData &d(m_sp[index]);
261 d.fillGradientActive = copyGenericGradient(gradient, &d.fillGradient);
262 d.syncDirty |= DirtyFillGradient;
263}
264
265void QQuickShapeGenericRenderer::setStrokeGradient(int index, QQuickShapeGradient *gradient)
266{
267 ShapePathData &d(m_sp[index]);
268 d.strokeGradientActive = copyGenericGradient(gradient, &d.strokeGradient);
269 d.syncDirty |= DirtyStrokeGradient;
270}
271
272void QQuickShapeGenericRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
273{
274 ShapePathData &d(m_sp[index]);
275 if ((d.fillTextureProviderItem == nullptr) != (textureProviderItem == nullptr))
276 d.syncDirty |= DirtyFillGeom;
277 if (d.fillTextureProviderItem != nullptr)
278 QQuickItemPrivate::get(d.fillTextureProviderItem)->derefWindow();
279 d.fillTextureProviderItem = textureProviderItem;
280 if (d.fillTextureProviderItem != nullptr)
281 QQuickItemPrivate::get(d.fillTextureProviderItem)->refWindow(m_item->window());
282 d.syncDirty |= DirtyFillTexture;
283}
284
286{
287 for (auto &pathData : m_sp) {
288 if (pathData.fillTextureProviderItem != nullptr) {
289 if (window == nullptr)
290 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
291 else
292 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(window);
293 }
294 }
295}
296
297
298void QQuickShapeGenericRenderer::setFillTransform(int index, const QSGTransform &transform)
299{
300 ShapePathData &d(m_sp[index]);
301 d.fillTransform = transform;
302 d.syncDirty |= DirtyFillTransform;
303}
304
306{
307 ShapePathData &d(m_sp[index]);
308 d.triangulationScale = scale;
309 d.syncDirty |= DirtyStrokeGeom;
310}
311
313{
314 QQuickShapeGenericRenderer::triangulateFill(path, fillColor, &fillVertices, &fillIndices, &indexType,
315 supportsElementIndexUint, triangulationScale);
316 // Emit done signal on main thread, since connected to lambda there
317 QMetaObject::invokeMethod(qApp, [this]() { emit done(this); }, Qt::QueuedConnection);
318}
319
321{
322 QQuickShapeGenericRenderer::triangulateStroke(path, pen, strokeColor, &strokeVertices, clipSize, triangulationScale);
323 // Emit done signal on main thread, since connected to lambda there
324 QMetaObject::invokeMethod(qApp, [this]() { emit done(this); }, Qt::QueuedConnection);
325}
326
327void QQuickShapeGenericRenderer::setAsyncCallback(void (*callback)(void *), void *data)
328{
329 m_asyncCallback = callback;
330 m_asyncCallbackData = data;
331}
332
333#if QT_CONFIG(thread)
334static QThreadPool *pathWorkThreadPool = nullptr;
335
336static void deletePathWorkThreadPool()
337{
338 delete pathWorkThreadPool;
339 pathWorkThreadPool = nullptr;
340}
341#endif
342
344{
345#if !QT_CONFIG(thread)
346 // Force synchronous mode for the no-thread configuration due
347 // to lack of QThreadPool.
348 async = false;
349#endif
350
351 bool didKickOffAsync = false;
352
353 for (int i = 0; i < m_sp.size(); ++i) {
354 ShapePathData &d(m_sp[i]);
355 if (!d.syncDirty)
356 continue;
357
358 m_accDirty |= d.syncDirty;
359
360 // Use a shadow dirty flag in order to avoid losing state in case there are
361 // multiple syncs with different dirty flags before we get to updateNode()
362 // on the render thread (with the gui thread blocked). For our purposes
363 // here syncDirty is still required since geometry regeneration must only
364 // happen when there was an actual change in this particular sync round.
365 d.effectiveDirty |= d.syncDirty;
366
367 // While a path is hidden the triangulation below is skipped, and both
368 // dirty masks are cleared before it is shown again. Remember what was
369 // skipped so that setVisible() can redo exactly that much, instead of
370 // re-triangulating on every show.
371 if (!d.visible)
372 d.hiddenGeomDirty |= d.syncDirty & (DirtyFillGeom | DirtyStrokeGeom);
373
374 if (d.path.isEmpty()) {
375 d.fillVertices.clear();
376 d.fillIndices.clear();
377 d.strokeVertices.clear();
378 continue;
379 }
380
381#if QT_CONFIG(thread)
382 if (async && !pathWorkThreadPool) {
383 qAddPostRoutine(deletePathWorkThreadPool);
384 pathWorkThreadPool = new QThreadPool;
385 const int idealCount = QThread::idealThreadCount();
386 pathWorkThreadPool->setMaxThreadCount(idealCount > 0 ? idealCount * 2 : 4);
387 }
388#endif
389
390 // RHI backend might not have had a chance of being initialized yet
391 // (we are called in the mainthread while renderthread might be in the
392 // process of starting up and setting the RHI backend.
393 // m_rhiBackendInitialized is set in updateNode() on the render thread
394 // when both threads are locked.
395 // Until then, we cannot assume we have support for uint32_t indices
396 // but only uint16_t ones.
397 const bool supportsElementIndexUint = m_rhiBackendInitialized ? m_supportsElementIndexUint : true;
398 if ((d.syncDirty & DirtyFillGeom) && d.fillColor.a && d.visible) {
399 d.path.setFillRule(d.fillRule);
400 if (m_api == QSGRendererInterface::Unknown)
401 m_api = m_item->window()->rendererInterface()->graphicsApi();
402 if (async) {
404 r->setAutoDelete(false);
405 if (d.pendingFill)
406 d.pendingFill->orphaned = true;
407 d.pendingFill = r;
408 r->path = d.path;
409 r->fillColor = d.fillColor;
410 r->supportsElementIndexUint = supportsElementIndexUint;
411 r->triangulationScale = d.triangulationScale;
412 // Unlikely in practice but in theory m_sp could be
413 // resized. Therefore, capture 'i' instead of 'd'.
414 QObject::connect(r, &QQuickShapeFillRunnable::done, qApp, [this, i](QQuickShapeFillRunnable *r) {
415 // Bail out when orphaned (meaning either another run was
416 // started after this one, or the renderer got destroyed).
417 if (!r->orphaned && i < m_sp.size()) {
418 ShapePathData &d(m_sp[i]);
419 d.fillVertices = r->fillVertices;
420 d.fillIndices = r->fillIndices;
421 d.indexType = r->indexType;
422 d.pendingFill = nullptr;
423 d.effectiveDirty |= DirtyFillGeom;
424 maybeUpdateAsyncItem();
425 }
426 r->deleteLater();
427 });
428 didKickOffAsync = true;
429#if QT_CONFIG(thread)
430 // qtVectorPathForPath() initializes a unique_ptr without locking.
431 // Do that before starting the threads as otherwise we get a race condition.
432 qtVectorPathForPath(r->path);
433 pathWorkThreadPool->start(r);
434#endif
435 } else {
436 triangulateFill(d.path, d.fillColor, &d.fillVertices, &d.fillIndices, &d.indexType,
437 supportsElementIndexUint,
438 d.triangulationScale);
439 }
440 }
441
442 if ((d.syncDirty & DirtyStrokeGeom) && d.strokeWidth > 0.0f
443 && (d.strokeColor.a || d.strokeGradientActive) && d.visible) {
444 if (async) {
446 r->setAutoDelete(false);
447 if (d.pendingStroke)
448 d.pendingStroke->orphaned = true;
449 d.pendingStroke = r;
450 r->path = d.path;
451 r->pen = d.pen;
452 r->strokeColor = d.strokeColor;
453 r->clipSize = QSize(m_item->width(), m_item->height());
454 r->triangulationScale = d.triangulationScale;
455 QObject::connect(r, &QQuickShapeStrokeRunnable::done, qApp, [this, i](QQuickShapeStrokeRunnable *r) {
456 if (!r->orphaned && i < m_sp.size()) {
457 ShapePathData &d(m_sp[i]);
458 d.strokeVertices = r->strokeVertices;
459 d.pendingStroke = nullptr;
460 d.effectiveDirty |= DirtyStrokeGeom;
461 maybeUpdateAsyncItem();
462 }
463 r->deleteLater();
464 });
465 didKickOffAsync = true;
466#if QT_CONFIG(thread)
467 // qtVectorPathForPath() initializes a unique_ptr without locking.
468 // Do that before starting the threads as otherwise we get a race condition.
469 qtVectorPathForPath(r->path);
470 pathWorkThreadPool->start(r);
471#endif
472 } else {
473 triangulateStroke(d.path, d.pen, d.strokeColor, &d.strokeVertices,
474 QSize(m_item->width(), m_item->height()), d.triangulationScale);
475 }
476 }
477 }
478
479 if (!didKickOffAsync && async && m_asyncCallback)
480 m_asyncCallback(m_asyncCallbackData);
481}
482
483void QQuickShapeGenericRenderer::maybeUpdateAsyncItem()
484{
485 for (const ShapePathData &d : std::as_const(m_sp)) {
486 if (d.pendingFill || d.pendingStroke)
487 return;
488 }
489 m_accDirty |= DirtyFillGeom | DirtyStrokeGeom;
490 m_item->update();
491 if (m_asyncCallback)
492 m_asyncCallback(m_asyncCallbackData);
493}
494
495// the stroke/fill triangulation functions may be invoked either on the gui
496// thread or some worker thread and must thus be self-contained.
497void QQuickShapeGenericRenderer::triangulateFill(const QPainterPath &path,
498 const Color4ub &fillColor,
499 VertexContainerType *fillVertices,
500 IndexContainerType *fillIndices,
501 QSGGeometry::Type *indexType,
502 bool supportsElementIndexUint,
503 qreal triangulationScale)
504{
505 const QVectorPath &vp = qtVectorPathForPath(path);
506
507 QTriangleSet ts = qTriangulate(vp, QTransform::fromScale(triangulationScale, triangulationScale), 1, supportsElementIndexUint);
508 const int vertexCount = ts.vertices.size() / 2; // just a qreal vector with x,y hence the / 2
509 fillVertices->resize(vertexCount);
510 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(fillVertices->data());
511 const qreal *vsrc = ts.vertices.constData();
512 for (int i = 0; i < vertexCount; ++i)
513 vdst[i].set(vsrc[i * 2] / triangulationScale, vsrc[i * 2 + 1] / triangulationScale, fillColor);
514
515 size_t indexByteSize;
516 if (ts.indices.type() == QVertexIndexVector::UnsignedShort || vertexCount <= 0xffff) {
517 *indexType = QSGGeometry::UnsignedShortType;
518 const int indexCount = ts.indices.size();
519
520 // fillIndices is still QList<quint32>. Just resize to N/2 and pack
521 // the N quint16s into it. To ensure that there is enough space to hold the full buffer,
522 // we round the number up. Any redundant padding indexes will be ignored as the renderer
523 // processes geometry in triplets.
524 fillIndices->resize((indexCount + 1) / 2);
525
526 // If indices are 32-bit, we convert to 16-bit and return early.
527 if (ts.indices.type() != QVertexIndexVector::UnsignedShort) {
528 quint16 *dst = reinterpret_cast<quint16 *>(fillIndices->data());
529 const quint32 *src = reinterpret_cast<const quint32 *>(ts.indices.data());
530 for (int i = 0; i < indexCount; ++i)
531 dst[i] = quint16(src[i]);
532 return; // Early exit to skip memcpy()
533 }
534
535 indexByteSize = indexCount * sizeof(quint16);
536 } else {
537 *indexType = QSGGeometry::UnsignedIntType;
538 fillIndices->resize(ts.indices.size());
539 indexByteSize = ts.indices.size() * sizeof(quint32);
540 }
541
542 Q_ASSERT(fillIndices->size() * sizeof(quint32) >= indexByteSize);
543 memcpy(fillIndices->data(), ts.indices.data(), indexByteSize);
544}
545
546void QQuickShapeGenericRenderer::triangulateStroke(const QPainterPath &path,
547 const QPen &pen,
548 const Color4ub &strokeColor,
549 VertexContainerType *strokeVertices,
550 const QSize &clipSize,
551 qreal triangulationScale)
552{
553 const QVectorPath &vp = qtVectorPathForPath(path);
554 const QRectF clip(QPointF(0, 0), clipSize);
555 const qreal inverseScale = 1.0 / triangulationScale;
556
557 QTriangulatingStroker stroker;
558 stroker.setInvScale(inverseScale);
559
560 if (pen.style() == Qt::SolidLine) {
561 stroker.process(vp, pen, clip, {});
562 } else {
563 QDashedStrokeProcessor dashStroker;
564 dashStroker.setInvScale(inverseScale);
565 dashStroker.process(vp, pen, clip, {});
566 QVectorPath dashStroke(dashStroker.points(), dashStroker.elementCount(),
567 dashStroker.elementTypes(), 0);
568 stroker.process(dashStroke, pen, clip, {});
569 }
570
571 if (!stroker.vertexCount()) {
572 strokeVertices->clear();
573 return;
574 }
575
576 const int vertexCount = stroker.vertexCount() / 2; // just a float vector with x,y hence the / 2
577 strokeVertices->resize(vertexCount);
578 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(strokeVertices->data());
579 const float *vsrc = stroker.vertices();
580 for (int i = 0; i < vertexCount; ++i)
581 vdst[i].set(vsrc[i * 2], vsrc[i * 2 + 1], strokeColor);
582}
583
585{
586 if (m_rootNode != node) {
587 m_rootNode = node;
588 m_accDirty |= DirtyList;
589 }
590}
591
592// on the render thread with gui blocked
594{
595 if (!m_rootNode || !m_accDirty)
596 return;
597
598 // Is it now safe to query the rhi backend
599 if (!m_rhiBackendInitialized) {
600 auto findRHIBackend = [](QQuickItem *item) -> QRhi * {
601 if (auto *w = item->window()) {
602 if (auto *rhi = QQuickWindowPrivate::get(w)->rhi)
603 return rhi;
604 }
605 return nullptr;
606 };
607 auto *rhi = findRHIBackend(m_item);
608 if (rhi != nullptr) {
609 m_rhiBackendInitialized = true;
610 m_supportsElementIndexUint = rhi->isFeatureSupported(QRhi::ElementIndexUint);
611 }
612 }
613
614// [ m_rootNode ]
615// / / /
616// #0 [ fill ] [ stroke ] [ next ]
617// / / |
618// #1 [ fill ] [ stroke ] [ next ]
619// / / |
620// #2 [ fill ] [ stroke ] [ next ]
621// ...
622// ...
623
624 QQuickShapeGenericNode **nodePtr = &m_rootNode;
625 QQuickShapeGenericNode *prevNode = nullptr;
626
627 for (ShapePathData &d : m_sp) {
628 if (!*nodePtr) {
629 Q_ASSERT(prevNode);
630 *nodePtr = new QQuickShapeGenericNode;
631 prevNode->m_next = *nodePtr;
632 prevNode->appendChildNode(*nodePtr);
633 }
634
635 QQuickShapeGenericNode *node = *nodePtr;
636
637 if (m_accDirty & DirtyList) {
638 d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient
639 | DirtyFillTransform | DirtyFillTexture | DirtyStrokeGradient;
640 }
641
642 if (!d.effectiveDirty) {
643 prevNode = node;
644 nodePtr = &node->m_next;
645 continue;
646 }
647
648 if (!d.visible || d.fillColor.a == 0) {
649 delete node->m_fillNode;
650 node->m_fillNode = nullptr;
651 } else if (!node->m_fillNode) {
652 node->m_fillNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
653 if (node->m_strokeNode)
654 node->removeChildNode(node->m_strokeNode);
655 // Insert before m_next (rather than merely appending) since m_next,
656 // if present, is already a child holding the rest of the path chain
657 // and must remain the last child so paint order stays correct.
658 if (node->m_next)
659 node->insertChildNodeBefore(node->m_fillNode, node->m_next);
660 else
661 node->appendChildNode(node->m_fillNode);
662 if (node->m_strokeNode) {
663 if (node->m_next)
664 node->insertChildNodeBefore(node->m_strokeNode, node->m_next);
665 else
666 node->appendChildNode(node->m_strokeNode);
667 }
668 d.effectiveDirty |= DirtyFillGeom | DirtyColor | DirtyFillGradient
669 | DirtyFillTransform | DirtyFillTexture;
670 }
671
672 if (!d.visible || d.strokeWidth <= 0.0f || d.strokeColor.a == 0) {
673 delete node->m_strokeNode;
674 node->m_strokeNode = nullptr;
675 } else if (!node->m_strokeNode) {
676 node->m_strokeNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
677 if (node->m_next)
678 node->insertChildNodeBefore(node->m_strokeNode, node->m_next);
679 else
680 node->appendChildNode(node->m_strokeNode);
681 d.effectiveDirty |= DirtyStrokeGeom | DirtyColor | DirtyStrokeGradient;
682 }
683
684 updateFillNode(&d, node);
685 updateStrokeNode(&d, node);
686
687 d.effectiveDirty = 0;
688
689 prevNode = node;
690 nodePtr = &node->m_next;
691 }
692
693 if (*nodePtr && prevNode) {
694 prevNode->removeChildNode(*nodePtr);
695 delete *nodePtr;
696 *nodePtr = nullptr;
697 }
698
699 if (m_sp.isEmpty()) {
700 delete m_rootNode->m_fillNode;
701 m_rootNode->m_fillNode = nullptr;
702 delete m_rootNode->m_strokeNode;
703 m_rootNode->m_strokeNode = nullptr;
704 delete m_rootNode->m_next;
705 m_rootNode->m_next = nullptr;
706 }
707
708 m_accDirty = 0;
709}
710
711void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuickShapeGenericStrokeFillNode *n)
712{
713 if (d->fillGradientActive) {
714 if (d->effectiveDirty & DirtyFillGradient)
715 n->m_gradient = d->fillGradient;
716 }
717
718 if (d->effectiveDirty & DirtyFillTexture) {
719 bool needsUpdate = d->fillTextureProviderItem == nullptr && n->m_fillTextureProvider != nullptr;
720 if (!needsUpdate
721 && d->fillTextureProviderItem != nullptr
722 && n->m_fillTextureProvider != d->fillTextureProviderItem->textureProvider()) {
723 needsUpdate = true;
724 }
725
726 if (needsUpdate) {
727 if (n->m_fillTextureProvider != nullptr) {
728 QObject::disconnect(n->m_fillTextureProvider, &QSGTextureProvider::textureChanged,
729 n, &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
730 QObject::disconnect(n->m_fillTextureProvider, &QSGTextureProvider::destroyed,
731 n, &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
732 }
733
734 n->m_fillTextureProvider = d->fillTextureProviderItem == nullptr
735 ? nullptr
736 : d->fillTextureProviderItem->textureProvider();
737
738 if (n->m_fillTextureProvider != nullptr) {
739 QObject::connect(n->m_fillTextureProvider, &QSGTextureProvider::textureChanged,
740 n, &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
741 QObject::connect(n->m_fillTextureProvider, &QSGTextureProvider::destroyed,
742 n, &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
743 }
744 }
745 }
746 if (d->effectiveDirty & DirtyFillTransform)
747 n->m_fillTransform = d->fillTransform;
748}
749
750void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGenericNode *node)
751{
752 if (!node->m_fillNode)
753 return;
755 return;
756
757 // Make a copy of the data that will be accessed by the material on
758 // the render thread. This must be done even when we bail out below.
760 updateShadowDataInNode(d, n);
761
762 QSGGeometry *g = n->geometry();
763 if (d->fillVertices.isEmpty()) {
764 if (g->vertexCount() || g->indexCount()) {
765 g->allocate(0, 0);
766 n->markDirty(QSGNode::DirtyGeometry);
767 }
768 return;
769 }
770
771 if (d->fillGradientActive) {
773 switch (d->fillGradientActive) {
774 case LinearGradient:
776 break;
777 case RadialGradient:
779 break;
780 case ConicalGradient:
782 break;
783 default:
784 Q_UNREACHABLE_RETURN();
785 }
786 n->activateMaterial(m_item->window(), gradMat);
787 if (d->effectiveDirty & (DirtyFillGradient | DirtyFillTransform)) {
788 // Gradients are implemented via a texture-based material.
789 n->markDirty(QSGNode::DirtyMaterial);
790 // stop here if only the gradient or filltransform changed; no need to touch the geometry
791 if (!(d->effectiveDirty & DirtyFillGeom))
792 return;
793 }
794 } else if (d->fillTextureProviderItem != nullptr) {
795 n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatTextureFill);
796 if (d->effectiveDirty & DirtyFillTexture)
797 n->markDirty(QSGNode::DirtyMaterial);
798 } else {
799 n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatSolidColor);
800 // fast path for updating only color values when no change in vertex positions
801 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyFillGeom) && d->fillTextureProviderItem == nullptr) {
802 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
803 for (int i = 0; i < g->vertexCount(); ++i)
804 vdst[i].set(vdst[i].x, vdst[i].y, d->fillColor);
805 n->markDirty(QSGNode::DirtyGeometry);
806 return;
807 }
808 }
809
810 const int indexCount = d->indexType == QSGGeometry::UnsignedShortType
811 ? d->fillIndices.size() * 2 : d->fillIndices.size();
812 if (g->indexType() != d->indexType) {
813 g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(),
814 d->fillVertices.size(), indexCount, d->indexType);
815 n->setGeometry(g);
816 } else {
817 g->allocate(d->fillVertices.size(), indexCount);
818 }
819 g->setDrawingMode(QSGGeometry::DrawTriangles);
820
821 memcpy(g->vertexData(), d->fillVertices.constData(), g->vertexCount() * g->sizeOfVertex());
822 memcpy(g->indexData(), d->fillIndices.constData(), g->indexCount() * g->sizeOfIndex());
823
824 n->markDirty(QSGNode::DirtyGeometry);
825}
826
827void QQuickShapeGenericRenderer::updateStrokeNode(ShapePathData *d, QQuickShapeGenericNode *node)
828{
829 if (!node->m_strokeNode)
830 return;
831 if (!(d->effectiveDirty & (DirtyStrokeGeom | DirtyColor | DirtyStrokeGradient)))
832 return;
833
835 if (d->strokeGradientActive) {
836 if (d->effectiveDirty & DirtyStrokeGradient)
837 n->m_gradient = d->strokeGradient;
838 }
839
840 QSGGeometry *g = n->geometry();
841 if (d->strokeVertices.isEmpty()) {
842 if (g->vertexCount() || g->indexCount()) {
843 g->allocate(0, 0);
844 n->markDirty(QSGNode::DirtyGeometry);
845 }
846 return;
847 }
848
849 n->markDirty(QSGNode::DirtyGeometry);
850
851 // Async loading runs update once, bails out above, then updates again once
852 // ready. Set the material dirty then. This is in-line with fill where the
853 // first activateMaterial() achieves the same.
854 if (!g->vertexCount())
855 n->markDirty(QSGNode::DirtyMaterial);
856
857 if (d->strokeGradientActive) {
859 switch (d->strokeGradientActive) {
860 case LinearGradient:
862 break;
863 case RadialGradient:
865 break;
866 case ConicalGradient:
868 break;
869 default:
870 Q_UNREACHABLE_RETURN();
871 }
872 n->activateMaterial(m_item->window(), gradMat);
873 if (d->effectiveDirty & DirtyStrokeGradient) {
874 // Gradients are implemented via a texture-based material.
875 n->markDirty(QSGNode::DirtyMaterial);
876 // stop here if only the gradient changed; no need to touch the geometry
877 if (!(d->effectiveDirty & DirtyStrokeGeom))
878 return;
879 }
880 } else {
881 n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatSolidColor);
882 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyStrokeGeom)) {
883 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
884 for (int i = 0; i < g->vertexCount(); ++i)
885 vdst[i].set(vdst[i].x, vdst[i].y, d->strokeColor);
886 return;
887 }
888 }
889
890 g->allocate(d->strokeVertices.size(), 0);
891 g->setDrawingMode(QSGGeometry::DrawTriangleStrip);
892 memcpy(g->vertexData(), d->strokeVertices.constData(), g->vertexCount() * g->sizeOfVertex());
893}
894
895QSGMaterial *QQuickShapeGenericMaterialFactory::createVertexColor(QQuickWindow *window)
896{
897 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
898
899 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
900 return new QSGVertexColorMaterial;
901
902 qWarning("Vertex-color material: Unsupported graphics API %d", api);
903 return nullptr;
904}
905
906QSGMaterial *QQuickShapeGenericMaterialFactory::createLinearGradient(QQuickWindow *window,
908{
909 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
910
911 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
913
914 qWarning("Linear gradient material: Unsupported graphics API %d", api);
915 return nullptr;
916}
917
918QSGMaterial *QQuickShapeGenericMaterialFactory::createRadialGradient(QQuickWindow *window,
920{
921 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
922
923 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
925
926 qWarning("Radial gradient material: Unsupported graphics API %d", api);
927 return nullptr;
928}
929
930QSGMaterial *QQuickShapeGenericMaterialFactory::createConicalGradient(QQuickWindow *window,
932{
933 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
934
935 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
937
938 qWarning("Conical gradient material: Unsupported graphics API %d", api);
939 return nullptr;
940}
941
942QSGMaterial *QQuickShapeGenericMaterialFactory::createTextureFill(QQuickWindow *window,
944{
945 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
946
947 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
949
950 qWarning("Texture fill material: Unsupported graphics API %d", api);
951 return nullptr;
952}
953
955{
956 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.vert.qsb"), viewCount);
957 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.frag.qsb"), viewCount);
958}
959
961 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
962{
963 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
964 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
965 bool changed = false;
966 QByteArray *buf = state.uniformData();
967 Q_ASSERT(buf->size() >= 84 + 64);
968 const int shaderMatrixCount = newMaterial->viewCount();
969 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
970
971 if (state.isMatrixDirty()) {
972 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
973 const QMatrix4x4 m = state.combinedMatrix();
974 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
975 changed = true;
976 }
977 }
978
980
981 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
982 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
983 m_fillTransform = node->m_fillTransform;
984 changed = true;
985 }
986
987 if (!oldMaterial || m_gradA.x() != node->m_gradient.a.x() || m_gradA.y() != node->m_gradient.a.y()) {
988 m_gradA = QVector2D(node->m_gradient.a.x(), node->m_gradient.a.y());
989 Q_ASSERT(sizeof(m_gradA) == 8);
990 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_gradA, 8);
991 changed = true;
992 }
993
994 if (!oldMaterial || m_gradB.x() != node->m_gradient.b.x() || m_gradB.y() != node->m_gradient.b.y()) {
995 m_gradB = QVector2D(node->m_gradient.b.x(), node->m_gradient.b.y());
996 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_gradB, 8);
997 changed = true;
998 }
999
1000 if (state.isOpacityDirty()) {
1001 const float opacity = state.opacity();
1002 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, &opacity, 4);
1003 changed = true;
1004 }
1005
1006 return changed;
1007}
1008
1009void QQuickShapeLinearGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1010 QSGMaterial *newMaterial, QSGMaterial *)
1011{
1012 if (binding != 1)
1013 return;
1014
1015 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
1017 const QSGGradientCacheKey cacheKey(node->m_gradient.stops, QGradient::Spread(node->m_gradient.spread));
1018 QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
1019 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1020 *texture = t;
1021}
1022
1024{
1025 static QSGMaterialType type;
1026 return &type;
1027}
1028
1029int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const
1030{
1031 Q_ASSERT(other && type() == other->type());
1032 const QQuickShapeLinearGradientMaterial *m = static_cast<const QQuickShapeLinearGradientMaterial *>(other);
1033
1036 Q_ASSERT(a && b);
1037 if (a == b)
1038 return 0;
1039
1040 const QSGGradientCache::GradientDesc *ga = &a->m_gradient;
1041 const QSGGradientCache::GradientDesc *gb = &b->m_gradient;
1042
1043 if (int d = ga->spread - gb->spread)
1044 return d;
1045
1046 if (int d = ga->a.x() - gb->a.x())
1047 return d;
1048 if (int d = ga->a.y() - gb->a.y())
1049 return d;
1050 if (int d = ga->b.x() - gb->b.x())
1051 return d;
1052 if (int d = ga->b.y() - gb->b.y())
1053 return d;
1054
1055 if (int d = ga->stops.size() - gb->stops.size())
1056 return d;
1057
1058 for (int i = 0; i < ga->stops.size(); ++i) {
1059 if (int d = ga->stops[i].first - gb->stops[i].first)
1060 return d;
1061 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1062 return d;
1063 }
1064
1065 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1066 return d;
1067
1068 return 0;
1069}
1070
1071QSGMaterialShader *QQuickShapeLinearGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1072{
1073 Q_UNUSED(renderMode);
1074 return new QQuickShapeLinearGradientRhiShader(viewCount());
1075}
1076
1078{
1079 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.vert.qsb"), viewCount);
1080 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.frag.qsb"), viewCount);
1081}
1082
1084 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1085{
1086 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1087 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
1088 bool changed = false;
1089 QByteArray *buf = state.uniformData();
1090 Q_ASSERT(buf->size() >= 92 + 64);
1091 const int shaderMatrixCount = newMaterial->viewCount();
1092 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
1093
1094 if (state.isMatrixDirty()) {
1095 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1096 const QMatrix4x4 m = state.combinedMatrix();
1097 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
1098 changed = true;
1099 }
1100 }
1101
1103
1104 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1105 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
1106 m_fillTransform = node->m_fillTransform;
1107 changed = true;
1108 }
1109
1110 const QPointF centerPoint = node->m_gradient.a;
1111 const QPointF focalPoint = node->m_gradient.b;
1112 const QPointF focalToCenter = centerPoint - focalPoint;
1113 const float centerRadius = node->m_gradient.v0;
1114 const float focalRadius = node->m_gradient.v1;
1115
1116 if (!oldMaterial || m_focalPoint.x() != focalPoint.x() || m_focalPoint.y() != focalPoint.y()) {
1117 m_focalPoint = QVector2D(focalPoint.x(), focalPoint.y());
1118 Q_ASSERT(sizeof(m_focalPoint) == 8);
1119 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_focalPoint, 8);
1120 changed = true;
1121 }
1122
1123 if (!oldMaterial || m_focalToCenter.x() != focalToCenter.x() || m_focalToCenter.y() != focalToCenter.y()) {
1124 m_focalToCenter = QVector2D(focalToCenter.x(), focalToCenter.y());
1125 Q_ASSERT(sizeof(m_focalToCenter) == 8);
1126 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_focalToCenter, 8);
1127 changed = true;
1128 }
1129
1130 if (!oldMaterial || m_centerRadius != centerRadius) {
1131 m_centerRadius = centerRadius;
1132 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, &m_centerRadius, 4);
1133 changed = true;
1134 }
1135
1136 if (!oldMaterial || m_focalRadius != focalRadius) {
1137 m_focalRadius = focalRadius;
1138 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4, &m_focalRadius, 4);
1139 changed = true;
1140 }
1141
1142 if (state.isOpacityDirty()) {
1143 const float opacity = state.opacity();
1144 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4 + 4, &opacity, 4);
1145 changed = true;
1146 }
1147
1148 return changed;
1149}
1150
1151void QQuickShapeRadialGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1152 QSGMaterial *newMaterial, QSGMaterial *)
1153{
1154 if (binding != 1)
1155 return;
1156
1157 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
1159 const QSGGradientCacheKey cacheKey(node->m_gradient.stops, QGradient::Spread(node->m_gradient.spread));
1160 QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
1161 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1162 *texture = t;
1163}
1164
1166{
1167 static QSGMaterialType type;
1168 return &type;
1169}
1170
1171int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const
1172{
1173 Q_ASSERT(other && type() == other->type());
1174 const QQuickShapeRadialGradientMaterial *m = static_cast<const QQuickShapeRadialGradientMaterial *>(other);
1175
1178 Q_ASSERT(a && b);
1179 if (a == b)
1180 return 0;
1181
1182 const QSGGradientCache::GradientDesc *ga = &a->m_gradient;
1183 const QSGGradientCache::GradientDesc *gb = &b->m_gradient;
1184
1185 if (int d = ga->spread - gb->spread)
1186 return d;
1187
1188 if (int d = ga->a.x() - gb->a.x())
1189 return d;
1190 if (int d = ga->a.y() - gb->a.y())
1191 return d;
1192 if (int d = ga->b.x() - gb->b.x())
1193 return d;
1194 if (int d = ga->b.y() - gb->b.y())
1195 return d;
1196
1197 if (int d = ga->v0 - gb->v0)
1198 return d;
1199 if (int d = ga->v1 - gb->v1)
1200 return d;
1201
1202 if (int d = ga->stops.size() - gb->stops.size())
1203 return d;
1204
1205 for (int i = 0; i < ga->stops.size(); ++i) {
1206 if (int d = ga->stops[i].first - gb->stops[i].first)
1207 return d;
1208 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1209 return d;
1210 }
1211
1212 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1213 return d;
1214
1215 return 0;
1216}
1217
1218QSGMaterialShader *QQuickShapeRadialGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1219{
1220 Q_UNUSED(renderMode);
1221 return new QQuickShapeRadialGradientRhiShader(viewCount());
1222}
1223
1225{
1226 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.vert.qsb"), viewCount);
1227 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.frag.qsb"), viewCount);
1228}
1229
1231 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1232{
1233 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1234 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
1235 bool changed = false;
1236 QByteArray *buf = state.uniformData();
1237 Q_ASSERT(buf->size() >= 80 + 64);
1238 const int shaderMatrixCount = newMaterial->viewCount();
1239 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
1240
1241 if (state.isMatrixDirty()) {
1242 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1243 const QMatrix4x4 m = state.combinedMatrix();
1244 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
1245 changed = true;
1246 }
1247 }
1248
1250
1251 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1252 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
1253 m_fillTransform = node->m_fillTransform;
1254 changed = true;
1255 }
1256
1257 const QPointF centerPoint = node->m_gradient.a;
1258 const float angle = -qDegreesToRadians(node->m_gradient.v0);
1259
1260 if (!oldMaterial || m_centerPoint.x() != centerPoint.x() || m_centerPoint.y() != centerPoint.y()) {
1261 m_centerPoint = QVector2D(centerPoint.x(), centerPoint.y());
1262 Q_ASSERT(sizeof(m_centerPoint) == 8);
1263 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_centerPoint, 8);
1264 changed = true;
1265 }
1266
1267 if (!oldMaterial || m_angle != angle) {
1268 m_angle = angle;
1269 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_angle, 4);
1270 changed = true;
1271 }
1272
1273 if (state.isOpacityDirty()) {
1274 const float opacity = state.opacity();
1275 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 4, &opacity, 4);
1276 changed = true;
1277 }
1278
1279 return changed;
1280}
1281
1282void QQuickShapeConicalGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1283 QSGMaterial *newMaterial, QSGMaterial *)
1284{
1285 if (binding != 1)
1286 return;
1287
1288 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
1290 const QSGGradientCacheKey cacheKey(node->m_gradient.stops, QGradient::Spread(node->m_gradient.spread));
1291 QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
1292 t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1293 *texture = t;
1294}
1295
1297{
1298 static QSGMaterialType type;
1299 return &type;
1300}
1301
1302int QQuickShapeConicalGradientMaterial::compare(const QSGMaterial *other) const
1303{
1304 Q_ASSERT(other && type() == other->type());
1305 const QQuickShapeConicalGradientMaterial *m = static_cast<const QQuickShapeConicalGradientMaterial *>(other);
1306
1309 Q_ASSERT(a && b);
1310 if (a == b)
1311 return 0;
1312
1313 const QSGGradientCache::GradientDesc *ga = &a->m_gradient;
1314 const QSGGradientCache::GradientDesc *gb = &b->m_gradient;
1315
1316 if (int d = ga->a.x() - gb->a.x())
1317 return d;
1318 if (int d = ga->a.y() - gb->a.y())
1319 return d;
1320
1321 if (int d = ga->v0 - gb->v0)
1322 return d;
1323
1324 if (int d = ga->stops.size() - gb->stops.size())
1325 return d;
1326
1327 for (int i = 0; i < ga->stops.size(); ++i) {
1328 if (int d = ga->stops[i].first - gb->stops[i].first)
1329 return d;
1330 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1331 return d;
1332 }
1333
1334 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1335 return d;
1336
1337 return 0;
1338}
1339
1340QSGMaterialShader *QQuickShapeConicalGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1341{
1342 Q_UNUSED(renderMode);
1343 return new QQuickShapeConicalGradientRhiShader(viewCount());
1344}
1345
1347{
1348 setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.vert.qsb"), viewCount);
1349 setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.frag.qsb"), viewCount);
1350}
1351
1353 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1354{
1355 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1356 QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
1357 bool changed = false;
1358 QByteArray *buf = state.uniformData();
1359 const int shaderMatrixCount = newMaterial->viewCount();
1360 const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
1361 Q_ASSERT(buf->size() >= 64 * shaderMatrixCount + 64 + 8 + 4);
1362
1363 if (state.isMatrixDirty()) {
1364 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1365 const QMatrix4x4 m = state.combinedMatrix();
1366 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
1367 changed = true;
1368 }
1369 }
1370
1372
1373 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1374 memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
1375 m_fillTransform = node->m_fillTransform;
1376 changed = true;
1377 }
1378
1379 const QSizeF boundsSize = node->m_fillTextureProvider != nullptr && node->m_fillTextureProvider->texture() != nullptr
1380 ? node->m_fillTextureProvider->texture()->textureSize()
1381 : QSizeF(0, 0);
1382
1383
1384 const QVector2D boundsVector(boundsSize.width() / state.devicePixelRatio(),
1385 boundsSize.height() / state.devicePixelRatio());
1386 if (!oldMaterial || m_boundsSize != boundsVector) {
1387 m_boundsSize = boundsVector;
1388 Q_ASSERT(sizeof(m_boundsSize) == 8);
1389 memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_boundsSize, 8);
1390 changed = true;
1391 }
1392
1393 if (state.isOpacityDirty()) {
1394 const float opacity = state.opacity();
1395 memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &opacity, 4);
1396 changed = true;
1397 }
1398
1399 return changed;
1400}
1401
1402void QQuickShapeTextureFillRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1403 QSGMaterial *newMaterial, QSGMaterial *)
1404{
1405 if (binding != 1)
1406 return;
1407
1408 QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
1410 if (node->m_fillTextureProvider != nullptr) {
1411 QSGTexture *providedTexture = node->m_fillTextureProvider->texture();
1412 if (providedTexture != nullptr) {
1413 if (providedTexture->isAtlasTexture()) {
1414 // Create a non-atlas copy to make texture coordinate wrapping work. This
1415 // texture copy is owned by the QSGTexture so memory is managed with the original
1416 // texture provider.
1417 QSGTexture *newTexture = providedTexture->removedFromAtlas(state.resourceUpdateBatch());
1418 if (newTexture != nullptr)
1419 providedTexture = newTexture;
1420 }
1421
1422 providedTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1423 *texture = providedTexture;
1424 return;
1425 }
1426 }
1427
1428 if (m->dummyTexture() == nullptr) {
1429 QSGPlainTexture *dummyTexture = new QSGPlainTexture;
1430 dummyTexture->setFiltering(QSGTexture::Nearest);
1431 dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
1432 dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
1433 QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
1434 img.fill(0);
1435 dummyTexture->setImage(img);
1436 dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
1437
1438 m->setDummyTexture(dummyTexture);
1439 }
1440
1441 *texture = m->dummyTexture();
1442}
1443
1445{
1446 delete m_dummyTexture;
1447}
1448
1450{
1451 static QSGMaterialType type;
1452 return &type;
1453}
1454
1455int QQuickShapeTextureFillMaterial::compare(const QSGMaterial *other) const
1456{
1457 Q_ASSERT(other && type() == other->type());
1458 const QQuickShapeTextureFillMaterial *m = static_cast<const QQuickShapeTextureFillMaterial *>(other);
1459
1462 Q_ASSERT(a && b);
1463 if (a == b)
1464 return 0;
1465
1466 if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
1467 return d;
1468
1469 const qintptr diff = qintptr(a->m_fillTextureProvider) - qintptr(b->m_fillTextureProvider);
1470 return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
1471}
1472
1473QSGMaterialShader *QQuickShapeTextureFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1474{
1475 Q_UNUSED(renderMode);
1476 return new QQuickShapeTextureFillRhiShader(viewCount());
1477}
1478
1479QT_END_NAMESPACE
1480
1481#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 setVisible(int index, bool visible) 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)