1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qsgcontext2dtexture_p.h"
43 #include "qsgcontext2dtile_p.h"
44 #include "qsgcanvasitem_p.h"
45 #include "qsgitem_p.h"
46 #include "private/qsgtexture_p.h"
47 #include "qsgcontext2dcommandbuffer_p.h"
48 #include <QOpenGLPaintDevice>
50 #include <QOpenGLFramebufferObject>
51 #include <QOpenGLFramebufferObjectFormat>
52 #include <QtCore/QThread>
54 #define QT_MINIMUM_FBO_SIZE 64
56 static inline int qt_next_power_of_two(int v)
69 Q_GLOBAL_STATIC(QThread, globalCanvasThreadRenderInstance)
72 QSGContext2DTexture::QSGContext2DTexture()
75 , m_canvasSize(QSize(1, 1))
76 , m_tileSize(QSize(1, 1))
77 , m_canvasWindow(QRect(0, 0, 1, 1))
78 , m_dirtyCanvas(false)
79 , m_dirtyTexture(false)
80 , m_threadRendering(false)
82 , m_tiledCanvas(false)
83 , m_doGrabImage(false)
88 QSGContext2DTexture::~QSGContext2DTexture()
93 QSize QSGContext2DTexture::textureSize() const
95 return m_canvasWindow.size();
98 void QSGContext2DTexture::markDirtyTexture()
101 m_dirtyTexture = true;
103 emit textureChanged();
106 bool QSGContext2DTexture::setCanvasSize(const QSize &size)
108 if (m_canvasSize != size) {
110 m_dirtyCanvas = true;
116 bool QSGContext2DTexture::setTileSize(const QSize &size)
118 if (m_tileSize != size) {
120 m_dirtyCanvas = true;
126 void QSGContext2DTexture::setSmooth(bool smooth)
131 void QSGContext2DTexture::setItem(QSGCanvasItem* item)
139 } else if (m_item != item) {
142 m_context = item->context();
143 m_state = m_context->state;
145 connect(this, SIGNAL(textureChanged()), m_item, SIGNAL(painted()));
149 bool QSGContext2DTexture::setCanvasWindow(const QRect& r)
151 if (m_canvasWindow != r) {
158 bool QSGContext2DTexture::setDirtyRect(const QRect &r)
160 bool doDirty = false;
162 foreach (QSGContext2DTile* t, m_tiles) {
163 bool dirty = t->rect().intersected(r).isValid();
169 doDirty = m_canvasWindow.intersected(r).isValid();
174 void QSGContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
179 if (ts.width() > canvasSize.width())
180 ts.setWidth(canvasSize.width());
182 if (ts.height() > canvasSize.height())
183 ts.setHeight(canvasSize.height());
185 bool canvasChanged = setCanvasSize(canvasSize);
186 bool tileChanged = setTileSize(ts);
188 if (canvasSize == canvasWindow.size()) {
189 m_tiledCanvas = false;
190 m_dirtyCanvas = false;
192 m_tiledCanvas = true;
195 bool doDirty = false;
196 if (dirtyRect.isValid())
197 doDirty = setDirtyRect(dirtyRect);
199 bool windowChanged = setCanvasWindow(canvasWindow);
200 if (windowChanged || doDirty) {
201 if (m_threadRendering)
202 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
203 else if (supportDirectRendering()) {
204 QMetaObject::invokeMethod(this, "paint", Qt::DirectConnection);
212 void QSGContext2DTexture::paintWithoutTiles()
214 QSGContext2DCommandBuffer* ccb = m_context->buffer();
216 if (ccb->isEmpty() && m_threadRendering && !m_doGrabImage) {
219 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, QRectF(0, 0, m_canvasSize.width(), m_canvasSize.height())));
223 if (ccb->isEmpty()) {
227 QPaintDevice* device = beginPainting();
236 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
237 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
239 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
240 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false);
241 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
242 m_state = ccb->replay(&p, m_state);
249 bool QSGContext2DTexture::canvasDestroyed()
251 bool noCanvas = false;
253 noCanvas = m_item == 0;
258 void QSGContext2DTexture::paint()
260 if (canvasDestroyed())
263 if (!m_tiledCanvas) {
266 QSGContext2D::State oldState = m_state;
267 QSGContext2DCommandBuffer* ccb = m_context->buffer();
270 QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
273 if (!tiledRegion.isEmpty()) {
274 if (m_threadRendering && !m_doGrabImage) {
278 foreach (QSGContext2DTile* tile, m_tiles) {
280 if (dirtyRect.isEmpty())
281 dirtyRect = tile->rect();
283 dirtyRect |= tile->rect();
288 if (dirtyRect.isValid()) {
291 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, dirtyRect));
297 if (beginPainting()) {
298 foreach (QSGContext2DTile* tile, m_tiles) {
299 bool dirtyTile = false, dirtyCanvas = false, smooth = false;
302 dirtyTile = tile->dirty();
304 dirtyCanvas = m_dirtyCanvas;
307 //canvas size or tile size may change during painting tiles
309 if (m_threadRendering)
310 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
313 } else if (dirtyTile) {
314 m_state = ccb->replay(tile->createPainter(smooth), oldState);
315 tile->drawFinished();
317 tile->markDirty(false);
331 QRect QSGContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
333 if (window.isEmpty())
336 const int tw = tileSize.width();
337 const int th = tileSize.height();
338 const int h1 = window.left() / tw;
339 const int v1 = window.top() / th;
341 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
342 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
344 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
347 QRect QSGContext2DTexture::createTiles(const QRect& window)
349 QList<QSGContext2DTile*> oldTiles = m_tiles;
352 if (window.isEmpty()) {
353 m_dirtyCanvas = false;
357 QRect r = tiledRect(window, m_tileSize);
359 const int tw = m_tileSize.width();
360 const int th = m_tileSize.height();
361 const int h1 = window.left() / tw;
362 const int v1 = window.top() / th;
365 const int htiles = r.width() / tw;
366 const int vtiles = r.height() / th;
368 for (int yy = 0; yy < vtiles; ++yy) {
369 for (int xx = 0; xx < htiles; ++xx) {
373 QSGContext2DTile* tile = 0;
375 QPoint pos(ht * tw, vt * th);
376 QRect rect(pos, m_tileSize);
378 for (int i = 0; i < oldTiles.size(); i++) {
379 if (oldTiles[i]->rect() == rect) {
380 tile = oldTiles.takeAt(i);
389 m_tiles.append(tile);
393 qDeleteAll(oldTiles);
395 m_dirtyCanvas = false;
399 void QSGContext2DTexture::clearTiles()
405 QSGContext2DFBOTexture::QSGContext2DFBOTexture()
406 : QSGContext2DTexture()
408 , m_multisampledFbo(0)
411 m_threadRendering = false;
414 QSGContext2DFBOTexture::~QSGContext2DFBOTexture()
417 delete m_multisampledFbo;
418 delete m_paint_device;
421 bool QSGContext2DFBOTexture::setCanvasSize(const QSize &size)
423 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
424 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
426 if (m_canvasSize != s) {
428 m_dirtyCanvas = true;
434 bool QSGContext2DFBOTexture::setTileSize(const QSize &size)
436 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
437 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
438 if (m_tileSize != s) {
440 m_dirtyCanvas = true;
446 bool QSGContext2DFBOTexture::setCanvasWindow(const QRect& canvasWindow)
448 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().width()))
449 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().height())));
452 bool doChanged = false;
453 if (m_fboSize != s) {
458 if (m_canvasWindow != canvasWindow)
459 m_canvasWindow = canvasWindow;
464 void QSGContext2DFBOTexture::bind()
466 glBindTexture(GL_TEXTURE_2D, textureId());
470 QRectF QSGContext2DFBOTexture::textureSubRect() const
474 , qreal(m_canvasWindow.width()) / m_fboSize.width()
475 , qreal(m_canvasWindow.height()) / m_fboSize.height());
479 int QSGContext2DFBOTexture::textureId() const
481 return m_fbo? m_fbo->texture() : 0;
485 bool QSGContext2DFBOTexture::updateTexture()
487 if (!m_context->buffer()->isEmpty()) {
491 bool textureUpdated = m_dirtyTexture;
493 m_dirtyTexture = false;
497 m_condition.wakeOne();
498 m_doGrabImage = false;
500 return textureUpdated;
503 QSGContext2DTile* QSGContext2DFBOTexture::createTile() const
505 return new QSGContext2DFBOTile();
508 void QSGContext2DFBOTexture::grabImage()
511 m_grabedImage = m_fbo->toImage();
515 bool QSGContext2DFBOTexture::doMultisampling() const
517 static bool extensionsChecked = false;
518 static bool multisamplingSupported = false;
520 if (!extensionsChecked) {
521 QList<QByteArray> extensions = QByteArray((const char *)glGetString(GL_EXTENSIONS)).split(' ');
522 multisamplingSupported = extensions.contains("GL_EXT_framebuffer_multisample")
523 && extensions.contains("GL_EXT_framebuffer_blit");
524 extensionsChecked = true;
527 return multisamplingSupported && m_smooth;
530 QImage QSGContext2DFBOTexture::toImage(const QRectF& region)
532 #define QML_CONTEXT2D_WAIT_MAX 5000
534 m_doGrabImage = true;
540 bool ok = m_condition.wait(&m_mutex, QML_CONTEXT2D_WAIT_MAX);
545 if (region.isValid())
546 grabbed = m_grabedImage.copy(region.toRect());
548 grabbed = m_grabedImage;
549 m_grabedImage = QImage();
553 void QSGContext2DFBOTexture::compositeTile(QSGContext2DTile* tile)
555 QSGContext2DFBOTile* t = static_cast<QSGContext2DFBOTile*>(tile);
556 QRect target = t->rect().intersect(m_canvasWindow);
557 if (target.isValid()) {
558 QRect source = target;
560 source.moveTo(source.topLeft() - t->rect().topLeft());
561 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
563 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
566 QSGCanvasItem::RenderTarget QSGContext2DFBOTexture::renderTarget() const
568 return QSGCanvasItem::FramebufferObject;
570 QPaintDevice* QSGContext2DFBOTexture::beginPainting()
572 QSGContext2DTexture::beginPainting();
574 if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
576 delete m_multisampledFbo;
578 m_multisampledFbo = 0;
580 } else if (!m_fbo || m_fbo->size() != m_fboSize) {
582 delete m_multisampledFbo;
583 if (doMultisampling()) {
585 QOpenGLFramebufferObjectFormat format;
586 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
587 format.setSamples(8);
588 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
591 QOpenGLFramebufferObjectFormat format;
592 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
593 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
596 QOpenGLFramebufferObjectFormat format;
597 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
599 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
603 if (doMultisampling())
604 m_multisampledFbo->bind();
609 if (!m_paint_device) {
610 QOpenGLPaintDevice *gl_device = new QOpenGLPaintDevice(m_fbo->size());
611 gl_device->setPaintFlipped(true);
612 m_paint_device = gl_device;
615 return m_paint_device;
618 void QSGContext2DFBOTexture::endPainting()
620 QSGContext2DTexture::endPainting();
621 if (m_multisampledFbo) {
622 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo);
623 m_multisampledFbo->release();
627 void qt_quit_context2d_render_thread()
629 QThread* thread = globalCanvasThreadRenderInstance();
631 if (thread->isRunning()) {
637 QSGContext2DImageTexture::QSGContext2DImageTexture(bool threadRendering)
638 : QSGContext2DTexture()
639 , m_texture(new QSGPlainTexture())
641 m_texture->setOwnsTexture(true);
642 m_texture->setHasMipmaps(false);
644 m_threadRendering = threadRendering;
646 if (m_threadRendering) {
647 QThread* thread = globalCanvasThreadRenderInstance();
648 moveToThread(thread);
650 if (!thread->isRunning()) {
651 qAddPostRoutine(qt_quit_context2d_render_thread);
657 QSGContext2DImageTexture::~QSGContext2DImageTexture()
659 m_texture->deleteLater();
662 int QSGContext2DImageTexture::textureId() const
664 return m_texture->textureId();
667 void QSGContext2DImageTexture::lock()
669 if (m_threadRendering)
672 void QSGContext2DImageTexture::unlock()
674 if (m_threadRendering)
678 void QSGContext2DImageTexture::wait()
680 if (m_threadRendering)
681 m_waitCondition.wait(&m_mutex);
684 void QSGContext2DImageTexture::wake()
686 if (m_threadRendering)
687 m_waitCondition.wakeOne();
690 bool QSGContext2DImageTexture::supportDirectRendering() const
692 return !m_threadRendering;
695 QSGCanvasItem::RenderTarget QSGContext2DImageTexture::renderTarget() const
697 return QSGCanvasItem::Image;
700 void QSGContext2DImageTexture::bind()
705 bool QSGContext2DImageTexture::updateTexture()
708 bool textureUpdated = m_dirtyTexture;
709 if (m_dirtyTexture) {
710 m_texture->setImage(m_image);
711 m_dirtyTexture = false;
714 return textureUpdated;
717 QSGContext2DTile* QSGContext2DImageTexture::createTile() const
719 return new QSGContext2DImageTile();
722 void QSGContext2DImageTexture::grabImage(const QRect& r)
724 m_doGrabImage = true;
726 m_doGrabImage = false;
727 m_grabedImage = m_image.copy(r);
730 QImage QSGContext2DImageTexture::toImage(const QRectF& region)
732 QRect r = region.isValid() ? region.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
733 if (threadRendering()) {
735 QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
737 QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
739 QImage image = m_grabedImage;
740 m_grabedImage = QImage();
744 QPaintDevice* QSGContext2DImageTexture::beginPainting()
746 QSGContext2DTexture::beginPainting();
748 if (m_canvasWindow.size().isEmpty())
752 if (m_image.size() != m_canvasWindow.size()) {
753 m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
754 m_image.fill(0x00000000);
760 void QSGContext2DImageTexture::compositeTile(QSGContext2DTile* tile)
762 Q_ASSERT(!tile->dirty());
763 QSGContext2DImageTile* t = static_cast<QSGContext2DImageTile*>(tile);
764 QRect target = t->rect().intersect(m_canvasWindow);
765 if (target.isValid()) {
766 QRect source = target;
767 source.moveTo(source.topLeft() - t->rect().topLeft());
768 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
771 m_painter.begin(&m_image);
772 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
773 m_painter.drawImage(target, t->image(), source);