| 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 |  | 
| 13 | QT_BEGIN_NAMESPACE | 
| 14 |  | 
| 15 | Q_LOGGING_CATEGORY(lcCanvas, "qt.quick.canvas" ) | 
| 16 |  | 
| 17 | QQuickContext2DTexture::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 |  | 
| 31 | QQuickContext2DTexture::~QQuickContext2DTexture() | 
| 32 | { | 
| 33 |    clearTiles(); | 
| 34 | } | 
| 35 |  | 
| 36 | void 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 |  | 
| 46 | bool QQuickContext2DTexture::setCanvasSize(const QSize &size) | 
| 47 | { | 
| 48 |     if (m_canvasSize != size) { | 
| 49 |         m_canvasSize = size; | 
| 50 |         return true; | 
| 51 |     } | 
| 52 |     return false; | 
| 53 | } | 
| 54 |  | 
| 55 | bool QQuickContext2DTexture::setTileSize(const QSize &size) | 
| 56 | { | 
| 57 |     if (m_tileSize != size) { | 
| 58 |         m_tileSize = size; | 
| 59 |         return true; | 
| 60 |     } | 
| 61 |     return false; | 
| 62 | } | 
| 63 |  | 
| 64 | void QQuickContext2DTexture::setSmooth(bool smooth) | 
| 65 | { | 
| 66 |     m_smooth = smooth; | 
| 67 | } | 
| 68 |  | 
| 69 | void QQuickContext2DTexture::setAntialiasing(bool antialiasing) | 
| 70 | { | 
| 71 |     m_antialiasing = antialiasing; | 
| 72 | } | 
| 73 |  | 
| 74 | void 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 |  | 
| 85 | bool 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 |  | 
| 112 | bool 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 |  | 
| 128 | void 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 |  | 
| 154 | void 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 |  | 
| 177 | bool QQuickContext2DTexture::canvasDestroyed() | 
| 178 | { | 
| 179 |     return m_item == nullptr; | 
| 180 | } | 
| 181 |  | 
| 182 | void 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 |  | 
| 227 | QRect 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 |  | 
| 243 | QRect 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 |  | 
| 293 | void QQuickContext2DTexture::clearTiles() | 
| 294 | { | 
| 295 |     qDeleteAll(c: m_tiles); | 
| 296 |     m_tiles.clear(); | 
| 297 | } | 
| 298 |  | 
| 299 | QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts) | 
| 300 | { | 
| 301 |     return ts; | 
| 302 | } | 
| 303 |  | 
| 304 | bool 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 |  | 
| 318 | QQuickContext2DImageTexture::QQuickContext2DImageTexture() | 
| 319 |     : QQuickContext2DTexture() | 
| 320 | { | 
| 321 | } | 
| 322 |  | 
| 323 | QQuickContext2DImageTexture::~QQuickContext2DImageTexture() | 
| 324 | { | 
| 325 | } | 
| 326 |  | 
| 327 | QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const | 
| 328 | { | 
| 329 |     return QQuickCanvasItem::Image; | 
| 330 | } | 
| 331 |  | 
| 332 | QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const | 
| 333 | { | 
| 334 |     return new QQuickContext2DImageTile(); | 
| 335 | } | 
| 336 |  | 
| 337 | void 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 |  | 
| 348 | QSGTexture *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 |  | 
| 364 | QPaintDevice* 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 |  | 
| 385 | void 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 |  | 
| 395 | void 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 |  | 
| 412 | QT_END_NAMESPACE | 
| 413 |  | 
| 414 | #include "moc_qquickcontext2dtexture_p.cpp" | 
| 415 |  |