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