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

source code of qtdeclarative/src/quick/items/qquickpainteditem.cpp