1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** 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>
57 #define QT_MINIMUM_FBO_SIZE 64
59 static inline int qt_next_power_of_two(int v)
71 struct GLAcquireContext {
72 GLAcquireContext(QOpenGLContext *c, QSurface *s):ctx(c) {
79 qWarning() << "Unable to create GL context";
80 else if (!ctx->makeCurrent(s))
81 qWarning() << "Can't make current GL context";
91 Q_GLOBAL_STATIC(QThread, globalCanvasThreadRenderInstance)
94 QQuickContext2DTexture::QQuickContext2DTexture()
97 , m_dirtyCanvas(false)
98 , m_canvasWindowChanged(false)
99 , m_dirtyTexture(false)
100 , m_threadRendering(false)
102 , m_tiledCanvas(false)
103 , m_doGrabImage(false)
108 QQuickContext2DTexture::~QQuickContext2DTexture()
113 QSize QQuickContext2DTexture::textureSize() const
115 return m_canvasWindow.size();
118 void QQuickContext2DTexture::markDirtyTexture()
120 const bool inGrab = m_doGrabImage;
122 m_dirtyTexture = true;
125 emit textureChanged();
128 bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
130 if (m_canvasSize != size) {
132 m_dirtyCanvas = true;
138 bool QQuickContext2DTexture::setTileSize(const QSize &size)
140 if (m_tileSize != size) {
142 m_dirtyCanvas = true;
148 void QQuickContext2DTexture::setSmooth(bool smooth)
153 void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
156 m_context = (QQuickContext2D*)item->rawContext(); // FIXME
157 m_state = m_context->state;
160 bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
162 if (m_canvasWindow != r) {
164 m_canvasWindowChanged = true;
170 bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
172 bool doDirty = false;
174 foreach (QQuickContext2DTile* t, m_tiles) {
175 bool dirty = t->rect().intersected(r).isValid();
181 doDirty = m_canvasWindow.intersected(r).isValid();
186 void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
191 if (ts.width() > canvasSize.width())
192 ts.setWidth(canvasSize.width());
194 if (ts.height() > canvasSize.height())
195 ts.setHeight(canvasSize.height());
197 setCanvasSize(canvasSize);
199 setCanvasWindow(canvasWindow);
201 if (canvasSize == canvasWindow.size()) {
202 m_tiledCanvas = false;
203 m_dirtyCanvas = false;
205 m_tiledCanvas = true;
208 if (dirtyRect.isValid())
209 setDirtyRect(dirtyRect);
216 void QQuickContext2DTexture::paintWithoutTiles()
218 QQuickContext2DCommandBuffer* ccb = m_context->nextBuffer();
220 if (!ccb || ccb->isEmpty())
223 QPaintDevice* device = beginPainting();
233 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
234 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
236 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
237 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false);
238 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
240 ccb->replay(&p, m_state);
249 bool QQuickContext2DTexture::canvasDestroyed()
251 bool noCanvas = false;
253 noCanvas = m_item == 0;
258 void QQuickContext2DTexture::paint()
260 if (canvasDestroyed())
263 GLAcquireContext currentContext(m_context->glContext(), m_context->surface());
265 if (m_threadRendering && QThread::currentThread() != globalCanvasThreadRenderInstance()) {
266 Q_ASSERT(thread() == globalCanvasThreadRenderInstance());
267 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
271 if (!m_tiledCanvas) {
275 QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
278 if (!tiledRegion.isEmpty()) {
279 if (m_threadRendering && !m_doGrabImage) {
283 foreach (QQuickContext2DTile* tile, m_tiles) {
285 if (dirtyRect.isEmpty())
286 dirtyRect = tile->rect();
288 dirtyRect |= tile->rect();
294 if (beginPainting()) {
295 QQuickContext2D::State oldState = m_state;
296 QQuickContext2DCommandBuffer* ccb = m_context->nextBuffer();
297 if (!ccb || ccb->isEmpty()) {
302 foreach (QQuickContext2DTile* tile, m_tiles) {
303 bool dirtyTile = false, dirtyCanvas = false, smooth = false;
306 dirtyTile = tile->dirty();
308 dirtyCanvas = m_dirtyCanvas;
311 //canvas size or tile size may change during painting tiles
313 if (m_threadRendering)
314 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
317 } else if (dirtyTile) {
318 ccb->replay(tile->createPainter(smooth), oldState);
319 tile->drawFinished();
321 tile->markDirty(false);
337 QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
339 if (window.isEmpty())
342 const int tw = tileSize.width();
343 const int th = tileSize.height();
344 const int h1 = window.left() / tw;
345 const int v1 = window.top() / th;
347 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
348 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
350 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
353 QRect QQuickContext2DTexture::createTiles(const QRect& window)
355 QList<QQuickContext2DTile*> oldTiles = m_tiles;
358 if (window.isEmpty()) {
359 m_dirtyCanvas = false;
363 QRect r = tiledRect(window, adjustedTileSize(m_tileSize));
365 const int tw = m_tileSize.width();
366 const int th = m_tileSize.height();
367 const int h1 = window.left() / tw;
368 const int v1 = window.top() / th;
371 const int htiles = r.width() / tw;
372 const int vtiles = r.height() / th;
374 for (int yy = 0; yy < vtiles; ++yy) {
375 for (int xx = 0; xx < htiles; ++xx) {
379 QQuickContext2DTile* tile = 0;
381 QPoint pos(ht * tw, vt * th);
382 QRect rect(pos, m_tileSize);
384 for (int i = 0; i < oldTiles.size(); i++) {
385 if (oldTiles[i]->rect() == rect) {
386 tile = oldTiles.takeAt(i);
395 m_tiles.append(tile);
399 qDeleteAll(oldTiles);
401 m_dirtyCanvas = false;
405 void QQuickContext2DTexture::clearTiles()
411 QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts)
416 static inline QSize npotAdjustedSize(const QSize &size)
418 static bool checked = false;
419 static bool npotSupported = false;
422 npotSupported = QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
427 return QSize(qMax(QT_MINIMUM_FBO_SIZE, size.width()),
428 qMax(QT_MINIMUM_FBO_SIZE, size.height()));
431 return QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width())),
432 qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
435 QQuickContext2DFBOTexture::QQuickContext2DFBOTexture()
436 : QQuickContext2DTexture()
438 , m_multisampledFbo(0)
441 m_threadRendering = false;
444 QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture()
447 delete m_multisampledFbo;
448 delete m_paint_device;
451 QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts)
453 return npotAdjustedSize(ts);
456 void QQuickContext2DFBOTexture::bind()
458 glBindTexture(GL_TEXTURE_2D, textureId());
462 QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const
466 , qreal(m_canvasWindow.width()) / m_fboSize.width()
467 , qreal(m_canvasWindow.height()) / m_fboSize.height());
471 int QQuickContext2DFBOTexture::textureId() const
473 return m_fbo? m_fbo->texture() : 0;
477 bool QQuickContext2DFBOTexture::updateTexture()
479 bool textureUpdated = m_dirtyTexture;
481 m_dirtyTexture = false;
485 m_condition.wakeOne();
486 m_doGrabImage = false;
488 return textureUpdated;
491 QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const
493 return new QQuickContext2DFBOTile();
496 void QQuickContext2DFBOTexture::grabImage()
499 m_grabedImage = m_fbo->toImage();
503 bool QQuickContext2DFBOTexture::doMultisampling() const
505 static bool extensionsChecked = false;
506 static bool multisamplingSupported = false;
508 if (!extensionsChecked) {
509 QList<QByteArray> extensions = QByteArray((const char *)glGetString(GL_EXTENSIONS)).split(' ');
510 multisamplingSupported = extensions.contains("GL_EXT_framebuffer_multisample")
511 && extensions.contains("GL_EXT_framebuffer_blit");
512 extensionsChecked = true;
515 return multisamplingSupported && m_smooth;
518 QImage QQuickContext2DFBOTexture::toImage(const QRectF& region)
520 const unsigned long context2d_wait_max = 5000;
522 m_doGrabImage = true;
523 if (m_item) // forces a call to updatePaintNode (repaints)
528 bool ok = m_condition.wait(&m_mutex, context2d_wait_max);
533 if (region.isValid())
534 grabbed = m_grabedImage.copy(region.toRect());
536 grabbed = m_grabedImage;
537 m_grabedImage = QImage();
541 void QQuickContext2DFBOTexture::compositeTile(QQuickContext2DTile* tile)
543 QQuickContext2DFBOTile* t = static_cast<QQuickContext2DFBOTile*>(tile);
544 QRect target = t->rect().intersected(m_canvasWindow);
545 if (target.isValid()) {
546 QRect source = target;
548 source.moveTo(source.topLeft() - t->rect().topLeft());
549 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
551 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
554 QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const
556 return QQuickCanvasItem::FramebufferObject;
558 QPaintDevice* QQuickContext2DFBOTexture::beginPainting()
560 QQuickContext2DTexture::beginPainting();
562 if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
564 delete m_multisampledFbo;
565 delete m_paint_device;
567 m_multisampledFbo = 0;
570 } else if (!m_fbo || m_canvasWindowChanged) {
572 delete m_multisampledFbo;
573 delete m_paint_device;
576 m_fboSize = npotAdjustedSize(m_canvasWindow.size());
577 m_canvasWindowChanged = false;
579 if (doMultisampling()) {
581 QOpenGLFramebufferObjectFormat format;
582 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
583 format.setSamples(8);
584 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
587 QOpenGLFramebufferObjectFormat format;
588 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
589 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
592 QOpenGLFramebufferObjectFormat format;
593 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
595 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
599 if (doMultisampling())
600 m_multisampledFbo->bind();
605 if (!m_paint_device) {
606 QOpenGLPaintDevice *gl_device = new QOpenGLPaintDevice(m_fbo->size());
607 gl_device->setPaintFlipped(true);
608 gl_device->setSize(m_fbo->size());
609 m_paint_device = gl_device;
612 return m_paint_device;
615 void QQuickContext2DFBOTexture::endPainting()
617 QQuickContext2DTexture::endPainting();
618 if (m_multisampledFbo) {
619 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo);
620 m_multisampledFbo->release();
624 void qt_quit_context2d_render_thread()
626 QThread* thread = globalCanvasThreadRenderInstance();
628 if (thread->isRunning()) {
634 QQuickContext2DImageTexture::QQuickContext2DImageTexture(bool threadRendering)
635 : QQuickContext2DTexture()
636 , m_texture(new QSGPlainTexture())
638 m_texture->setOwnsTexture(true);
639 m_texture->setHasMipmaps(false);
641 m_threadRendering = threadRendering;
643 if (m_threadRendering) {
644 QThread* thread = globalCanvasThreadRenderInstance();
645 moveToThread(thread);
647 if (!thread->isRunning()) {
648 qAddPostRoutine(qt_quit_context2d_render_thread); // XXX: change this method
654 QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
659 int QQuickContext2DImageTexture::textureId() const
661 return m_texture->textureId();
664 void QQuickContext2DImageTexture::lock()
666 if (m_threadRendering)
669 void QQuickContext2DImageTexture::unlock()
671 if (m_threadRendering)
675 void QQuickContext2DImageTexture::wait()
677 if (m_threadRendering)
678 m_waitCondition.wait(&m_mutex);
681 void QQuickContext2DImageTexture::wake()
683 if (m_threadRendering)
684 m_waitCondition.wakeOne();
687 bool QQuickContext2DImageTexture::supportDirectRendering() const
689 return !m_threadRendering;
692 QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
694 return QQuickCanvasItem::Image;
697 void QQuickContext2DImageTexture::bind()
702 bool QQuickContext2DImageTexture::updateTexture()
704 bool textureUpdated = m_dirtyTexture;
705 if (m_dirtyTexture) {
706 m_texture->setImage(m_image);
707 m_dirtyTexture = false;
709 return textureUpdated;
712 QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
714 return new QQuickContext2DImageTile();
717 void QQuickContext2DImageTexture::grabImage(const QRect& r)
719 m_doGrabImage = true;
721 m_doGrabImage = false;
722 m_grabedImage = m_image.copy(r);
725 QImage QQuickContext2DImageTexture::toImage(const QRectF& rect)
727 QRect r = rect.isValid() ? rect.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
728 if (threadRendering()) {
730 QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
732 QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
734 QImage image = m_grabedImage;
735 m_grabedImage = QImage();
739 QPaintDevice* QQuickContext2DImageTexture::beginPainting()
741 QQuickContext2DTexture::beginPainting();
743 if (m_canvasWindow.size().isEmpty())
746 if (m_canvasWindowChanged) {
747 m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
748 m_image.fill(0x00000000);
749 m_canvasWindowChanged = false;
755 void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
757 Q_ASSERT(!tile->dirty());
758 QQuickContext2DImageTile* t = static_cast<QQuickContext2DImageTile*>(tile);
759 QRect target = t->rect().intersected(m_canvasWindow);
760 if (target.isValid()) {
761 QRect source = target;
762 source.moveTo(source.topLeft() - t->rect().topLeft());
763 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
766 m_painter.begin(&m_image);
767 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
768 m_painter.drawImage(target, t->image(), source);