1 #include "qsgcontext2dtexture_p.h"
2 #include "qsgcontext2dtile_p.h"
3 #include "qsgcanvasitem_p.h"
5 #include "private/qsgtexture_p.h"
6 #include "qsgcontext2dcommandbuffer_p.h"
7 #include <QOpenGLPaintDevice>
9 #include <QOpenGLFramebufferObject>
10 #include <QOpenGLFramebufferObjectFormat>
11 #include <QtCore/QThread>
13 #define QT_MINIMUM_FBO_SIZE 64
15 static inline int qt_next_power_of_two(int v)
28 Q_GLOBAL_STATIC(QThread, globalCanvasThreadRenderInstance)
31 QSGContext2DTexture::QSGContext2DTexture()
34 , m_canvasSize(QSize(1, 1))
35 , m_tileSize(QSize(1, 1))
36 , m_canvasWindow(QRect(0, 0, 1, 1))
37 , m_dirtyCanvas(false)
38 , m_dirtyTexture(false)
39 , m_threadRendering(false)
41 , m_tiledCanvas(false)
42 , m_doGrabImage(false)
47 QSGContext2DTexture::~QSGContext2DTexture()
52 QSize QSGContext2DTexture::textureSize() const
54 return m_canvasWindow.size();
57 void QSGContext2DTexture::markDirtyTexture()
60 m_dirtyTexture = true;
62 emit textureChanged();
65 bool QSGContext2DTexture::setCanvasSize(const QSize &size)
67 if (m_canvasSize != size) {
75 bool QSGContext2DTexture::setTileSize(const QSize &size)
77 if (m_tileSize != size) {
85 void QSGContext2DTexture::setSmooth(bool smooth)
90 void QSGContext2DTexture::setItem(QSGCanvasItem* item)
98 } else if (m_item != item) {
101 m_context = item->context();
102 m_state = m_context->state;
104 connect(this, SIGNAL(textureChanged()), m_item, SIGNAL(painted()), Qt::QueuedConnection);
105 canvasChanged(item->canvasSize().toSize()
107 , item->canvasWindow().toAlignedRect()
108 , item->canvasWindow().toAlignedRect()
113 bool QSGContext2DTexture::setCanvasWindow(const QRect& r)
115 if (m_canvasWindow != r) {
120 bool QSGContext2DTexture::setDirtyRect(const QRect &r)
122 bool doDirty = false;
123 foreach (QSGContext2DTile* t, m_tiles) {
124 bool dirty = t->rect().intersected(r).isValid();
132 void QSGContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
137 if (ts.width() > canvasSize.width())
138 ts.setWidth(canvasSize.width());
140 if (ts.height() > canvasSize.height())
141 ts.setHeight(canvasSize.height());
143 bool canvasChanged = setCanvasSize(canvasSize);
144 bool tileChanged = setTileSize(ts);
146 bool doDirty = false;
147 if (canvasSize == canvasWindow.size()) {
148 m_tiledCanvas = false;
149 m_dirtyCanvas = false;
151 m_tiledCanvas = true;
152 if (dirtyRect.isValid())
153 doDirty = setDirtyRect(dirtyRect);
156 bool windowChanged = setCanvasWindow(canvasWindow);
158 if (windowChanged || doDirty) {
159 if (m_threadRendering)
160 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
161 else if (supportDirectRendering())
162 QMetaObject::invokeMethod(this, "paint", Qt::DirectConnection);
169 void QSGContext2DTexture::paintWithoutTiles()
171 QSGContext2DCommandBuffer* ccb = m_context->buffer();
173 if (ccb->isEmpty() && m_threadRendering && !m_doGrabImage) {
176 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, QRectF(0, 0, m_canvasSize.width(), m_canvasSize.height())));
180 if (ccb->isEmpty()) {
184 QPaintDevice* device = beginPainting();
193 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
194 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
196 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
197 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false);
198 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
199 m_state = ccb->replay(&p, m_state);
206 bool QSGContext2DTexture::canvasDestroyed()
208 bool noCanvas = false;
210 noCanvas = m_item == 0;
215 void QSGContext2DTexture::paint()
217 if (canvasDestroyed())
220 if (!m_tiledCanvas) {
223 QSGContext2D::State oldState = m_state;
224 QSGContext2DCommandBuffer* ccb = m_context->buffer();
227 QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
230 if (!tiledRegion.isEmpty()) {
231 if (m_threadRendering && !m_doGrabImage) {
235 foreach (QSGContext2DTile* tile, m_tiles) {
237 if (dirtyRect.isEmpty())
238 dirtyRect = tile->rect();
240 dirtyRect |= tile->rect();
245 if (dirtyRect.isValid()) {
248 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, dirtyRect));
254 if (beginPainting()) {
255 foreach (QSGContext2DTile* tile, m_tiles) {
256 bool dirtyTile = false, dirtyCanvas = false, smooth = false;
259 dirtyTile = tile->dirty();
261 dirtyCanvas = m_dirtyCanvas;
264 //canvas size or tile size may change during painting tiles
266 if (m_threadRendering)
267 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
270 } else if (dirtyTile) {
271 m_state = ccb->replay(tile->createPainter(smooth), oldState);
274 tile->markDirty(false);
288 QRect QSGContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
290 if (window.isEmpty())
293 const int tw = tileSize.width();
294 const int th = tileSize.height();
295 const int h1 = window.left() / tw;
296 const int v1 = window.top() / th;
298 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
299 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
301 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
304 QRect QSGContext2DTexture::createTiles(const QRect& window)
306 QList<QSGContext2DTile*> oldTiles = m_tiles;
309 if (window.isEmpty()) {
310 m_dirtyCanvas = false;
314 QRect r = tiledRect(window, m_tileSize);
316 const int tw = m_tileSize.width();
317 const int th = m_tileSize.height();
318 const int h1 = window.left() / tw;
319 const int v1 = window.top() / th;
322 const int htiles = r.width() / tw;
323 const int vtiles = r.height() / th;
325 for (int yy = 0; yy < vtiles; ++yy) {
326 for (int xx = 0; xx < htiles; ++xx) {
330 QSGContext2DTile* tile = 0;
332 QPoint pos(ht * tw, vt * th);
333 QRect rect(pos, m_tileSize);
335 for (int i = 0; i < oldTiles.size(); i++) {
336 if (oldTiles[i]->rect() == rect) {
337 tile = oldTiles.takeAt(i);
346 m_tiles.append(tile);
350 qDeleteAll(oldTiles);
352 m_dirtyCanvas = false;
356 void QSGContext2DTexture::clearTiles()
362 QSGContext2DFBOTexture::QSGContext2DFBOTexture()
363 : QSGContext2DTexture()
367 m_threadRendering = false;
370 bool QSGContext2DFBOTexture::setCanvasSize(const QSize &size)
372 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
373 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
375 if (m_canvasSize != s) {
377 m_dirtyCanvas = true;
383 bool QSGContext2DFBOTexture::setTileSize(const QSize &size)
385 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
386 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
387 if (m_tileSize != s) {
389 m_dirtyCanvas = true;
395 bool QSGContext2DFBOTexture::setCanvasWindow(const QRect& canvasWindow)
397 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().width()))
398 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().height())));
401 bool doChanged = false;
402 if (m_fboSize != s) {
407 if (m_canvasWindow != canvasWindow)
408 m_canvasWindow = canvasWindow;
413 void QSGContext2DFBOTexture::bind()
415 glBindTexture(GL_TEXTURE_2D, textureId());
419 QRectF QSGContext2DFBOTexture::textureSubRect() const
423 , qreal(m_canvasWindow.width()) / m_fboSize.width()
424 , qreal(-m_canvasWindow.height()) / m_fboSize.height());
428 int QSGContext2DFBOTexture::textureId() const
430 return m_fbo? m_fbo->texture() : 0;
434 bool QSGContext2DFBOTexture::updateTexture()
436 if (!m_context->buffer()->isEmpty()) {
440 bool textureUpdated = m_dirtyTexture;
442 m_dirtyTexture = false;
446 m_condition.wakeOne();
447 m_doGrabImage = false;
449 return textureUpdated;
452 QSGContext2DTile* QSGContext2DFBOTexture::createTile() const
454 return new QSGContext2DFBOTile();
457 void QSGContext2DFBOTexture::grabImage()
460 m_grabedImage = m_fbo->toImage();
464 QImage QSGContext2DFBOTexture::toImage(const QRectF& region)
466 #define QML_CONTEXT2D_WAIT_MAX 5000
468 m_doGrabImage = true;
474 bool ok = m_condition.wait(&m_mutex, QML_CONTEXT2D_WAIT_MAX);
479 if (region.isValid())
480 grabbed = m_grabedImage.copy(region.toRect());
482 grabbed = m_grabedImage;
483 m_grabedImage = QImage();
487 void QSGContext2DFBOTexture::compositeTile(QSGContext2DTile* tile)
489 QSGContext2DFBOTile* t = static_cast<QSGContext2DFBOTile*>(tile);
490 QRect target = t->rect().intersect(m_canvasWindow);
491 if (target.isValid()) {
492 QRect source = target;
494 source.moveTo(source.topLeft() - t->rect().topLeft());
495 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
497 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
500 QSGCanvasItem::RenderTarget QSGContext2DFBOTexture::renderTarget() const
502 return QSGCanvasItem::FramebufferObject;
504 QPaintDevice* QSGContext2DFBOTexture::beginPainting()
506 QSGContext2DTexture::beginPainting();
508 if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
511 } else if (!m_fbo || m_fbo->size() != m_fboSize) {
512 QOpenGLFramebufferObjectFormat format;
513 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
514 format.setInternalTextureFormat(GL_RGBA);
515 format.setMipmap(false);
516 format.setTextureTarget(GL_TEXTURE_2D);
518 glDisable(GL_DEPTH_TEST);
521 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
522 glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
523 updateBindOptions(false);
529 m_paint_device = new QOpenGLPaintDevice(m_fbo->size());
531 return m_paint_device;
534 void qt_quit_context2d_render_thread()
536 QThread* thread = globalCanvasThreadRenderInstance();
541 QSGContext2DImageTexture::QSGContext2DImageTexture(bool threadRendering)
542 : QSGContext2DTexture()
543 , m_texture(new QSGPlainTexture())
545 m_texture->setOwnsTexture(true);
546 m_texture->setHasMipmaps(false);
548 m_threadRendering = threadRendering;
550 if (m_threadRendering) {
551 QThread* thread = globalCanvasThreadRenderInstance();
552 moveToThread(thread);
554 if (!thread->isRunning()) {
555 qAddPostRoutine(qt_quit_context2d_render_thread);
561 QSGContext2DImageTexture::~QSGContext2DImageTexture()
563 m_texture->deleteLater();
566 int QSGContext2DImageTexture::textureId() const
568 return m_texture->textureId();
571 void QSGContext2DImageTexture::lock()
573 if (m_threadRendering)
576 void QSGContext2DImageTexture::unlock()
578 if (m_threadRendering)
582 void QSGContext2DImageTexture::wait()
584 if (m_threadRendering)
585 m_waitCondition.wait(&m_mutex);
588 void QSGContext2DImageTexture::wake()
590 if (m_threadRendering)
591 m_waitCondition.wakeOne();
594 bool QSGContext2DImageTexture::supportDirectRendering() const
596 return !m_threadRendering;
599 QSGCanvasItem::RenderTarget QSGContext2DImageTexture::renderTarget() const
601 return QSGCanvasItem::Image;
604 void QSGContext2DImageTexture::bind()
609 bool QSGContext2DImageTexture::updateTexture()
612 bool textureUpdated = m_dirtyTexture;
613 if (m_dirtyTexture) {
614 m_texture->setImage(m_image);
615 m_dirtyTexture = false;
618 return textureUpdated;
621 QSGContext2DTile* QSGContext2DImageTexture::createTile() const
623 return new QSGContext2DImageTile();
626 void QSGContext2DImageTexture::grabImage(const QRect& r)
628 m_doGrabImage = true;
630 m_doGrabImage = false;
631 m_grabedImage = m_image.copy(r);
634 QImage QSGContext2DImageTexture::toImage(const QRectF& region)
636 QRect r = region.isValid() ? region.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
637 if (threadRendering()) {
639 QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
641 QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
643 QImage image = m_grabedImage;
644 m_grabedImage = QImage();
648 QPaintDevice* QSGContext2DImageTexture::beginPainting()
650 QSGContext2DTexture::beginPainting();
652 if (m_canvasWindow.size().isEmpty())
656 if (m_image.size() != m_canvasWindow.size()) {
657 m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
658 m_image.fill(Qt::transparent);
664 void QSGContext2DImageTexture::compositeTile(QSGContext2DTile* tile)
666 Q_ASSERT(!tile->dirty());
667 QSGContext2DImageTile* t = static_cast<QSGContext2DImageTile*>(tile);
668 QRect target = t->rect().intersect(m_canvasWindow);
669 if (target.isValid()) {
670 QRect source = target;
671 source.moveTo(source.topLeft() - t->rect().topLeft());
672 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
675 m_painter.begin(&m_image);
676 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
677 m_painter.drawImage(target, t->image(), source);