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