| 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 |  |