1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickcontext2dtexture_p.h"
41#include "qquickcontext2dtile_p.h"
42#include "qquickcanvasitem_p.h"
43#include <private/qquickitem_p.h>
44#include <QtQuick/private/qsgplaintexture_p.h>
45#include "qquickcontext2dcommandbuffer_p.h"
46#include <QOpenGLPaintDevice>
47#if QT_CONFIG(opengl)
48#include <QOpenGLFramebufferObject>
49#include <QOpenGLFramebufferObjectFormat>
50#include <QOpenGLFunctions>
51#include <QtGui/private/qopenglextensions_p.h>
52#endif
53#include <QtCore/QThread>
54#include <QtGui/QGuiApplication>
55
56QT_BEGIN_NAMESPACE
57
58Q_LOGGING_CATEGORY(lcCanvas, "qt.quick.canvas")
59
60#if QT_CONFIG(opengl)
61#define QT_MINIMUM_FBO_SIZE 64
62
63static inline int qt_next_power_of_two(int v)
64{
65 v--;
66 v |= v >> 1;
67 v |= v >> 2;
68 v |= v >> 4;
69 v |= v >> 8;
70 v |= v >> 16;
71 ++v;
72 return v;
73}
74
75struct GLAcquireContext {
76 GLAcquireContext(QOpenGLContext *c, QSurface *s):ctx(c) {
77 if (ctx) {
78 Q_ASSERT(s);
79 if (!ctx->isValid())
80 ctx->create();
81
82 if (!ctx->isValid())
83 qWarning() << "Unable to create GL context";
84 else if (!ctx->makeCurrent(surface: s))
85 qWarning() << "Can't make current GL context";
86 }
87 }
88 ~GLAcquireContext() {
89 if (ctx)
90 ctx->doneCurrent();
91 }
92 QOpenGLContext *ctx;
93};
94#endif
95QQuickContext2DTexture::QQuickContext2DTexture()
96 : m_context(nullptr)
97#if QT_CONFIG(opengl)
98 , m_gl(nullptr)
99#endif
100 , m_surface(nullptr)
101 , m_item(nullptr)
102 , m_canvasDevicePixelRatio(1)
103 , m_canvasWindowChanged(false)
104 , m_dirtyTexture(false)
105 , m_smooth(true)
106 , m_antialiasing(false)
107 , m_tiledCanvas(false)
108 , m_painting(false)
109{
110}
111
112QQuickContext2DTexture::~QQuickContext2DTexture()
113{
114 clearTiles();
115}
116
117void QQuickContext2DTexture::markDirtyTexture()
118{
119 if (m_onCustomThread)
120 m_mutex.lock();
121 m_dirtyTexture = true;
122 emit textureChanged();
123 if (m_onCustomThread)
124 m_mutex.unlock();
125}
126
127bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
128{
129 if (m_canvasSize != size) {
130 m_canvasSize = size;
131 return true;
132 }
133 return false;
134}
135
136bool QQuickContext2DTexture::setTileSize(const QSize &size)
137{
138 if (m_tileSize != size) {
139 m_tileSize = size;
140 return true;
141 }
142 return false;
143}
144
145void QQuickContext2DTexture::setSmooth(bool smooth)
146{
147 m_smooth = smooth;
148}
149
150void QQuickContext2DTexture::setAntialiasing(bool antialiasing)
151{
152 m_antialiasing = antialiasing;
153}
154
155void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
156{
157 m_item = item;
158 if (m_item) {
159 m_context = (QQuickContext2D*) item->rawContext(); // FIXME
160 m_state = m_context->state;
161 } else {
162 m_context = nullptr;
163 }
164}
165
166bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
167{
168 bool ok = false;
169 static qreal overriddenDevicePixelRatio =
170 !qEnvironmentVariableIsEmpty(varName: "QT_CANVAS_OVERRIDE_DEVICEPIXELRATIO") ?
171 qgetenv(varName: "QT_CANVAS_OVERRIDE_DEVICEPIXELRATIO").toFloat(ok: &ok) : 0.0;
172 qreal canvasDevicePixelRatio = overriddenDevicePixelRatio;
173 if (overriddenDevicePixelRatio == 0.0) {
174 canvasDevicePixelRatio = (m_item && m_item->window()) ?
175 m_item->window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio();
176 }
177 if (!qFuzzyCompare(p1: m_canvasDevicePixelRatio, p2: canvasDevicePixelRatio)) {
178 qCDebug(lcCanvas, "%s device pixel ratio %.1lf -> %.1lf",
179 (m_item->objectName().isEmpty() ? "Canvas" : qPrintable(m_item->objectName())),
180 m_canvasDevicePixelRatio, canvasDevicePixelRatio);
181 m_canvasDevicePixelRatio = canvasDevicePixelRatio;
182 m_canvasWindowChanged = true;
183 }
184
185 if (m_canvasWindow != r) {
186 m_canvasWindow = r;
187 m_canvasWindowChanged = true;
188 }
189
190 return m_canvasWindowChanged;
191}
192
193bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
194{
195 bool doDirty = false;
196 if (m_tiledCanvas) {
197 for (QQuickContext2DTile* t : qAsConst(t&: m_tiles)) {
198 bool dirty = t->rect().intersected(other: r).isValid();
199 t->markDirty(dirty);
200 if (dirty)
201 doDirty = true;
202 }
203 } else {
204 doDirty = m_canvasWindow.intersected(other: r).isValid();
205 }
206 return doDirty;
207}
208
209void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing)
210{
211 QSize ts = tileSize;
212 if (ts.width() > canvasSize.width())
213 ts.setWidth(canvasSize.width());
214
215 if (ts.height() > canvasSize.height())
216 ts.setHeight(canvasSize.height());
217
218 setCanvasSize(canvasSize);
219 setTileSize(ts);
220 setCanvasWindow(canvasWindow);
221
222 if (canvasSize == canvasWindow.size()) {
223 m_tiledCanvas = false;
224 } else {
225 m_tiledCanvas = true;
226 }
227
228 if (dirtyRect.isValid())
229 setDirtyRect(dirtyRect);
230
231 setSmooth(smooth);
232 setAntialiasing(antialiasing);
233}
234
235void QQuickContext2DTexture::paintWithoutTiles(QQuickContext2DCommandBuffer *ccb)
236{
237 if (!ccb || ccb->isEmpty())
238 return;
239
240 QPaintDevice* device = beginPainting();
241 if (!device) {
242 endPainting();
243 return;
244 }
245
246 QPainter p;
247 p.begin(device);
248 p.setRenderHints(hints: QPainter::Antialiasing | QPainter::TextAntialiasing, on: m_antialiasing);
249 p.setRenderHint(hint: QPainter::SmoothPixmapTransform, on: m_smooth);
250
251 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
252
253 ccb->replay(painter: &p, state&: m_state, scaleFactor: scaleFactor());
254 endPainting();
255 markDirtyTexture();
256}
257
258bool QQuickContext2DTexture::canvasDestroyed()
259{
260 return m_item == nullptr;
261}
262
263void QQuickContext2DTexture::paint(QQuickContext2DCommandBuffer *ccb)
264{
265 QQuickContext2D::mutex.lock();
266 if (canvasDestroyed()) {
267 delete ccb;
268 QQuickContext2D::mutex.unlock();
269 return;
270 }
271 QQuickContext2D::mutex.unlock();
272#if QT_CONFIG(opengl)
273 GLAcquireContext currentContext(m_gl, m_surface);
274#endif
275 if (!m_tiledCanvas) {
276 paintWithoutTiles(ccb);
277 delete ccb;
278 return;
279 }
280
281 QRect tiledRegion = createTiles(window: m_canvasWindow.intersected(other: QRect(QPoint(0, 0), m_canvasSize)));
282 if (!tiledRegion.isEmpty()) {
283 QRect dirtyRect;
284 for (QQuickContext2DTile* tile : qAsConst(t&: m_tiles)) {
285 if (tile->dirty()) {
286 if (dirtyRect.isEmpty())
287 dirtyRect = tile->rect();
288 else
289 dirtyRect |= tile->rect();
290 }
291 }
292
293 if (beginPainting()) {
294 QQuickContext2D::State oldState = m_state;
295 for (QQuickContext2DTile* tile : qAsConst(t&: m_tiles)) {
296 if (tile->dirty()) {
297 ccb->replay(painter: tile->createPainter(smooth: m_smooth, antialiasing: m_antialiasing), state&: oldState, scaleFactor: scaleFactor());
298 tile->drawFinished();
299 tile->markDirty(dirty: false);
300 }
301 compositeTile(tile);
302 }
303 endPainting();
304 m_state = oldState;
305 markDirtyTexture();
306 }
307 }
308 delete ccb;
309}
310
311QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
312{
313 if (window.isEmpty())
314 return QRect();
315
316 const int tw = tileSize.width();
317 const int th = tileSize.height();
318 const int h1 = window.left() / tw;
319 const int v1 = window.top() / th;
320
321 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
322 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
323
324 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
325}
326
327QRect QQuickContext2DTexture::createTiles(const QRect& window)
328{
329 QList<QQuickContext2DTile*> oldTiles = m_tiles;
330 m_tiles.clear();
331
332 if (window.isEmpty()) {
333 return QRect();
334 }
335
336 QRect r = tiledRect(window, tileSize: adjustedTileSize(ts: m_tileSize));
337
338 const int tw = m_tileSize.width();
339 const int th = m_tileSize.height();
340 const int h1 = window.left() / tw;
341 const int v1 = window.top() / th;
342
343
344 const int htiles = r.width() / tw;
345 const int vtiles = r.height() / th;
346
347 for (int yy = 0; yy < vtiles; ++yy) {
348 for (int xx = 0; xx < htiles; ++xx) {
349 int ht = xx + h1;
350 int vt = yy + v1;
351
352 QQuickContext2DTile* tile = nullptr;
353
354 QPoint pos(ht * tw, vt * th);
355 QRect rect(pos, m_tileSize);
356
357 for (int i = 0; i < oldTiles.size(); i++) {
358 if (oldTiles[i]->rect() == rect) {
359 tile = oldTiles.takeAt(i);
360 break;
361 }
362 }
363
364 if (!tile)
365 tile = createTile();
366
367 tile->setRect(rect);
368 m_tiles.append(t: tile);
369 }
370 }
371
372 qDeleteAll(c: oldTiles);
373
374 return r;
375}
376
377void QQuickContext2DTexture::clearTiles()
378{
379 qDeleteAll(c: m_tiles);
380 m_tiles.clear();
381}
382
383QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts)
384{
385 return ts;
386}
387
388bool QQuickContext2DTexture::event(QEvent *e)
389{
390 if ((int) e->type() == QEvent::User + 1) {
391 PaintEvent *pe = static_cast<PaintEvent *>(e);
392 paint(ccb: pe->buffer);
393 return true;
394 } else if ((int) e->type() == QEvent::User + 2) {
395 CanvasChangeEvent *ce = static_cast<CanvasChangeEvent *>(e);
396 canvasChanged(canvasSize: ce->canvasSize, tileSize: ce->tileSize, canvasWindow: ce->canvasWindow, dirtyRect: ce->dirtyRect, smooth: ce->smooth, antialiasing: ce->antialiasing);
397 return true;
398 }
399 return QObject::event(event: e);
400}
401#if QT_CONFIG(opengl)
402static inline QSize npotAdjustedSize(const QSize &size)
403{
404 static bool checked = false;
405 static bool npotSupported = false;
406
407 if (!checked) {
408 npotSupported = QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(feature: QOpenGLFunctions::NPOTTextures);
409 checked = true;
410 }
411
412 if (npotSupported) {
413 return QSize(qMax(QT_MINIMUM_FBO_SIZE, b: size.width()),
414 qMax(QT_MINIMUM_FBO_SIZE, b: size.height()));
415 }
416
417 return QSize(qMax(QT_MINIMUM_FBO_SIZE, b: qt_next_power_of_two(v: size.width())),
418 qMax(QT_MINIMUM_FBO_SIZE, b: qt_next_power_of_two(v: size.height())));
419}
420
421QQuickContext2DFBOTexture::QQuickContext2DFBOTexture()
422 : QQuickContext2DTexture()
423 , m_fbo(nullptr)
424 , m_multisampledFbo(nullptr)
425 , m_paint_device(nullptr)
426{
427 m_displayTextures[0] = 0;
428 m_displayTextures[1] = 0;
429 m_displayTexture = -1;
430}
431
432QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture()
433{
434 if (m_multisampledFbo)
435 m_multisampledFbo->release();
436 else if (m_fbo)
437 m_fbo->release();
438
439 delete m_fbo;
440 delete m_multisampledFbo;
441 delete m_paint_device;
442
443 if (QOpenGLContext::currentContext())
444 QOpenGLContext::currentContext()->functions()->glDeleteTextures(n: 2, textures: m_displayTextures);
445}
446
447QVector2D QQuickContext2DFBOTexture::scaleFactor() const
448{
449 if (!m_fbo)
450 return QVector2D(1, 1);
451 return QVector2D(m_fbo->width() / m_fboSize.width(),
452 m_fbo->height() / m_fboSize.height());
453}
454
455QSGTexture *QQuickContext2DFBOTexture::textureForNextFrame(QSGTexture *lastTexture, QQuickWindow *)
456{
457 QSGPlainTexture *texture = static_cast<QSGPlainTexture *>(lastTexture);
458
459 if (m_onCustomThread)
460 m_mutex.lock();
461
462 if (m_fbo) {
463 if (!texture) {
464 texture = new QSGPlainTexture();
465 texture->setHasAlphaChannel(true);
466 texture->setOwnsTexture(false);
467 m_dirtyTexture = true;
468 }
469
470 if (m_dirtyTexture) {
471 if (!m_gl) {
472 // on a rendering thread, use the fbo directly...
473 texture->setTextureId(m_fbo->texture());
474 } else {
475 // on GUI or custom thread, use display textures...
476 m_displayTexture = m_displayTexture == 0 ? 1 : 0;
477 texture->setTextureId(m_displayTextures[m_displayTexture]);
478 }
479 texture->setTextureSize(m_fbo->size());
480 m_dirtyTexture = false;
481 }
482
483 }
484
485 if (m_onCustomThread) {
486 m_condition.wakeOne();
487 m_mutex.unlock();
488 }
489
490 return texture;
491}
492
493QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts)
494{
495 return npotAdjustedSize(size: ts);
496}
497
498QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const
499{
500 return QRectF(0
501 , 0
502 , qreal(m_canvasWindow.width()) / m_fboSize.width()
503 , qreal(m_canvasWindow.height()) / m_fboSize.height());
504}
505
506QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const
507{
508 return new QQuickContext2DFBOTile();
509}
510
511bool QQuickContext2DFBOTexture::doMultisampling() const
512{
513 static bool extensionsChecked = false;
514 static bool multisamplingSupported = false;
515
516 if (!extensionsChecked) {
517 QOpenGLExtensions *e = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
518 multisamplingSupported = e->hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample)
519 && e->hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferBlit);
520 extensionsChecked = true;
521 }
522
523 return multisamplingSupported && m_antialiasing;
524}
525
526void QQuickContext2DFBOTexture::grabImage(const QRectF& rf)
527{
528 Q_ASSERT(rf.isValid());
529 QQuickContext2D::mutex.lock();
530 if (m_context) {
531 if (!m_fbo) {
532 m_context->setGrabbedImage(QImage());
533 } else {
534 QImage grabbed;
535 GLAcquireContext ctx(m_gl, m_surface);
536 grabbed = m_fbo->toImage().scaled(s: m_fboSize, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation).mirrored().copy(rect: rf.toRect());
537 m_context->setGrabbedImage(grabbed);
538 }
539 }
540 QQuickContext2D::mutex.unlock();
541}
542
543void QQuickContext2DFBOTexture::compositeTile(QQuickContext2DTile* tile)
544{
545 QQuickContext2DFBOTile* t = static_cast<QQuickContext2DFBOTile*>(tile);
546 QRect target = t->rect().intersected(other: m_canvasWindow);
547 if (target.isValid()) {
548 QRect source = target;
549
550 source.moveTo(p: source.topLeft() - t->rect().topLeft());
551 target.moveTo(p: target.topLeft() - m_canvasWindow.topLeft());
552
553 QOpenGLFramebufferObject::blitFramebuffer(target: m_fbo, targetRect: target, source: t->fbo(), sourceRect: source);
554 }
555}
556
557QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const
558{
559 return QQuickCanvasItem::FramebufferObject;
560}
561
562QPaintDevice* QQuickContext2DFBOTexture::beginPainting()
563{
564 QQuickContext2DTexture::beginPainting();
565
566 if (m_canvasWindow.size().isEmpty()) {
567 delete m_fbo;
568 delete m_multisampledFbo;
569 delete m_paint_device;
570 m_fbo = nullptr;
571 m_multisampledFbo = nullptr;
572 m_paint_device = nullptr;
573 return nullptr;
574 } else if (!m_fbo || m_canvasWindowChanged) {
575 delete m_fbo;
576 delete m_multisampledFbo;
577 delete m_paint_device;
578 m_paint_device = nullptr;
579
580 m_fboSize = npotAdjustedSize(size: m_canvasWindow.size() * m_canvasDevicePixelRatio);
581 m_canvasWindowChanged = false;
582
583 if (doMultisampling()) {
584 {
585 QOpenGLFramebufferObjectFormat format;
586 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
587 format.setSamples(8);
588 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
589 }
590 {
591 QOpenGLFramebufferObjectFormat format;
592 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
593 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
594 }
595 } else {
596 QOpenGLFramebufferObjectFormat format;
597 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
598 QSize s = m_fboSize;
599 if (m_antialiasing) { // do supersampling since multisampling is not available
600 GLint max;
601 QOpenGLContext::currentContext()->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &max);
602 if (s.width() * 2 <= max && s.height() * 2 <= max)
603 s = s * 2;
604 }
605 m_fbo = new QOpenGLFramebufferObject(s, format);
606 }
607 }
608
609 if (doMultisampling())
610 m_multisampledFbo->bind();
611 else
612 m_fbo->bind();
613
614 if (!m_paint_device) {
615 QOpenGLPaintDevice *gl_device = new QOpenGLPaintDevice(m_fbo->size());
616 gl_device->setPaintFlipped(true);
617 gl_device->setSize(m_fbo->size());
618 gl_device->setDevicePixelRatio(m_canvasDevicePixelRatio);
619 qCDebug(lcCanvas, "%s size %.1lf x %.1lf painting with size %d x %d DPR %.1lf",
620 (m_item->objectName().isEmpty() ? "Canvas" : qPrintable(m_item->objectName())),
621 m_item->width(), m_item->height(), m_fbo->size().width(), m_fbo->size().height(), m_canvasDevicePixelRatio);
622 m_paint_device = gl_device;
623 }
624
625 return m_paint_device;
626}
627
628void QQuickContext2DFBOTexture::endPainting()
629{
630 QQuickContext2DTexture::endPainting();
631
632 // There may not be an FBO due to zero width or height.
633 if (!m_fbo)
634 return;
635
636 if (m_multisampledFbo)
637 QOpenGLFramebufferObject::blitFramebuffer(target: m_fbo, source: m_multisampledFbo);
638
639 if (m_gl) {
640 /* When rendering happens on the render thread, the fbo's texture is
641 * used directly for display. If we are on the GUI thread or a
642 * dedicated Canvas render thread, we need to decouple the FBO from
643 * the texture we are displaying in the SG rendering thread to avoid
644 * stalls and read/write issues in the GL pipeline as the FBO's texture
645 * could then potentially be used in different threads.
646 *
647 * We could have gotten away with only one display texture, but this
648 * would have implied that beginPainting would have to wait for SG
649 * to release that texture.
650 */
651
652 if (m_onCustomThread)
653 m_mutex.lock();
654
655 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
656 if (m_displayTextures[0] == 0) {
657 m_displayTexture = 1;
658 funcs->glGenTextures(n: 2, textures: m_displayTextures);
659 }
660
661 m_fbo->bind();
662 GLuint target = m_displayTexture == 0 ? 1 : 0;
663 funcs->glBindTexture(GL_TEXTURE_2D, texture: m_displayTextures[target]);
664 funcs->glCopyTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, x: 0, y: 0, width: m_fbo->width(), height: m_fbo->height(), border: 0);
665
666 if (m_onCustomThread)
667 m_mutex.unlock();
668 }
669
670 m_fbo->bindDefault();
671}
672#endif
673
674QQuickContext2DImageTexture::QQuickContext2DImageTexture()
675 : QQuickContext2DTexture()
676{
677}
678
679QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
680{
681}
682
683QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
684{
685 return QQuickCanvasItem::Image;
686}
687
688QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
689{
690 return new QQuickContext2DImageTile();
691}
692
693void QQuickContext2DImageTexture::grabImage(const QRectF& rf)
694{
695 Q_ASSERT(rf.isValid());
696 QQuickContext2D::mutex.lock();
697 if (m_context) {
698 QImage grabbed = m_displayImage.copy(rect: rf.toRect());
699 m_context->setGrabbedImage(grabbed);
700 }
701 QQuickContext2D::mutex.unlock();
702}
703
704QSGTexture *QQuickContext2DImageTexture::textureForNextFrame(QSGTexture *last, QQuickWindow *window)
705{
706 if (m_onCustomThread)
707 m_mutex.lock();
708
709 delete last;
710
711 QSGTexture *texture = window->createTextureFromImage(image: m_displayImage, options: QQuickWindow::TextureCanUseAtlas);
712 m_dirtyTexture = false;
713
714 if (m_onCustomThread)
715 m_mutex.unlock();
716
717 return texture;
718}
719
720QPaintDevice* QQuickContext2DImageTexture::beginPainting()
721{
722 QQuickContext2DTexture::beginPainting();
723
724 if (m_canvasWindow.size().isEmpty())
725 return nullptr;
726
727
728 if (m_canvasWindowChanged) {
729 m_image = QImage(m_canvasWindow.size() * m_canvasDevicePixelRatio, QImage::Format_ARGB32_Premultiplied);
730 m_image.setDevicePixelRatio(m_canvasDevicePixelRatio);
731 m_image.fill(pixel: 0x00000000);
732 m_canvasWindowChanged = false;
733 qCDebug(lcCanvas, "%s size %.1lf x %.1lf painting with size %d x %d DPR %.1lf",
734 (m_item->objectName().isEmpty() ? "Canvas" : qPrintable(m_item->objectName())),
735 m_item->width(), m_item->height(), m_image.size().width(), m_image.size().height(), m_canvasDevicePixelRatio);
736 }
737
738 return &m_image;
739}
740
741void QQuickContext2DImageTexture::endPainting()
742{
743 QQuickContext2DTexture::endPainting();
744 if (m_onCustomThread)
745 m_mutex.lock();
746 m_displayImage = m_image;
747 if (m_onCustomThread)
748 m_mutex.unlock();
749}
750
751void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
752{
753 Q_ASSERT(!tile->dirty());
754 QQuickContext2DImageTile* t = static_cast<QQuickContext2DImageTile*>(tile);
755 QRect target = t->rect().intersected(other: m_canvasWindow);
756 if (target.isValid()) {
757 QRect source = target;
758 source.moveTo(p: source.topLeft() - t->rect().topLeft());
759 target.moveTo(p: target.topLeft() - m_canvasWindow.topLeft());
760
761 m_painter.begin(&m_image);
762 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
763 m_painter.drawImage(targetRect: target, image: t->image(), sourceRect: source);
764 m_painter.end();
765 }
766}
767
768QT_END_NAMESPACE
769
770#include "moc_qquickcontext2dtexture_p.cpp"
771

source code of qtdeclarative/src/quick/items/context2d/qquickcontext2dtexture.cpp