1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
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 "qquickcontext2dtexture_p.h"
43 #include "qquickcontext2dtile_p.h"
44 #include "qquickcanvasitem_p.h"
45 #include <private/qquickitem_p.h>
46 #include <QtQuick/private/qsgtexture_p.h>
47 #include "qquickcontext2dcommandbuffer_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 QQuickContext2DTexture::QQuickContext2DTexture()
76 , m_canvasSize(QSize(1, 1))
77 , m_tileSize(QSize(1, 1))
78 , m_canvasWindow(QRect(0, 0, 1, 1))
79 , m_dirtyCanvas(false)
80 , m_dirtyTexture(false)
81 , m_threadRendering(false)
83 , m_tiledCanvas(false)
84 , m_doGrabImage(false)
89 QQuickContext2DTexture::~QQuickContext2DTexture()
94 QSize QQuickContext2DTexture::textureSize() const
96 return m_canvasWindow.size();
99 void QQuickContext2DTexture::markDirtyTexture()
102 m_dirtyTexture = true;
104 emit textureChanged();
107 bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
109 if (m_canvasSize != size) {
111 m_dirtyCanvas = true;
117 bool QQuickContext2DTexture::setTileSize(const QSize &size)
119 if (m_tileSize != size) {
121 m_dirtyCanvas = true;
127 void QQuickContext2DTexture::setSmooth(bool smooth)
132 void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
140 } else if (m_item != item) {
143 m_context = item->context();
144 m_state = m_context->state;
146 connect(this, SIGNAL(textureChanged()), m_item, SIGNAL(painted()));
150 bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
152 if (m_canvasWindow != r) {
159 bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
161 bool doDirty = false;
163 foreach (QQuickContext2DTile* t, m_tiles) {
164 bool dirty = t->rect().intersected(r).isValid();
170 doDirty = m_canvasWindow.intersected(r).isValid();
175 void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
180 if (ts.width() > canvasSize.width())
181 ts.setWidth(canvasSize.width());
183 if (ts.height() > canvasSize.height())
184 ts.setHeight(canvasSize.height());
186 setCanvasSize(canvasSize);
189 if (canvasSize == canvasWindow.size()) {
190 m_tiledCanvas = false;
191 m_dirtyCanvas = false;
193 m_tiledCanvas = true;
196 bool doDirty = false;
197 if (dirtyRect.isValid())
198 doDirty = setDirtyRect(dirtyRect);
200 bool windowChanged = setCanvasWindow(canvasWindow);
201 if (windowChanged || doDirty) {
202 if (m_threadRendering)
203 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
204 else if (supportDirectRendering()) {
205 QMetaObject::invokeMethod(this, "paint", Qt::DirectConnection);
213 void QQuickContext2DTexture::paintWithoutTiles()
215 QQuickContext2DCommandBuffer* ccb = m_context->buffer();
217 if (ccb->isEmpty() && m_threadRendering && !m_doGrabImage) {
220 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, QRectF(0, 0, m_canvasSize.width(), m_canvasSize.height())));
224 if (ccb->isEmpty()) {
228 QPaintDevice* device = beginPainting();
237 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
238 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
240 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
241 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false);
242 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
243 ccb->replay(&p, m_state);
250 bool QQuickContext2DTexture::canvasDestroyed()
252 bool noCanvas = false;
254 noCanvas = m_item == 0;
259 void QQuickContext2DTexture::paint()
261 if (canvasDestroyed())
264 if (!m_tiledCanvas) {
268 QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
271 if (!tiledRegion.isEmpty()) {
272 if (m_threadRendering && !m_doGrabImage) {
276 foreach (QQuickContext2DTile* tile, m_tiles) {
278 if (dirtyRect.isEmpty())
279 dirtyRect = tile->rect();
281 dirtyRect |= tile->rect();
286 if (dirtyRect.isValid()) {
289 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, dirtyRect));
295 if (beginPainting()) {
296 QQuickContext2D::State oldState = m_state;
297 QQuickContext2DCommandBuffer* ccb = m_context->buffer();
298 foreach (QQuickContext2DTile* 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 ccb->replay(tile->createPainter(smooth), oldState);
315 tile->drawFinished();
317 tile->markDirty(false);
332 QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
334 if (window.isEmpty())
337 const int tw = tileSize.width();
338 const int th = tileSize.height();
339 const int h1 = window.left() / tw;
340 const int v1 = window.top() / th;
342 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
343 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
345 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
348 QRect QQuickContext2DTexture::createTiles(const QRect& window)
350 QList<QQuickContext2DTile*> oldTiles = m_tiles;
353 if (window.isEmpty()) {
354 m_dirtyCanvas = false;
358 QRect r = tiledRect(window, m_tileSize);
360 const int tw = m_tileSize.width();
361 const int th = m_tileSize.height();
362 const int h1 = window.left() / tw;
363 const int v1 = window.top() / th;
366 const int htiles = r.width() / tw;
367 const int vtiles = r.height() / th;
369 for (int yy = 0; yy < vtiles; ++yy) {
370 for (int xx = 0; xx < htiles; ++xx) {
374 QQuickContext2DTile* tile = 0;
376 QPoint pos(ht * tw, vt * th);
377 QRect rect(pos, m_tileSize);
379 for (int i = 0; i < oldTiles.size(); i++) {
380 if (oldTiles[i]->rect() == rect) {
381 tile = oldTiles.takeAt(i);
390 m_tiles.append(tile);
394 qDeleteAll(oldTiles);
396 m_dirtyCanvas = false;
400 void QQuickContext2DTexture::clearTiles()
406 QQuickContext2DFBOTexture::QQuickContext2DFBOTexture()
407 : QQuickContext2DTexture()
409 , m_multisampledFbo(0)
412 m_threadRendering = false;
415 QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture()
418 delete m_multisampledFbo;
419 delete m_paint_device;
422 bool QQuickContext2DFBOTexture::setCanvasSize(const QSize &size)
424 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
425 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
427 if (m_canvasSize != s) {
429 m_dirtyCanvas = true;
435 bool QQuickContext2DFBOTexture::setTileSize(const QSize &size)
437 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
438 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
439 if (m_tileSize != s) {
441 m_dirtyCanvas = true;
447 bool QQuickContext2DFBOTexture::setCanvasWindow(const QRect& canvasWindow)
449 QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().width()))
450 , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().height())));
453 bool doChanged = false;
454 if (m_fboSize != s) {
459 if (m_canvasWindow != canvasWindow)
460 m_canvasWindow = canvasWindow;
465 void QQuickContext2DFBOTexture::bind()
467 glBindTexture(GL_TEXTURE_2D, textureId());
471 QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const
475 , qreal(m_canvasWindow.width()) / m_fboSize.width()
476 , qreal(m_canvasWindow.height()) / m_fboSize.height());
480 int QQuickContext2DFBOTexture::textureId() const
482 return m_fbo? m_fbo->texture() : 0;
486 bool QQuickContext2DFBOTexture::updateTexture()
488 if (!m_context->buffer()->isEmpty()) {
492 bool textureUpdated = m_dirtyTexture;
494 m_dirtyTexture = false;
498 m_condition.wakeOne();
499 m_doGrabImage = false;
501 return textureUpdated;
504 QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const
506 return new QQuickContext2DFBOTile();
509 void QQuickContext2DFBOTexture::grabImage()
512 m_grabedImage = m_fbo->toImage();
516 bool QQuickContext2DFBOTexture::doMultisampling() const
518 static bool extensionsChecked = false;
519 static bool multisamplingSupported = false;
521 if (!extensionsChecked) {
522 QList<QByteArray> extensions = QByteArray((const char *)glGetString(GL_EXTENSIONS)).split(' ');
523 multisamplingSupported = extensions.contains("GL_EXT_framebuffer_multisample")
524 && extensions.contains("GL_EXT_framebuffer_blit");
525 extensionsChecked = true;
528 return multisamplingSupported && m_smooth;
531 QImage QQuickContext2DFBOTexture::toImage(const QRectF& region)
533 #define QML_CONTEXT2D_WAIT_MAX 5000
535 m_doGrabImage = true;
541 bool ok = m_condition.wait(&m_mutex, QML_CONTEXT2D_WAIT_MAX);
546 if (region.isValid())
547 grabbed = m_grabedImage.copy(region.toRect());
549 grabbed = m_grabedImage;
550 m_grabedImage = QImage();
554 void QQuickContext2DFBOTexture::compositeTile(QQuickContext2DTile* tile)
556 QQuickContext2DFBOTile* t = static_cast<QQuickContext2DFBOTile*>(tile);
557 QRect target = t->rect().intersect(m_canvasWindow);
558 if (target.isValid()) {
559 QRect source = target;
561 source.moveTo(source.topLeft() - t->rect().topLeft());
562 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
564 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
567 QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const
569 return QQuickCanvasItem::FramebufferObject;
571 QPaintDevice* QQuickContext2DFBOTexture::beginPainting()
573 QQuickContext2DTexture::beginPainting();
575 if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
577 delete m_multisampledFbo;
579 m_multisampledFbo = 0;
581 } else if (!m_fbo || m_fbo->size() != m_fboSize) {
583 delete m_multisampledFbo;
584 if (doMultisampling()) {
586 QOpenGLFramebufferObjectFormat format;
587 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
588 format.setSamples(8);
589 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
592 QOpenGLFramebufferObjectFormat format;
593 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
594 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
597 QOpenGLFramebufferObjectFormat format;
598 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
600 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
604 if (doMultisampling())
605 m_multisampledFbo->bind();
610 if (!m_paint_device) {
611 QOpenGLPaintDevice *gl_device = new QOpenGLPaintDevice(m_fbo->size());
612 gl_device->setPaintFlipped(true);
613 m_paint_device = gl_device;
616 return m_paint_device;
619 void QQuickContext2DFBOTexture::endPainting()
621 QQuickContext2DTexture::endPainting();
622 if (m_multisampledFbo) {
623 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo);
624 m_multisampledFbo->release();
628 void qt_quit_context2d_render_thread()
630 QThread* thread = globalCanvasThreadRenderInstance();
632 if (thread->isRunning()) {
638 QQuickContext2DImageTexture::QQuickContext2DImageTexture(bool threadRendering)
639 : QQuickContext2DTexture()
640 , m_texture(new QSGPlainTexture())
642 m_texture->setOwnsTexture(true);
643 m_texture->setHasMipmaps(false);
645 m_threadRendering = threadRendering;
647 if (m_threadRendering) {
648 QThread* thread = globalCanvasThreadRenderInstance();
649 moveToThread(thread);
651 if (!thread->isRunning()) {
652 qAddPostRoutine(qt_quit_context2d_render_thread);
658 QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
663 int QQuickContext2DImageTexture::textureId() const
665 return m_texture->textureId();
668 void QQuickContext2DImageTexture::lock()
670 if (m_threadRendering)
673 void QQuickContext2DImageTexture::unlock()
675 if (m_threadRendering)
679 void QQuickContext2DImageTexture::wait()
681 if (m_threadRendering)
682 m_waitCondition.wait(&m_mutex);
685 void QQuickContext2DImageTexture::wake()
687 if (m_threadRendering)
688 m_waitCondition.wakeOne();
691 bool QQuickContext2DImageTexture::supportDirectRendering() const
693 return !m_threadRendering;
696 QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
698 return QQuickCanvasItem::Image;
701 void QQuickContext2DImageTexture::bind()
706 bool QQuickContext2DImageTexture::updateTexture()
709 bool textureUpdated = m_dirtyTexture;
710 if (m_dirtyTexture) {
711 m_texture->setImage(m_image);
712 m_dirtyTexture = false;
715 return textureUpdated;
718 QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
720 return new QQuickContext2DImageTile();
723 void QQuickContext2DImageTexture::grabImage(const QRect& r)
725 m_doGrabImage = true;
727 m_doGrabImage = false;
728 m_grabedImage = m_image.copy(r);
731 QImage QQuickContext2DImageTexture::toImage(const QRectF& region)
733 QRect r = region.isValid() ? region.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
734 if (threadRendering()) {
736 QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
738 QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
740 QImage image = m_grabedImage;
741 m_grabedImage = QImage();
745 QPaintDevice* QQuickContext2DImageTexture::beginPainting()
747 QQuickContext2DTexture::beginPainting();
749 if (m_canvasWindow.size().isEmpty())
753 if (m_image.size() != m_canvasWindow.size()) {
754 m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
755 m_image.fill(0x00000000);
761 void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
763 Q_ASSERT(!tile->dirty());
764 QQuickContext2DImageTile* t = static_cast<QQuickContext2DImageTile*>(tile);
765 QRect target = t->rect().intersect(m_canvasWindow);
766 if (target.isValid()) {
767 QRect source = target;
768 source.moveTo(source.topLeft() - t->rect().topLeft());
769 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
772 m_painter.begin(&m_image);
773 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
774 m_painter.drawImage(target, t->image(), source);