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>
56 #define QT_MINIMUM_FBO_SIZE 64
58 static inline int qt_next_power_of_two(int v)
70 struct GLAcquireContext {
71 GLAcquireContext(QOpenGLContext *c, QSurface *s):ctx(c) {
78 qWarning() << "Unable to create GL context";
79 else if (!ctx->makeCurrent(s))
80 qWarning() << "Can't make current GL context";
90 QQuickContext2DTexture::QQuickContext2DTexture()
93 , m_dirtyCanvas(false)
94 , m_canvasWindowChanged(false)
95 , m_dirtyTexture(false)
97 , m_antialiasing(false)
98 , m_tiledCanvas(false)
103 QQuickContext2DTexture::~QQuickContext2DTexture()
108 QSize QQuickContext2DTexture::textureSize() const
110 return m_canvasWindow.size();
113 void QQuickContext2DTexture::markDirtyTexture()
115 m_dirtyTexture = true;
117 emit textureChanged();
120 bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
122 if (m_canvasSize != size) {
124 m_dirtyCanvas = true;
130 bool QQuickContext2DTexture::setTileSize(const QSize &size)
132 if (m_tileSize != size) {
134 m_dirtyCanvas = true;
140 void QQuickContext2DTexture::setSmooth(bool smooth)
145 void QQuickContext2DTexture::setAntialiasing(bool antialiasing)
147 m_antialiasing = antialiasing;
150 void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
153 m_context = (QQuickContext2D*)item->rawContext(); // FIXME
154 m_state = m_context->state;
157 bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
159 if (m_canvasWindow != r) {
161 m_canvasWindowChanged = true;
167 bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
169 bool doDirty = false;
171 foreach (QQuickContext2DTile* t, m_tiles) {
172 bool dirty = t->rect().intersected(r).isValid();
178 doDirty = m_canvasWindow.intersected(r).isValid();
183 void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing)
186 if (ts.width() > canvasSize.width())
187 ts.setWidth(canvasSize.width());
189 if (ts.height() > canvasSize.height())
190 ts.setHeight(canvasSize.height());
192 setCanvasSize(canvasSize);
194 setCanvasWindow(canvasWindow);
196 if (canvasSize == canvasWindow.size()) {
197 m_tiledCanvas = false;
198 m_dirtyCanvas = false;
200 m_tiledCanvas = true;
203 if (dirtyRect.isValid())
204 setDirtyRect(dirtyRect);
207 setAntialiasing(antialiasing);
210 void QQuickContext2DTexture::paintWithoutTiles(QQuickContext2DCommandBuffer *ccb)
212 if (!ccb || ccb->isEmpty())
215 QPaintDevice* device = beginPainting();
224 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing, true);
226 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing, false);
229 p.setRenderHint(QPainter::SmoothPixmapTransform, true);
231 p.setRenderHint(QPainter::SmoothPixmapTransform, false);
233 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
235 ccb->replay(&p, m_state);
240 bool QQuickContext2DTexture::canvasDestroyed()
245 void QQuickContext2DTexture::paint(QQuickContext2DCommandBuffer *ccb)
247 if (canvasDestroyed()) {
252 GLAcquireContext currentContext(m_context->glContext(), m_context->surface());
254 if (!m_tiledCanvas) {
255 paintWithoutTiles(ccb);
260 QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
261 if (!tiledRegion.isEmpty()) {
263 foreach (QQuickContext2DTile* tile, m_tiles) {
265 if (dirtyRect.isEmpty())
266 dirtyRect = tile->rect();
268 dirtyRect |= tile->rect();
272 if (beginPainting()) {
273 QQuickContext2D::State oldState = m_state;
274 foreach (QQuickContext2DTile* tile, m_tiles) {
276 ccb->replay(tile->createPainter(m_smooth, m_antialiasing), oldState);
277 tile->drawFinished();
278 tile->markDirty(false);
290 QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
292 if (window.isEmpty())
295 const int tw = tileSize.width();
296 const int th = tileSize.height();
297 const int h1 = window.left() / tw;
298 const int v1 = window.top() / th;
300 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
301 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
303 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
306 QRect QQuickContext2DTexture::createTiles(const QRect& window)
308 QList<QQuickContext2DTile*> oldTiles = m_tiles;
311 if (window.isEmpty()) {
312 m_dirtyCanvas = false;
316 QRect r = tiledRect(window, adjustedTileSize(m_tileSize));
318 const int tw = m_tileSize.width();
319 const int th = m_tileSize.height();
320 const int h1 = window.left() / tw;
321 const int v1 = window.top() / th;
324 const int htiles = r.width() / tw;
325 const int vtiles = r.height() / th;
327 for (int yy = 0; yy < vtiles; ++yy) {
328 for (int xx = 0; xx < htiles; ++xx) {
332 QQuickContext2DTile* tile = 0;
334 QPoint pos(ht * tw, vt * th);
335 QRect rect(pos, m_tileSize);
337 for (int i = 0; i < oldTiles.size(); i++) {
338 if (oldTiles[i]->rect() == rect) {
339 tile = oldTiles.takeAt(i);
348 m_tiles.append(tile);
352 qDeleteAll(oldTiles);
354 m_dirtyCanvas = false;
358 void QQuickContext2DTexture::clearTiles()
364 QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts)
369 static inline QSize npotAdjustedSize(const QSize &size)
371 static bool checked = false;
372 static bool npotSupported = false;
375 npotSupported = QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
380 return QSize(qMax(QT_MINIMUM_FBO_SIZE, size.width()),
381 qMax(QT_MINIMUM_FBO_SIZE, size.height()));
384 return QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width())),
385 qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
388 QQuickContext2DFBOTexture::QQuickContext2DFBOTexture()
389 : QQuickContext2DTexture()
391 , m_multisampledFbo(0)
396 QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture()
398 if (m_multisampledFbo)
399 m_multisampledFbo->release();
404 delete m_multisampledFbo;
405 delete m_paint_device;
408 QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts)
410 return npotAdjustedSize(ts);
413 void QQuickContext2DFBOTexture::bind()
415 glBindTexture(GL_TEXTURE_2D, textureId());
419 QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const
423 , qreal(m_canvasWindow.width()) / m_fboSize.width()
424 , qreal(m_canvasWindow.height()) / m_fboSize.height());
428 int QQuickContext2DFBOTexture::textureId() const
430 return m_fbo? m_fbo->texture() : 0;
434 bool QQuickContext2DFBOTexture::updateTexture()
436 bool textureUpdated = m_dirtyTexture;
437 m_dirtyTexture = false;
438 return textureUpdated;
441 QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const
443 return new QQuickContext2DFBOTile();
446 bool QQuickContext2DFBOTexture::doMultisampling() const
448 static bool extensionsChecked = false;
449 static bool multisamplingSupported = false;
451 if (!extensionsChecked) {
452 QList<QByteArray> extensions = QByteArray((const char *)glGetString(GL_EXTENSIONS)).split(' ');
453 multisamplingSupported = extensions.contains("GL_EXT_framebuffer_multisample")
454 && extensions.contains("GL_EXT_framebuffer_blit");
455 extensionsChecked = true;
458 return multisamplingSupported && m_antialiasing;
461 void QQuickContext2DFBOTexture::grabImage(const QRectF& rf)
463 Q_ASSERT(rf.isValid());
466 m_context->setGrabbedImage(QImage());
472 GLAcquireContext ctx(m_context->glContext(), m_context->surface());
473 grabbed = m_fbo->toImage().mirrored().copy(rf.toRect());
476 m_context->setGrabbedImage(grabbed);
479 void QQuickContext2DFBOTexture::compositeTile(QQuickContext2DTile* tile)
481 QQuickContext2DFBOTile* t = static_cast<QQuickContext2DFBOTile*>(tile);
482 QRect target = t->rect().intersected(m_canvasWindow);
483 if (target.isValid()) {
484 QRect source = target;
486 source.moveTo(source.topLeft() - t->rect().topLeft());
487 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
489 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
493 QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const
495 return QQuickCanvasItem::FramebufferObject;
498 QPaintDevice* QQuickContext2DFBOTexture::beginPainting()
500 QQuickContext2DTexture::beginPainting();
502 if (m_canvasWindow.size().isEmpty()) {
504 delete m_multisampledFbo;
505 delete m_paint_device;
507 m_multisampledFbo = 0;
510 } else if (!m_fbo || m_canvasWindowChanged) {
512 delete m_multisampledFbo;
513 delete m_paint_device;
516 m_fboSize = npotAdjustedSize(m_canvasWindow.size());
517 m_canvasWindowChanged = false;
519 if (doMultisampling()) {
521 QOpenGLFramebufferObjectFormat format;
522 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
523 format.setSamples(8);
524 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
527 QOpenGLFramebufferObjectFormat format;
528 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
529 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
532 QOpenGLFramebufferObjectFormat format;
533 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
535 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
539 if (doMultisampling())
540 m_multisampledFbo->bind();
545 if (!m_paint_device) {
546 QOpenGLPaintDevice *gl_device = new QOpenGLPaintDevice(m_fbo->size());
547 gl_device->setPaintFlipped(true);
548 gl_device->setSize(m_fbo->size());
549 m_paint_device = gl_device;
552 return m_paint_device;
555 void QQuickContext2DFBOTexture::endPainting()
557 QQuickContext2DTexture::endPainting();
558 if (m_multisampledFbo)
559 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo);
562 QQuickContext2DImageTexture::QQuickContext2DImageTexture()
563 : QQuickContext2DTexture()
568 QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
570 if (m_texture && m_texture->thread() != QThread::currentThread())
571 m_texture->deleteLater();
576 int QQuickContext2DImageTexture::textureId() const
578 return imageTexture()->textureId();
581 QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
583 return QQuickCanvasItem::Image;
586 void QQuickContext2DImageTexture::bind()
588 imageTexture()->bind();
591 bool QQuickContext2DImageTexture::updateTexture()
593 bool textureUpdated = m_dirtyTexture;
594 if (m_dirtyTexture) {
595 imageTexture()->setImage(m_image);
596 m_dirtyTexture = false;
598 return textureUpdated;
601 QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
603 return new QQuickContext2DImageTile();
606 void QQuickContext2DImageTexture::grabImage(const QRectF& rf)
608 Q_ASSERT(rf.isValid());
610 QImage grabbed = m_image.copy(rf.toRect());
611 m_context->setGrabbedImage(grabbed);
614 QSGPlainTexture *QQuickContext2DImageTexture::imageTexture() const
617 QQuickContext2DImageTexture *that = const_cast<QQuickContext2DImageTexture *>(this);
618 that->m_texture = new QSGPlainTexture;
619 that->m_texture->setOwnsTexture(true);
620 that->m_texture->setHasMipmaps(false);
625 QPaintDevice* QQuickContext2DImageTexture::beginPainting()
627 QQuickContext2DTexture::beginPainting();
629 if (m_canvasWindow.size().isEmpty())
632 if (m_canvasWindowChanged) {
633 m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
634 m_image.fill(0x00000000);
635 m_canvasWindowChanged = false;
641 void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
643 Q_ASSERT(!tile->dirty());
644 QQuickContext2DImageTile* t = static_cast<QQuickContext2DImageTile*>(tile);
645 QRect target = t->rect().intersected(m_canvasWindow);
646 if (target.isValid()) {
647 QRect source = target;
648 source.moveTo(source.topLeft() - t->rect().topLeft());
649 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
651 m_painter.begin(&m_image);
652 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
653 m_painter.drawImage(target, t->image(), source);