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 "qquickcontext2dtexture_p.h"
5#include "qquickcontext2dtile_p.h"
6#include "qquickcanvasitem_p.h"
7#include <private/qquickitem_p.h>
8#include <QtQuick/private/qsgplaintexture_p.h>
9#include "qquickcontext2dcommandbuffer_p.h"
10#include <QtCore/QThread>
11#include <QtGui/QGuiApplication>
12
13QT_BEGIN_NAMESPACE
14
15Q_LOGGING_CATEGORY(lcCanvas, "qt.quick.canvas")
16
17QQuickContext2DTexture::QQuickContext2DTexture()
18 : m_context(nullptr)
19 , m_surface(nullptr)
20 , m_item(nullptr)
21 , m_canvasDevicePixelRatio(1)
22 , m_canvasWindowChanged(false)
23 , m_dirtyTexture(false)
24 , m_smooth(true)
25 , m_antialiasing(false)
26 , m_tiledCanvas(false)
27 , m_painting(false)
28{
29}
30
31QQuickContext2DTexture::~QQuickContext2DTexture()
32{
33 clearTiles();
34}
35
36void QQuickContext2DTexture::markDirtyTexture()
37{
38 if (m_onCustomThread)
39 m_mutex.lock();
40 m_dirtyTexture = true;
41 emit textureChanged();
42 if (m_onCustomThread)
43 m_mutex.unlock();
44}
45
46bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
47{
48 if (m_canvasSize != size) {
49 m_canvasSize = size;
50 return true;
51 }
52 return false;
53}
54
55bool QQuickContext2DTexture::setTileSize(const QSize &size)
56{
57 if (m_tileSize != size) {
58 m_tileSize = size;
59 return true;
60 }
61 return false;
62}
63
64void QQuickContext2DTexture::setSmooth(bool smooth)
65{
66 m_smooth = smooth;
67}
68
69void QQuickContext2DTexture::setAntialiasing(bool antialiasing)
70{
71 m_antialiasing = antialiasing;
72}
73
74void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
75{
76 m_item = item;
77 if (m_item) {
78 m_context = (QQuickContext2D*) item->rawContext(); // FIXME
79 m_state = m_context->state;
80 } else {
81 m_context = nullptr;
82 }
83}
84
85bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
86{
87 bool ok = false;
88 static qreal overriddenDevicePixelRatio =
89 !qEnvironmentVariableIsEmpty(varName: "QT_CANVAS_OVERRIDE_DEVICEPIXELRATIO") ?
90 qgetenv(varName: "QT_CANVAS_OVERRIDE_DEVICEPIXELRATIO").toFloat(ok: &ok) : 0.0;
91 qreal canvasDevicePixelRatio = overriddenDevicePixelRatio;
92 if (overriddenDevicePixelRatio == 0.0) {
93 canvasDevicePixelRatio = (m_item && m_item->window()) ?
94 m_item->window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio();
95 }
96 if (!qFuzzyCompare(p1: m_canvasDevicePixelRatio, p2: canvasDevicePixelRatio)) {
97 qCDebug(lcCanvas, "%s device pixel ratio %.1lf -> %.1lf",
98 (m_item->objectName().isEmpty() ? "Canvas" : qPrintable(m_item->objectName())),
99 m_canvasDevicePixelRatio, canvasDevicePixelRatio);
100 m_canvasDevicePixelRatio = canvasDevicePixelRatio;
101 m_canvasWindowChanged = true;
102 }
103
104 if (m_canvasWindow != r) {
105 m_canvasWindow = r;
106 m_canvasWindowChanged = true;
107 }
108
109 return m_canvasWindowChanged;
110}
111
112bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
113{
114 bool doDirty = false;
115 if (m_tiledCanvas) {
116 for (QQuickContext2DTile* t : std::as_const(t&: m_tiles)) {
117 bool dirty = t->rect().intersected(other: r).isValid();
118 t->markDirty(dirty);
119 if (dirty)
120 doDirty = true;
121 }
122 } else {
123 doDirty = m_canvasWindow.intersected(other: r).isValid();
124 }
125 return doDirty;
126}
127
128void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing)
129{
130 QSize ts = tileSize;
131 if (ts.width() > canvasSize.width())
132 ts.setWidth(canvasSize.width());
133
134 if (ts.height() > canvasSize.height())
135 ts.setHeight(canvasSize.height());
136
137 setCanvasSize(canvasSize);
138 setTileSize(ts);
139 setCanvasWindow(canvasWindow);
140
141 if (canvasSize == canvasWindow.size()) {
142 m_tiledCanvas = false;
143 } else {
144 m_tiledCanvas = true;
145 }
146
147 if (dirtyRect.isValid())
148 setDirtyRect(dirtyRect);
149
150 setSmooth(smooth);
151 setAntialiasing(antialiasing);
152}
153
154void QQuickContext2DTexture::paintWithoutTiles(QQuickContext2DCommandBuffer *ccb)
155{
156 if (!ccb || ccb->isEmpty())
157 return;
158
159 QPaintDevice* device = beginPainting();
160 if (!device) {
161 endPainting();
162 return;
163 }
164
165 QPainter p;
166 p.begin(device);
167 p.setRenderHints(hints: QPainter::Antialiasing | QPainter::TextAntialiasing, on: m_antialiasing);
168 p.setRenderHint(hint: QPainter::SmoothPixmapTransform, on: m_smooth);
169
170 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
171
172 ccb->replay(painter: &p, state&: m_state, scaleFactor: scaleFactor());
173 endPainting();
174 markDirtyTexture();
175}
176
177bool QQuickContext2DTexture::canvasDestroyed()
178{
179 return m_item == nullptr;
180}
181
182void QQuickContext2DTexture::paint(QQuickContext2DCommandBuffer *ccb)
183{
184 QQuickContext2D::mutex.lock();
185 if (canvasDestroyed()) {
186 delete ccb;
187 QQuickContext2D::mutex.unlock();
188 return;
189 }
190 QQuickContext2D::mutex.unlock();
191 if (!m_tiledCanvas) {
192 paintWithoutTiles(ccb);
193 delete ccb;
194 return;
195 }
196
197 QRect tiledRegion = createTiles(window: m_canvasWindow.intersected(other: QRect(QPoint(0, 0), m_canvasSize)));
198 if (!tiledRegion.isEmpty()) {
199 QRect dirtyRect;
200 for (QQuickContext2DTile* tile : std::as_const(t&: m_tiles)) {
201 if (tile->dirty()) {
202 if (dirtyRect.isEmpty())
203 dirtyRect = tile->rect();
204 else
205 dirtyRect |= tile->rect();
206 }
207 }
208
209 if (beginPainting()) {
210 QQuickContext2D::State oldState = m_state;
211 for (QQuickContext2DTile* tile : std::as_const(t&: m_tiles)) {
212 if (tile->dirty()) {
213 ccb->replay(painter: tile->createPainter(smooth: m_smooth, antialiasing: m_antialiasing), state&: oldState, scaleFactor: scaleFactor());
214 tile->drawFinished();
215 tile->markDirty(dirty: false);
216 }
217 compositeTile(tile);
218 }
219 endPainting();
220 m_state = oldState;
221 markDirtyTexture();
222 }
223 }
224 delete ccb;
225}
226
227QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
228{
229 if (window.isEmpty())
230 return QRect();
231
232 const int tw = tileSize.width();
233 const int th = tileSize.height();
234 const int h1 = window.left() / tw;
235 const int v1 = window.top() / th;
236
237 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
238 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
239
240 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
241}
242
243QRect QQuickContext2DTexture::createTiles(const QRect& window)
244{
245 QList<QQuickContext2DTile*> oldTiles = m_tiles;
246 m_tiles.clear();
247
248 if (window.isEmpty()) {
249 return QRect();
250 }
251
252 QRect r = tiledRect(window, tileSize: adjustedTileSize(ts: m_tileSize));
253
254 const int tw = m_tileSize.width();
255 const int th = m_tileSize.height();
256 const int h1 = window.left() / tw;
257 const int v1 = window.top() / th;
258
259
260 const int htiles = r.width() / tw;
261 const int vtiles = r.height() / th;
262
263 for (int yy = 0; yy < vtiles; ++yy) {
264 for (int xx = 0; xx < htiles; ++xx) {
265 int ht = xx + h1;
266 int vt = yy + v1;
267
268 QQuickContext2DTile* tile = nullptr;
269
270 QPoint pos(ht * tw, vt * th);
271 QRect rect(pos, m_tileSize);
272
273 for (int i = 0; i < oldTiles.size(); i++) {
274 if (oldTiles[i]->rect() == rect) {
275 tile = oldTiles.takeAt(i);
276 break;
277 }
278 }
279
280 if (!tile)
281 tile = createTile();
282
283 tile->setRect(rect);
284 m_tiles.append(t: tile);
285 }
286 }
287
288 qDeleteAll(c: oldTiles);
289
290 return r;
291}
292
293void QQuickContext2DTexture::clearTiles()
294{
295 qDeleteAll(c: m_tiles);
296 m_tiles.clear();
297}
298
299QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts)
300{
301 return ts;
302}
303
304bool QQuickContext2DTexture::event(QEvent *e)
305{
306 if ((int) e->type() == QEvent::User + 1) {
307 PaintEvent *pe = static_cast<PaintEvent *>(e);
308 paint(ccb: pe->buffer);
309 return true;
310 } else if ((int) e->type() == QEvent::User + 2) {
311 CanvasChangeEvent *ce = static_cast<CanvasChangeEvent *>(e);
312 canvasChanged(canvasSize: ce->canvasSize, tileSize: ce->tileSize, canvasWindow: ce->canvasWindow, dirtyRect: ce->dirtyRect, smooth: ce->smooth, antialiasing: ce->antialiasing);
313 return true;
314 }
315 return QObject::event(event: e);
316}
317
318QQuickContext2DImageTexture::QQuickContext2DImageTexture()
319 : QQuickContext2DTexture()
320{
321}
322
323QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
324{
325}
326
327QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
328{
329 return QQuickCanvasItem::Image;
330}
331
332QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
333{
334 return new QQuickContext2DImageTile();
335}
336
337void QQuickContext2DImageTexture::grabImage(const QRectF& rf)
338{
339 Q_ASSERT(rf.isValid());
340 QQuickContext2D::mutex.lock();
341 if (m_context) {
342 QImage grabbed = m_displayImage.copy(rect: rf.toRect());
343 m_context->setGrabbedImage(grabbed);
344 }
345 QQuickContext2D::mutex.unlock();
346}
347
348QSGTexture *QQuickContext2DImageTexture::textureForNextFrame(QSGTexture *last, QQuickWindow *window)
349{
350 if (m_onCustomThread)
351 m_mutex.lock();
352
353 delete last;
354
355 QSGTexture *texture = window->createTextureFromImage(image: m_displayImage, options: QQuickWindow::TextureCanUseAtlas);
356 m_dirtyTexture = false;
357
358 if (m_onCustomThread)
359 m_mutex.unlock();
360
361 return texture;
362}
363
364QPaintDevice* QQuickContext2DImageTexture::beginPainting()
365{
366 QQuickContext2DTexture::beginPainting();
367
368 if (m_canvasWindow.size().isEmpty())
369 return nullptr;
370
371
372 if (m_canvasWindowChanged) {
373 m_image = QImage(m_canvasWindow.size() * m_canvasDevicePixelRatio, QImage::Format_ARGB32_Premultiplied);
374 m_image.setDevicePixelRatio(m_canvasDevicePixelRatio);
375 m_image.fill(pixel: 0x00000000);
376 m_canvasWindowChanged = false;
377 qCDebug(lcCanvas, "%s size %.1lf x %.1lf painting with size %d x %d DPR %.1lf",
378 (m_item->objectName().isEmpty() ? "Canvas" : qPrintable(m_item->objectName())),
379 m_item->width(), m_item->height(), m_image.size().width(), m_image.size().height(), m_canvasDevicePixelRatio);
380 }
381
382 return &m_image;
383}
384
385void QQuickContext2DImageTexture::endPainting()
386{
387 QQuickContext2DTexture::endPainting();
388 if (m_onCustomThread)
389 m_mutex.lock();
390 m_displayImage = m_image;
391 if (m_onCustomThread)
392 m_mutex.unlock();
393}
394
395void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
396{
397 Q_ASSERT(!tile->dirty());
398 QQuickContext2DImageTile* t = static_cast<QQuickContext2DImageTile*>(tile);
399 QRect target = t->rect().intersected(other: m_canvasWindow);
400 if (target.isValid()) {
401 QRect source = target;
402 source.moveTo(p: source.topLeft() - t->rect().topLeft());
403 target.moveTo(p: target.topLeft() - m_canvasWindow.topLeft());
404
405 m_painter.begin(&m_image);
406 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
407 m_painter.drawImage(targetRect: target, image: t->image(), sourceRect: source);
408 m_painter.end();
409 }
410}
411
412QT_END_NAMESPACE
413
414#include "moc_qquickcontext2dtexture_p.cpp"
415

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