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 | , mipmap(false) |
91 | , textureProvider(nullptr) |
92 | , node(nullptr) |
93 | { |
94 | } |
95 | |
96 | /*! |
97 | Constructs a QQuickPaintedItem with the given \a parent item. |
98 | */ |
99 | QQuickPaintedItem::QQuickPaintedItem(QQuickItem *parent) |
100 | : QQuickItem(*(new QQuickPaintedItemPrivate), parent) |
101 | { |
102 | setFlag(flag: ItemHasContents); |
103 | } |
104 | |
105 | /*! |
106 | \internal |
107 | */ |
108 | QQuickPaintedItem::QQuickPaintedItem(QQuickPaintedItemPrivate &dd, QQuickItem *parent) |
109 | : QQuickItem(dd, parent) |
110 | { |
111 | setFlag(flag: ItemHasContents); |
112 | } |
113 | |
114 | /*! |
115 | Destroys the QQuickPaintedItem. |
116 | */ |
117 | QQuickPaintedItem::~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 | */ |
134 | void 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 | */ |
152 | bool 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 | */ |
168 | void 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 | */ |
187 | bool 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 | */ |
199 | void 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 | */ |
211 | bool 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 | */ |
227 | void 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 | */ |
245 | QQuickPaintedItem::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 | */ |
259 | void 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 | */ |
278 | void 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 | |
287 | QSize 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 | */ |
308 | void 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 | */ |
325 | QRectF 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 | */ |
350 | QSize QQuickPaintedItem::contentsSize() const |
351 | { |
352 | Q_D(const QQuickPaintedItem); |
353 | return d->contentsSize; |
354 | } |
355 | |
356 | void 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 | */ |
373 | void 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 | */ |
388 | qreal QQuickPaintedItem::contentsScale() const |
389 | { |
390 | Q_D(const QQuickPaintedItem); |
391 | return d->contentsScale; |
392 | } |
393 | |
394 | void 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 | */ |
417 | QColor QQuickPaintedItem::fillColor() const |
418 | { |
419 | Q_D(const QQuickPaintedItem); |
420 | return d->fillColor; |
421 | } |
422 | |
423 | void 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 | */ |
452 | QQuickPaintedItem::RenderTarget QQuickPaintedItem::renderTarget() const |
453 | { |
454 | Q_D(const QQuickPaintedItem); |
455 | return d->renderTarget; |
456 | } |
457 | |
458 | void 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 | */ |
502 | QSGNode *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 | */ |
567 | void 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 | |
577 | void 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 | */ |
588 | bool QQuickPaintedItem::isTextureProvider() const |
589 | { |
590 | return true; |
591 | } |
592 | |
593 | /*! |
594 | \reimp |
595 | */ |
596 | QSGTextureProvider *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 | */ |
621 | void QQuickPaintedItem::itemChange(ItemChange change, const ItemChangeData &value) |
622 | { |
623 | if (change == ItemDevicePixelRatioHasChanged) |
624 | update(); |
625 | QQuickItem::itemChange(change, value); |
626 | } |
627 | |
628 | QT_END_NAMESPACE |
629 | |
630 | #include "moc_qquickpainteditem.cpp" |
631 | |