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 | |
56 | QT_BEGIN_NAMESPACE |
57 | |
58 | Q_LOGGING_CATEGORY(lcCanvas, "qt.quick.canvas" ) |
59 | |
60 | #if QT_CONFIG(opengl) |
61 | #define QT_MINIMUM_FBO_SIZE 64 |
62 | |
63 | static 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 | |
75 | struct 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 |
95 | QQuickContext2DTexture::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 | |
112 | QQuickContext2DTexture::~QQuickContext2DTexture() |
113 | { |
114 | clearTiles(); |
115 | } |
116 | |
117 | void 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 | |
127 | bool QQuickContext2DTexture::setCanvasSize(const QSize &size) |
128 | { |
129 | if (m_canvasSize != size) { |
130 | m_canvasSize = size; |
131 | return true; |
132 | } |
133 | return false; |
134 | } |
135 | |
136 | bool QQuickContext2DTexture::setTileSize(const QSize &size) |
137 | { |
138 | if (m_tileSize != size) { |
139 | m_tileSize = size; |
140 | return true; |
141 | } |
142 | return false; |
143 | } |
144 | |
145 | void QQuickContext2DTexture::setSmooth(bool smooth) |
146 | { |
147 | m_smooth = smooth; |
148 | } |
149 | |
150 | void QQuickContext2DTexture::setAntialiasing(bool antialiasing) |
151 | { |
152 | m_antialiasing = antialiasing; |
153 | } |
154 | |
155 | void 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 | |
166 | bool 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 | |
193 | bool 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 | |
209 | void 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 | |
235 | void 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 | |
258 | bool QQuickContext2DTexture::canvasDestroyed() |
259 | { |
260 | return m_item == nullptr; |
261 | } |
262 | |
263 | void 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 | |
311 | QRect 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 | |
327 | QRect 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 | |
377 | void QQuickContext2DTexture::clearTiles() |
378 | { |
379 | qDeleteAll(c: m_tiles); |
380 | m_tiles.clear(); |
381 | } |
382 | |
383 | QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts) |
384 | { |
385 | return ts; |
386 | } |
387 | |
388 | bool 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) |
402 | static 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 | |
421 | QQuickContext2DFBOTexture::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 | |
432 | QQuickContext2DFBOTexture::~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 | |
447 | QVector2D 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 | |
455 | QSGTexture *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 | |
493 | QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts) |
494 | { |
495 | return npotAdjustedSize(size: ts); |
496 | } |
497 | |
498 | QRectF 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 | |
506 | QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const |
507 | { |
508 | return new QQuickContext2DFBOTile(); |
509 | } |
510 | |
511 | bool 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 | |
526 | void 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 | |
543 | void 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 | |
557 | QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const |
558 | { |
559 | return QQuickCanvasItem::FramebufferObject; |
560 | } |
561 | |
562 | QPaintDevice* 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 | |
628 | void 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 | |
674 | QQuickContext2DImageTexture::QQuickContext2DImageTexture() |
675 | : QQuickContext2DTexture() |
676 | { |
677 | } |
678 | |
679 | QQuickContext2DImageTexture::~QQuickContext2DImageTexture() |
680 | { |
681 | } |
682 | |
683 | QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const |
684 | { |
685 | return QQuickCanvasItem::Image; |
686 | } |
687 | |
688 | QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const |
689 | { |
690 | return new QQuickContext2DImageTile(); |
691 | } |
692 | |
693 | void 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 | |
704 | QSGTexture *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 | |
720 | QPaintDevice* 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 | |
741 | void 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 | |
751 | void 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 | |
768 | QT_END_NAMESPACE |
769 | |
770 | #include "moc_qquickcontext2dtexture_p.cpp" |
771 | |