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 "qquickpainteditem.h" |
5 | #include <private/qquickpainteditem_p.h> |
6 | |
7 | #include <QtQuick/private/qsgdefaultpainternode_p.h> |
8 | #include <QtQuick/private/qsgcontext_p.h> |
9 | #include <private/qsgadaptationlayer_p.h> |
10 | #include <qsgtextureprovider.h> |
11 | #include <rhi/qrhi.h> |
12 | |
13 | #include <qmath.h> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | class QQuickPaintedItemTextureProvider : public QSGTextureProvider |
18 | { |
19 | public: |
20 | QSGPainterNode *node; |
21 | QSGTexture *texture() const override { return node ? node->texture() : nullptr; } |
22 | void fireTextureChanged() { emit textureChanged(); } |
23 | }; |
24 | |
25 | /*! |
26 | \class QQuickPaintedItem |
27 | \brief The QQuickPaintedItem class provides a way to use the QPainter API in the |
28 | QML Scene Graph. |
29 | |
30 | \inmodule QtQuick |
31 | |
32 | The QQuickPaintedItem makes it possible to use the QPainter API with the QML |
33 | Scene Graph. It sets up a textured rectangle in the Scene Graph and uses a |
34 | QPainter to paint onto the texture. The render target in Qt 6 is always a |
35 | QImage. When the render target is a QImage, QPainter first renders into the |
36 | image then the content is uploaded to the texture. Call update() to trigger |
37 | a repaint. |
38 | |
39 | To enable QPainter to do anti-aliased rendering, use setAntialiasing(). |
40 | |
41 | To write your own painted item, you first create a subclass of |
42 | QQuickPaintedItem, and then start by implementing its only pure virtual |
43 | public function: paint(), which implements the actual painting. The |
44 | painting will be inside the rectangle spanning from 0,0 to |
45 | width(),height(). |
46 | |
47 | \note It important to understand the performance implications such items |
48 | can incur. See QQuickPaintedItem::RenderTarget and |
49 | QQuickPaintedItem::renderTarget. |
50 | |
51 | \sa {Scene Graph - Painted Item}, {Writing QML Extensions with C++} |
52 | */ |
53 | |
54 | /*! |
55 | \enum QQuickPaintedItem::RenderTarget |
56 | |
57 | This enum describes QQuickPaintedItem's render targets. The render target is the |
58 | surface QPainter paints onto before the item is rendered on screen. |
59 | |
60 | \value Image The default; QPainter paints into a QImage using the raster paint engine. |
61 | The image's content needs to be uploaded to graphics memory afterward, this operation |
62 | can potentially be slow if the item is large. This render target allows high quality |
63 | anti-aliasing and fast item resizing. |
64 | |
65 | \value FramebufferObject As of Qt 6.0, this value is ignored. |
66 | |
67 | \value InvertedYFramebufferObject As of Qt 6.0, this value is ignored. |
68 | |
69 | \sa setRenderTarget() |
70 | */ |
71 | |
72 | /*! |
73 | \enum QQuickPaintedItem::PerformanceHint |
74 | |
75 | This enum describes flags that you can enable to improve rendering |
76 | performance in QQuickPaintedItem. By default, none of these flags are set. |
77 | |
78 | \value FastFBOResizing As of Qt 6.0, this value is ignored. |
79 | */ |
80 | |
81 | /*! |
82 | \internal |
83 | */ |
84 | QQuickPaintedItemPrivate::QQuickPaintedItemPrivate() |
85 | : QQuickItemPrivate() |
86 | , contentsScale(1.0) |
87 | , fillColor(Qt::transparent) |
88 | , renderTarget(QQuickPaintedItem::Image) |
89 | , opaquePainting(false) |
90 | , antialiasing(false) |
91 | , mipmap(false) |
92 | , textureProvider(nullptr) |
93 | , node(nullptr) |
94 | { |
95 | } |
96 | |
97 | /*! |
98 | Constructs a QQuickPaintedItem with the given \a parent item. |
99 | */ |
100 | QQuickPaintedItem::QQuickPaintedItem(QQuickItem *parent) |
101 | : QQuickItem(*(new QQuickPaintedItemPrivate), parent) |
102 | { |
103 | setFlag(flag: ItemHasContents); |
104 | } |
105 | |
106 | /*! |
107 | \internal |
108 | */ |
109 | QQuickPaintedItem::QQuickPaintedItem(QQuickPaintedItemPrivate &dd, QQuickItem *parent) |
110 | : QQuickItem(dd, parent) |
111 | { |
112 | setFlag(flag: ItemHasContents); |
113 | } |
114 | |
115 | /*! |
116 | Destroys the QQuickPaintedItem. |
117 | */ |
118 | QQuickPaintedItem::~QQuickPaintedItem() |
119 | { |
120 | Q_D(QQuickPaintedItem); |
121 | if (d->textureProvider) |
122 | QQuickWindowQObjectCleanupJob::schedule(window: window(), object: d->textureProvider); |
123 | } |
124 | |
125 | /*! |
126 | Schedules a redraw of the area covered by \a rect in this item. You can call this function |
127 | whenever your item needs to be redrawn, such as if it changes appearance or size. |
128 | |
129 | This function does not cause an immediate paint; instead it schedules a paint request that |
130 | is processed by the QML Scene Graph when the next frame is rendered. The item will only be |
131 | redrawn if it is visible. |
132 | |
133 | \sa paint() |
134 | */ |
135 | void QQuickPaintedItem::update(const QRect &rect) |
136 | { |
137 | Q_D(QQuickPaintedItem); |
138 | |
139 | if (rect.isNull() && !d->dirtyRect.isNull()) |
140 | d->dirtyRect = contentsBoundingRect().toAlignedRect(); |
141 | else |
142 | d->dirtyRect |= (contentsBoundingRect() & rect).toAlignedRect(); |
143 | QQuickItem::update(); |
144 | } |
145 | |
146 | /*! |
147 | Returns true if this item is opaque; otherwise, false is returned. |
148 | |
149 | By default, painted items are not opaque. |
150 | |
151 | \sa setOpaquePainting() |
152 | */ |
153 | bool QQuickPaintedItem::opaquePainting() const |
154 | { |
155 | Q_D(const QQuickPaintedItem); |
156 | return d->opaquePainting; |
157 | } |
158 | |
159 | /*! |
160 | If \a opaque is true, the item is opaque; otherwise, it is considered as translucent. |
161 | |
162 | Opaque items are not blended with the rest of the scene, you should set this to true |
163 | if the content of the item is opaque to speed up rendering. |
164 | |
165 | By default, painted items are not opaque. |
166 | |
167 | \sa opaquePainting() |
168 | */ |
169 | void QQuickPaintedItem::setOpaquePainting(bool opaque) |
170 | { |
171 | Q_D(QQuickPaintedItem); |
172 | |
173 | if (d->opaquePainting == opaque) |
174 | return; |
175 | |
176 | d->opaquePainting = opaque; |
177 | QQuickItem::update(); |
178 | } |
179 | |
180 | /*! |
181 | Returns true if antialiased painting is enabled; otherwise, false is returned. |
182 | |
183 | By default, antialiasing is not enabled. |
184 | |
185 | \sa setAntialiasing() |
186 | */ |
187 | bool QQuickPaintedItem::antialiasing() const |
188 | { |
189 | Q_D(const QQuickPaintedItem); |
190 | return d->antialiasing; |
191 | } |
192 | |
193 | /*! |
194 | If \a enable is true, antialiased painting is enabled. |
195 | |
196 | By default, antialiasing is not enabled. |
197 | |
198 | \sa antialiasing() |
199 | */ |
200 | void QQuickPaintedItem::setAntialiasing(bool enable) |
201 | { |
202 | Q_D(QQuickPaintedItem); |
203 | |
204 | if (d->antialiasing == enable) |
205 | return; |
206 | |
207 | d->antialiasing = enable; |
208 | update(); |
209 | } |
210 | |
211 | /*! |
212 | Returns true if mipmaps are enabled; otherwise, false is returned. |
213 | |
214 | By default, mipmapping is not enabled. |
215 | |
216 | \sa setMipmap() |
217 | */ |
218 | bool QQuickPaintedItem::mipmap() const |
219 | { |
220 | Q_D(const QQuickPaintedItem); |
221 | return d->mipmap; |
222 | } |
223 | |
224 | /*! |
225 | If \a enable is true, mipmapping is enabled on the associated texture. |
226 | |
227 | Mipmapping increases rendering speed and reduces aliasing artifacts when the item is |
228 | scaled down. |
229 | |
230 | By default, mipmapping is not enabled. |
231 | |
232 | \sa mipmap() |
233 | */ |
234 | void QQuickPaintedItem::setMipmap(bool enable) |
235 | { |
236 | Q_D(QQuickPaintedItem); |
237 | |
238 | if (d->mipmap == enable) |
239 | return; |
240 | |
241 | d->mipmap = enable; |
242 | update(); |
243 | } |
244 | |
245 | /*! |
246 | Returns the performance hints. |
247 | |
248 | By default, no performance hint is enabled. |
249 | |
250 | \sa setPerformanceHint(), setPerformanceHints() |
251 | */ |
252 | QQuickPaintedItem::PerformanceHints QQuickPaintedItem::performanceHints() const |
253 | { |
254 | Q_D(const QQuickPaintedItem); |
255 | return d->performanceHints; |
256 | } |
257 | |
258 | /*! |
259 | Sets the given performance \a hint on the item if \a enabled is true; |
260 | otherwise clears the performance hint. |
261 | |
262 | By default, no performance hint is enabled/ |
263 | |
264 | \sa setPerformanceHints(), performanceHints() |
265 | */ |
266 | void QQuickPaintedItem::setPerformanceHint(QQuickPaintedItem::PerformanceHint hint, bool enabled) |
267 | { |
268 | Q_D(QQuickPaintedItem); |
269 | PerformanceHints oldHints = d->performanceHints; |
270 | if (enabled) |
271 | d->performanceHints |= hint; |
272 | else |
273 | d->performanceHints &= ~hint; |
274 | if (oldHints != d->performanceHints) |
275 | update(); |
276 | } |
277 | |
278 | /*! |
279 | Sets the performance hints to \a hints |
280 | |
281 | By default, no performance hint is enabled/ |
282 | |
283 | \sa setPerformanceHint(), performanceHints() |
284 | */ |
285 | void QQuickPaintedItem::setPerformanceHints(QQuickPaintedItem::PerformanceHints hints) |
286 | { |
287 | Q_D(QQuickPaintedItem); |
288 | if (d->performanceHints == hints) |
289 | return; |
290 | d->performanceHints = hints; |
291 | update(); |
292 | } |
293 | |
294 | QSize QQuickPaintedItem::textureSize() const |
295 | { |
296 | Q_D(const QQuickPaintedItem); |
297 | return d->textureSize; |
298 | } |
299 | |
300 | /*! |
301 | \property QQuickPaintedItem::textureSize |
302 | |
303 | \brief Defines the size of the texture. |
304 | |
305 | Changing the texture's size does not affect the coordinate system used in |
306 | paint(). A scale factor is instead applied so painting should still happen |
307 | inside 0,0 to width(),height(). |
308 | |
309 | By default, the texture size will have the same size as this item. |
310 | |
311 | \note If the item is on a window with a device pixel ratio different from |
312 | 1, this scale factor will be implicitly applied to the texture size. |
313 | |
314 | */ |
315 | void QQuickPaintedItem::setTextureSize(const QSize &size) |
316 | { |
317 | Q_D(QQuickPaintedItem); |
318 | if (d->textureSize == size) |
319 | return; |
320 | d->textureSize = size; |
321 | emit textureSizeChanged(); |
322 | } |
323 | |
324 | /*! |
325 | \deprecated |
326 | |
327 | This function is provided for compatibility, use size in combination |
328 | with textureSize to decide the size of what you are drawing. |
329 | |
330 | \sa width(), height(), textureSize() |
331 | */ |
332 | QRectF QQuickPaintedItem::contentsBoundingRect() const |
333 | { |
334 | Q_D(const QQuickPaintedItem); |
335 | |
336 | qreal w = d->width; |
337 | QSizeF sz = d->contentsSize * d->contentsScale; |
338 | if (w < sz.width()) |
339 | w = sz.width(); |
340 | qreal h = d->height; |
341 | if (h < sz.height()) |
342 | h = sz.height(); |
343 | |
344 | return QRectF(0, 0, w, h); |
345 | } |
346 | |
347 | /*! |
348 | \property QQuickPaintedItem::contentsSize |
349 | \brief Obsolete method for setting the contents size. |
350 | \deprecated |
351 | |
352 | This function is provided for compatibility, use size in combination |
353 | with textureSize to decide the size of what you are drawing. |
354 | |
355 | \sa width(), height(), textureSize() |
356 | */ |
357 | QSize QQuickPaintedItem::contentsSize() const |
358 | { |
359 | Q_D(const QQuickPaintedItem); |
360 | return d->contentsSize; |
361 | } |
362 | |
363 | void QQuickPaintedItem::setContentsSize(const QSize &size) |
364 | { |
365 | Q_D(QQuickPaintedItem); |
366 | |
367 | if (d->contentsSize == size) |
368 | return; |
369 | |
370 | d->contentsSize = size; |
371 | update(); |
372 | |
373 | emit contentsSizeChanged(); |
374 | } |
375 | |
376 | /*! |
377 | \deprecated |
378 | This convenience function is equivalent to calling setContentsSize(QSize()). |
379 | */ |
380 | void QQuickPaintedItem::resetContentsSize() |
381 | { |
382 | setContentsSize(QSize()); |
383 | } |
384 | |
385 | /*! |
386 | \property QQuickPaintedItem::contentsScale |
387 | \brief Obsolete method for scaling the contents. |
388 | \deprecated |
389 | |
390 | This function is provided for compatibility, use size() in combination |
391 | with textureSize() to decide the size of what you are drawing. |
392 | |
393 | \sa width(), height(), textureSize() |
394 | */ |
395 | qreal QQuickPaintedItem::contentsScale() const |
396 | { |
397 | Q_D(const QQuickPaintedItem); |
398 | return d->contentsScale; |
399 | } |
400 | |
401 | void QQuickPaintedItem::setContentsScale(qreal scale) |
402 | { |
403 | Q_D(QQuickPaintedItem); |
404 | |
405 | if (d->contentsScale == scale) |
406 | return; |
407 | |
408 | d->contentsScale = scale; |
409 | update(); |
410 | |
411 | emit contentsScaleChanged(); |
412 | } |
413 | |
414 | /*! |
415 | \property QQuickPaintedItem::fillColor |
416 | \brief The item's background fill color. |
417 | |
418 | By default, the fill color is set to Qt::transparent. |
419 | |
420 | Set the fill color to an invalid color (e.g. QColor()) to disable background |
421 | filling. This may improve performance, and is safe to do if the paint() function |
422 | draws to all pixels on each frame. |
423 | */ |
424 | QColor QQuickPaintedItem::fillColor() const |
425 | { |
426 | Q_D(const QQuickPaintedItem); |
427 | return d->fillColor; |
428 | } |
429 | |
430 | void QQuickPaintedItem::setFillColor(const QColor &c) |
431 | { |
432 | Q_D(QQuickPaintedItem); |
433 | |
434 | if (d->fillColor == c) |
435 | return; |
436 | |
437 | d->fillColor = c; |
438 | update(); |
439 | |
440 | emit fillColorChanged(); |
441 | } |
442 | |
443 | /*! |
444 | \property QQuickPaintedItem::renderTarget |
445 | \brief The item's render target. |
446 | |
447 | This property defines which render target the QPainter renders into, it can be either |
448 | QQuickPaintedItem::Image, QQuickPaintedItem::FramebufferObject or QQuickPaintedItem::InvertedYFramebufferObject. |
449 | |
450 | Each has certain benefits, typically performance versus quality. Using a framebuffer |
451 | object avoids a costly upload of the image contents to the texture in graphics memory, |
452 | while using an image enables high quality anti-aliasing. |
453 | |
454 | \warning Resizing a framebuffer object is a costly operation, avoid using |
455 | the QQuickPaintedItem::FramebufferObject render target if the item gets resized often. |
456 | |
457 | By default, the render target is QQuickPaintedItem::Image. |
458 | */ |
459 | QQuickPaintedItem::RenderTarget QQuickPaintedItem::renderTarget() const |
460 | { |
461 | Q_D(const QQuickPaintedItem); |
462 | return d->renderTarget; |
463 | } |
464 | |
465 | void QQuickPaintedItem::setRenderTarget(RenderTarget target) |
466 | { |
467 | Q_D(QQuickPaintedItem); |
468 | |
469 | if (d->renderTarget == target) |
470 | return; |
471 | |
472 | d->renderTarget = target; |
473 | update(); |
474 | |
475 | emit renderTargetChanged(); |
476 | } |
477 | |
478 | /*! |
479 | \fn virtual void QQuickPaintedItem::paint(QPainter *painter) = 0 |
480 | |
481 | This function, which is usually called by the QML Scene Graph, paints the |
482 | contents of an item in local coordinates. |
483 | |
484 | The underlying texture will have a size defined by textureSize when set, |
485 | or the item's size, multiplied by the window's device pixel ratio. |
486 | |
487 | The function is called after the item has been filled with the fillColor. |
488 | |
489 | Reimplement this function in a QQuickPaintedItem subclass to provide the |
490 | item's painting implementation, using \a painter. |
491 | |
492 | \note The QML Scene Graph uses two separate threads, the main thread does |
493 | things such as processing events or updating animations while a second |
494 | thread does the actual issuing of graphics resource updates and the |
495 | recording of draw calls. As a consequence, paint() is not called from the |
496 | main GUI thread but from the GL enabled renderer thread. At the moment |
497 | paint() is called, the GUI thread is blocked and this is therefore |
498 | thread-safe. |
499 | |
500 | \warning Extreme caution must be used when creating QObjects, emitting signals, starting |
501 | timers and similar inside this function as these will have affinity to the rendering thread. |
502 | |
503 | \sa width(), height(), textureSize |
504 | */ |
505 | |
506 | /*! |
507 | \reimp |
508 | */ |
509 | QSGNode *QQuickPaintedItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) |
510 | { |
511 | Q_UNUSED(data); |
512 | Q_D(QQuickPaintedItem); |
513 | |
514 | if (width() <= 0 || height() <= 0) { |
515 | delete oldNode; |
516 | if (d->textureProvider) { |
517 | d->textureProvider->node = nullptr; |
518 | d->textureProvider->fireTextureChanged(); |
519 | } |
520 | return nullptr; |
521 | } |
522 | |
523 | QSGPainterNode *node = static_cast<QSGPainterNode *>(oldNode); |
524 | if (!node) { |
525 | node = d->sceneGraphContext()->createPainterNode(item: this); |
526 | d->node = node; |
527 | } |
528 | |
529 | bool hasTextureSize = d->textureSize.width() > 0 && d->textureSize.height() > 0; |
530 | |
531 | // Use the compat mode if any of the compat things are set and |
532 | // textureSize is 0x0. |
533 | if (!hasTextureSize |
534 | && (d->contentsScale != 1 |
535 | || (d->contentsSize.width() > 0 && d->contentsSize.height() > 0))) { |
536 | QRectF br = contentsBoundingRect(); |
537 | node->setContentsScale(d->contentsScale); |
538 | QSize size = QSize(qRound(d: br.width()), qRound(d: br.height())); |
539 | node->setSize(size); |
540 | node->setTextureSize(size); |
541 | } else { |
542 | // The default, use textureSize or item's size * device pixel ratio |
543 | node->setContentsScale(1); |
544 | QSize itemSize(qRound(d: width()), qRound(d: height())); |
545 | node->setSize(itemSize); |
546 | QSize textureSize = (hasTextureSize ? d->textureSize : itemSize) |
547 | * window()->effectiveDevicePixelRatio(); |
548 | node->setTextureSize(textureSize); |
549 | } |
550 | |
551 | node->setPreferredRenderTarget(d->renderTarget); |
552 | node->setFastFBOResizing(d->performanceHints & FastFBOResizing); |
553 | node->setSmoothPainting(d->antialiasing); |
554 | node->setLinearFiltering(d->smooth); |
555 | node->setMipmapping(d->mipmap); |
556 | node->setOpaquePainting(d->opaquePainting); |
557 | node->setFillColor(d->fillColor); |
558 | node->setDirty(d->dirtyRect); |
559 | node->update(); |
560 | |
561 | d->dirtyRect = QRect(); |
562 | |
563 | if (d->textureProvider) { |
564 | d->textureProvider->node = node; |
565 | d->textureProvider->fireTextureChanged(); |
566 | } |
567 | |
568 | return node; |
569 | } |
570 | |
571 | /*! |
572 | \reimp |
573 | */ |
574 | void QQuickPaintedItem::releaseResources() |
575 | { |
576 | Q_D(QQuickPaintedItem); |
577 | if (d->textureProvider) { |
578 | QQuickWindowQObjectCleanupJob::schedule(window: window(), object: d->textureProvider); |
579 | d->textureProvider = nullptr; |
580 | } |
581 | d->node = nullptr; // Managed by the scene graph, just clear the pointer. |
582 | } |
583 | |
584 | void QQuickPaintedItem::invalidateSceneGraph() |
585 | { |
586 | Q_D(QQuickPaintedItem); |
587 | delete d->textureProvider; |
588 | d->textureProvider = nullptr; |
589 | d->node = nullptr; // Managed by the scene graph, just clear the pointer |
590 | } |
591 | |
592 | /*! |
593 | \reimp |
594 | */ |
595 | bool QQuickPaintedItem::isTextureProvider() const |
596 | { |
597 | return true; |
598 | } |
599 | |
600 | /*! |
601 | \reimp |
602 | */ |
603 | QSGTextureProvider *QQuickPaintedItem::textureProvider() const |
604 | { |
605 | // When Item::layer::enabled == true, QQuickItem will be a texture |
606 | // provider. In this case we should prefer to return the layer rather |
607 | // than the image itself. The layer will include any children and any |
608 | // the image's wrap and fill mode. |
609 | if (QQuickItem::isTextureProvider()) |
610 | return QQuickItem::textureProvider(); |
611 | |
612 | Q_D(const QQuickPaintedItem); |
613 | QQuickWindow *w = window(); |
614 | if (!w || !w->isSceneGraphInitialized() || QThread::currentThread() != d->sceneGraphContext()->thread()) { |
615 | qWarning(msg: "QQuickPaintedItem::textureProvider: can only be queried on the rendering thread of an exposed window" ); |
616 | return nullptr; |
617 | } |
618 | if (!d->textureProvider) |
619 | d->textureProvider = new QQuickPaintedItemTextureProvider(); |
620 | d->textureProvider->node = d->node; |
621 | return d->textureProvider; |
622 | } |
623 | |
624 | |
625 | /*! |
626 | \reimp |
627 | */ |
628 | void QQuickPaintedItem::itemChange(ItemChange change, const ItemChangeData &value) |
629 | { |
630 | if (change == ItemDevicePixelRatioHasChanged) |
631 | update(); |
632 | QQuickItem::itemChange(change, value); |
633 | } |
634 | |
635 | QT_END_NAMESPACE |
636 | |
637 | #include "moc_qquickpainteditem.cpp" |
638 | |