Fix resizing of Canvas item that use FBO as renderTargets.
[profile/ivi/qtdeclarative.git] / src / quick / items / context2d / qquickcontext2dtexture.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
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>
49
50 #include <QOpenGLFramebufferObject>
51 #include <QOpenGLFramebufferObjectFormat>
52 #include <QtCore/QThread>
53
54
55 QT_BEGIN_NAMESPACE
56
57 #define QT_MINIMUM_FBO_SIZE 64
58
59 static inline int qt_next_power_of_two(int v)
60 {
61     v--;
62     v |= v >> 1;
63     v |= v >> 2;
64     v |= v >> 4;
65     v |= v >> 8;
66     v |= v >> 16;
67     ++v;
68     return v;
69 }
70
71 struct GLAcquireContext {
72     GLAcquireContext(QOpenGLContext *c, QSurface *s):ctx(c) {
73         if (ctx) {
74             Q_ASSERT(s);
75             if (!ctx->isValid())
76                 ctx->create();
77
78             if (!ctx->isValid())
79                 qWarning() << "Unable to create GL context";
80             else if (!ctx->makeCurrent(s))
81                 qWarning() << "Can't make current GL context";
82         }
83     }
84     ~GLAcquireContext() {
85         if (ctx)
86             ctx->doneCurrent();
87     }
88     QOpenGLContext *ctx;
89 };
90
91 Q_GLOBAL_STATIC(QThread, globalCanvasThreadRenderInstance)
92
93
94 QQuickContext2DTexture::QQuickContext2DTexture()
95     : m_context(0)
96     , m_item(0)
97     , m_dirtyCanvas(false)
98     , m_canvasWindowChanged(false)
99     , m_dirtyTexture(false)
100     , m_threadRendering(false)
101     , m_smooth(false)
102     , m_tiledCanvas(false)
103     , m_doGrabImage(false)
104     , m_painting(false)
105 {
106 }
107
108 QQuickContext2DTexture::~QQuickContext2DTexture()
109 {
110    clearTiles();
111 }
112
113 QSize QQuickContext2DTexture::textureSize() const
114 {
115     return m_canvasWindow.size();
116 }
117
118 void QQuickContext2DTexture::markDirtyTexture()
119 {
120     const bool inGrab = m_doGrabImage;
121
122     m_dirtyTexture = true;
123     updateTexture();
124     if (!inGrab)
125         emit textureChanged();
126 }
127
128 bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
129 {
130     if (m_canvasSize != size) {
131         m_canvasSize = size;
132         m_dirtyCanvas = true;
133         return true;
134     }
135     return false;
136 }
137
138 bool QQuickContext2DTexture::setTileSize(const QSize &size)
139 {
140     if (m_tileSize != size) {
141         m_tileSize = size;
142         m_dirtyCanvas = true;
143         return true;
144     }
145     return false;
146 }
147
148 void QQuickContext2DTexture::setSmooth(bool smooth)
149 {
150     m_smooth = smooth;
151 }
152
153 void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
154 {
155     m_item = item;
156     m_context = (QQuickContext2D*)item->rawContext(); // FIXME
157     m_state = m_context->state;
158 }
159
160 bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
161 {
162     if (m_canvasWindow != r) {
163         m_canvasWindow = r;
164         m_canvasWindowChanged = true;
165         return true;
166     }
167     return false;
168 }
169
170 bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
171 {
172     bool doDirty = false;
173     if (m_tiledCanvas) {
174         foreach (QQuickContext2DTile* t, m_tiles) {
175             bool dirty = t->rect().intersected(r).isValid();
176             t->markDirty(dirty);
177             if (dirty)
178                 doDirty = true;
179         }
180     } else {
181         doDirty = m_canvasWindow.intersected(r).isValid();
182     }
183     return doDirty;
184 }
185
186 void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
187 {
188     lock();
189
190     QSize ts = tileSize;
191     if (ts.width() > canvasSize.width())
192         ts.setWidth(canvasSize.width());
193
194     if (ts.height() > canvasSize.height())
195         ts.setHeight(canvasSize.height());
196
197     setCanvasSize(canvasSize);
198     setTileSize(ts);
199     setCanvasWindow(canvasWindow);
200
201     if (canvasSize == canvasWindow.size()) {
202         m_tiledCanvas = false;
203         m_dirtyCanvas = false;
204     } else {
205         m_tiledCanvas = true;
206     }
207
208     if (dirtyRect.isValid())
209         setDirtyRect(dirtyRect);
210
211     setSmooth(smooth);
212
213     unlock();
214 }
215
216 void QQuickContext2DTexture::paintWithoutTiles()
217 {
218     QQuickContext2DCommandBuffer* ccb = m_context->nextBuffer();
219
220     if (!ccb || ccb->isEmpty())
221         return;
222
223     QPaintDevice* device = beginPainting();
224     if (!device) {
225         delete ccb;
226         endPainting();
227         return;
228     }
229
230     QPainter p;
231     p.begin(device);
232     if (m_smooth)
233         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
234                                | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
235     else
236         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
237                                  | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false);
238     p.setCompositionMode(QPainter::CompositionMode_SourceOver);
239
240     ccb->replay(&p, m_state);
241     ccb->clear();
242     delete ccb;
243
244     endPainting();
245
246     markDirtyTexture();
247 }
248
249 bool QQuickContext2DTexture::canvasDestroyed()
250 {
251     bool noCanvas = false;
252     lock();
253     noCanvas = m_item == 0;
254     unlock();
255     return noCanvas;
256 }
257
258 void QQuickContext2DTexture::paint()
259 {
260     if (canvasDestroyed())
261         return;
262
263     GLAcquireContext currentContext(m_context->glContext(), m_context->surface());
264
265     if (m_threadRendering && QThread::currentThread() != globalCanvasThreadRenderInstance()) {
266         Q_ASSERT(thread() == globalCanvasThreadRenderInstance());
267         QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
268         return;
269     }
270
271     if (!m_tiledCanvas) {
272         paintWithoutTiles();
273     } else {
274         lock();
275         QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
276         unlock();
277
278         if (!tiledRegion.isEmpty()) {
279             if (m_threadRendering && !m_doGrabImage) {
280                 QRect dirtyRect;
281
282                 lock();
283                 foreach (QQuickContext2DTile* tile, m_tiles) {
284                     if (tile->dirty()) {
285                         if (dirtyRect.isEmpty())
286                             dirtyRect = tile->rect();
287                         else
288                             dirtyRect |= tile->rect();
289                     }
290                 }
291                 unlock();
292             }
293
294             if (beginPainting()) {
295                 QQuickContext2D::State oldState = m_state;
296                 QQuickContext2DCommandBuffer* ccb = m_context->nextBuffer();
297                 if (!ccb || ccb->isEmpty()) {
298                     endPainting();
299                     delete ccb;
300                     return;
301                 }
302                 foreach (QQuickContext2DTile* tile, m_tiles) {
303                     bool dirtyTile = false, dirtyCanvas = false, smooth = false;
304
305                     lock();
306                     dirtyTile = tile->dirty();
307                     smooth = m_smooth;
308                     dirtyCanvas  = m_dirtyCanvas;
309                     unlock();
310
311                     //canvas size or tile size may change during painting tiles
312                     if (dirtyCanvas) {
313                         if (m_threadRendering)
314                             QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
315                         endPainting();
316                         return;
317                     } else if (dirtyTile) {
318                         ccb->replay(tile->createPainter(smooth), oldState);
319                         tile->drawFinished();
320                         lock();
321                         tile->markDirty(false);
322                         unlock();
323                     }
324
325                     compositeTile(tile);
326                 }
327                 ccb->clear();
328                 delete ccb;
329                 endPainting();
330                 m_state = oldState;
331                 markDirtyTexture();
332             }
333         }
334     }
335 }
336
337 QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
338 {
339     if (window.isEmpty())
340         return QRect();
341
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;
346
347     const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
348     const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
349
350     return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
351 }
352
353 QRect QQuickContext2DTexture::createTiles(const QRect& window)
354 {
355     QList<QQuickContext2DTile*> oldTiles = m_tiles;
356     m_tiles.clear();
357
358     if (window.isEmpty()) {
359         m_dirtyCanvas = false;
360         return QRect();
361     }
362
363     QRect r = tiledRect(window, adjustedTileSize(m_tileSize));
364
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;
369
370
371     const int htiles = r.width() / tw;
372     const int vtiles = r.height() / th;
373
374     for (int yy = 0; yy < vtiles; ++yy) {
375         for (int xx = 0; xx < htiles; ++xx) {
376             int ht = xx + h1;
377             int vt = yy + v1;
378
379             QQuickContext2DTile* tile = 0;
380
381             QPoint pos(ht * tw, vt * th);
382             QRect rect(pos, m_tileSize);
383
384             for (int i = 0; i < oldTiles.size(); i++) {
385                 if (oldTiles[i]->rect() == rect) {
386                     tile = oldTiles.takeAt(i);
387                     break;
388                 }
389             }
390
391             if (!tile)
392                 tile = createTile();
393
394             tile->setRect(rect);
395             m_tiles.append(tile);
396         }
397     }
398
399     qDeleteAll(oldTiles);
400
401     m_dirtyCanvas = false;
402     return r;
403 }
404
405 void QQuickContext2DTexture::clearTiles()
406 {
407     qDeleteAll(m_tiles);
408     m_tiles.clear();
409 }
410
411 QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts)
412 {
413     return ts;
414 }
415
416 static inline QSize npotAdjustedSize(const QSize &size)
417 {
418     static bool checked = false;
419     static bool npotSupported = false;
420
421     if (!checked) {
422         npotSupported = QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
423         checked = true;
424     }
425
426     if (npotSupported) {
427         return QSize(qMax(QT_MINIMUM_FBO_SIZE, size.width()),
428                      qMax(QT_MINIMUM_FBO_SIZE, size.height()));
429     }
430
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())));
433 }
434
435 QQuickContext2DFBOTexture::QQuickContext2DFBOTexture()
436     : QQuickContext2DTexture()
437     , m_fbo(0)
438     , m_multisampledFbo(0)
439     , m_paint_device(0)
440 {
441     m_threadRendering = false;
442 }
443
444 QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture()
445 {
446     delete m_fbo;
447     delete m_multisampledFbo;
448     delete m_paint_device;
449 }
450
451 QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts)
452 {
453     return npotAdjustedSize(ts);
454 }
455
456 void QQuickContext2DFBOTexture::bind()
457 {
458     glBindTexture(GL_TEXTURE_2D, textureId());
459     updateBindOptions();
460 }
461
462 QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const
463 {
464     return QRectF(0
465                 , 0
466                 , qreal(m_canvasWindow.width()) / m_fboSize.width()
467                 , qreal(m_canvasWindow.height()) / m_fboSize.height());
468 }
469
470
471 int QQuickContext2DFBOTexture::textureId() const
472 {
473     return m_fbo? m_fbo->texture() : 0;
474 }
475
476
477 bool QQuickContext2DFBOTexture::updateTexture()
478 {
479     bool textureUpdated = m_dirtyTexture;
480
481     m_dirtyTexture = false;
482
483     if (m_doGrabImage) {
484         grabImage();
485         m_condition.wakeOne();
486         m_doGrabImage = false;
487     }
488     return textureUpdated;
489 }
490
491 QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const
492 {
493     return new QQuickContext2DFBOTile();
494 }
495
496 void QQuickContext2DFBOTexture::grabImage()
497 {
498     if (m_fbo) {
499         m_grabedImage = m_fbo->toImage();
500     }
501 }
502
503 bool QQuickContext2DFBOTexture::doMultisampling() const
504 {
505     static bool extensionsChecked = false;
506     static bool multisamplingSupported = false;
507
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;
513     }
514
515     return multisamplingSupported  && m_smooth;
516 }
517
518 QImage QQuickContext2DFBOTexture::toImage(const QRectF& region)
519 {
520     const unsigned long context2d_wait_max = 5000;
521
522     m_doGrabImage = true;
523     if (m_item)                 // forces a call to updatePaintNode (repaints)
524         m_item->update();
525
526     QImage grabbed;
527     m_mutex.lock();
528     bool ok = m_condition.wait(&m_mutex, context2d_wait_max);
529
530     if (!ok)
531         grabbed = QImage();
532
533     if (region.isValid())
534         grabbed = m_grabedImage.copy(region.toRect());
535     else
536         grabbed = m_grabedImage;
537     m_grabedImage = QImage();
538     return grabbed;
539 }
540
541 void QQuickContext2DFBOTexture::compositeTile(QQuickContext2DTile* tile)
542 {
543     QQuickContext2DFBOTile* t = static_cast<QQuickContext2DFBOTile*>(tile);
544     QRect target = t->rect().intersected(m_canvasWindow);
545     if (target.isValid()) {
546         QRect source = target;
547
548         source.moveTo(source.topLeft() - t->rect().topLeft());
549         target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
550
551         QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
552     }
553 }
554 QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const
555 {
556     return QQuickCanvasItem::FramebufferObject;
557 }
558 QPaintDevice* QQuickContext2DFBOTexture::beginPainting()
559 {
560     QQuickContext2DTexture::beginPainting();
561
562     if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
563         delete m_fbo;
564         delete m_multisampledFbo;
565         delete m_paint_device;
566         m_fbo = 0;
567         m_multisampledFbo = 0;
568         m_paint_device = 0;
569         return 0;
570     } else if (!m_fbo || m_canvasWindowChanged) {
571         delete m_fbo;
572         delete m_multisampledFbo;
573         delete m_paint_device;
574         m_paint_device = 0;
575
576         m_fboSize = npotAdjustedSize(m_canvasWindow.size());
577         m_canvasWindowChanged = false;
578
579         if (doMultisampling()) {
580             {
581                 QOpenGLFramebufferObjectFormat format;
582                 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
583                 format.setSamples(8);
584                 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
585             }
586             {
587                 QOpenGLFramebufferObjectFormat format;
588                 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
589                 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
590             }
591         } else {
592             QOpenGLFramebufferObjectFormat format;
593             format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
594
595             m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
596         }
597     }
598
599     if (doMultisampling())
600         m_multisampledFbo->bind();
601     else
602         m_fbo->bind();
603
604
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;
610     }
611
612     return m_paint_device;
613 }
614
615 void QQuickContext2DFBOTexture::endPainting()
616 {
617     QQuickContext2DTexture::endPainting();
618     if (m_multisampledFbo) {
619         QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo);
620         m_multisampledFbo->release();
621     } else if (m_fbo)
622         m_fbo->release();
623 }
624 void qt_quit_context2d_render_thread()
625 {
626     QThread* thread = globalCanvasThreadRenderInstance();
627
628     if (thread->isRunning()) {
629         thread->exit(0);
630         thread->wait(1000);
631     }
632 }
633
634 QQuickContext2DImageTexture::QQuickContext2DImageTexture(bool threadRendering)
635     : QQuickContext2DTexture()
636     , m_texture(new QSGPlainTexture())
637 {
638     m_texture->setOwnsTexture(true);
639     m_texture->setHasMipmaps(false);
640
641     m_threadRendering = threadRendering;
642
643     if (m_threadRendering) {
644         QThread* thread = globalCanvasThreadRenderInstance();
645         moveToThread(thread);
646
647         if (!thread->isRunning()) {
648             qAddPostRoutine(qt_quit_context2d_render_thread); // XXX: change this method
649             thread->start();
650         }
651     }
652 }
653
654 QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
655 {
656     delete m_texture;
657 }
658
659 int QQuickContext2DImageTexture::textureId() const
660 {
661     return m_texture->textureId();
662 }
663
664 void QQuickContext2DImageTexture::lock()
665 {
666     if (m_threadRendering)
667         m_mutex.lock();
668 }
669 void QQuickContext2DImageTexture::unlock()
670 {
671     if (m_threadRendering)
672         m_mutex.unlock();
673 }
674
675 void QQuickContext2DImageTexture::wait()
676 {
677     if (m_threadRendering)
678         m_waitCondition.wait(&m_mutex);
679 }
680
681 void QQuickContext2DImageTexture::wake()
682 {
683     if (m_threadRendering)
684         m_waitCondition.wakeOne();
685 }
686
687 bool QQuickContext2DImageTexture::supportDirectRendering() const
688 {
689     return !m_threadRendering;
690 }
691
692 QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
693 {
694     return QQuickCanvasItem::Image;
695 }
696
697 void QQuickContext2DImageTexture::bind()
698 {
699     m_texture->bind();
700 }
701
702 bool QQuickContext2DImageTexture::updateTexture()
703 {
704     bool textureUpdated = m_dirtyTexture;
705     if (m_dirtyTexture) {
706         m_texture->setImage(m_image);
707         m_dirtyTexture = false;
708     }
709     return textureUpdated;
710 }
711
712 QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
713 {
714     return new QQuickContext2DImageTile();
715 }
716
717 void QQuickContext2DImageTexture::grabImage(const QRect& r)
718 {
719     m_doGrabImage = true;
720     paint();
721     m_doGrabImage = false;
722     m_grabedImage = m_image.copy(r);
723 }
724
725 QImage QQuickContext2DImageTexture::toImage(const QRectF& rect)
726 {
727     QRect r = rect.isValid() ? rect.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
728     if (threadRendering()) {
729         wake();
730         QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
731     } else {
732         QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
733     }
734     QImage image = m_grabedImage;
735     m_grabedImage = QImage();
736     return image;
737 }
738
739 QPaintDevice* QQuickContext2DImageTexture::beginPainting()
740 {
741     QQuickContext2DTexture::beginPainting();
742
743     if (m_canvasWindow.size().isEmpty())
744         return 0;
745
746     if (m_canvasWindowChanged) {
747         m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
748         m_image.fill(0x00000000);
749         m_canvasWindowChanged = false;
750     }
751
752     return &m_image;
753 }
754
755 void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
756 {
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());
764
765         lock();
766         m_painter.begin(&m_image);
767         m_painter.setCompositionMode(QPainter::CompositionMode_Source);
768         m_painter.drawImage(target, t->image(), source);
769         m_painter.end();
770         unlock();
771     }
772 }
773
774 QT_END_NAMESPACE
775