Update to 5.0.0-beta1
[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 QT_BEGIN_NAMESPACE
55
56 #define QT_MINIMUM_FBO_SIZE 64
57
58 static inline int qt_next_power_of_two(int v)
59 {
60     v--;
61     v |= v >> 1;
62     v |= v >> 2;
63     v |= v >> 4;
64     v |= v >> 8;
65     v |= v >> 16;
66     ++v;
67     return v;
68 }
69
70 struct GLAcquireContext {
71     GLAcquireContext(QOpenGLContext *c, QSurface *s):ctx(c) {
72         if (ctx) {
73             Q_ASSERT(s);
74             if (!ctx->isValid())
75                 ctx->create();
76
77             if (!ctx->isValid())
78                 qWarning() << "Unable to create GL context";
79             else if (!ctx->makeCurrent(s))
80                 qWarning() << "Can't make current GL context";
81         }
82     }
83     ~GLAcquireContext() {
84         if (ctx)
85             ctx->doneCurrent();
86     }
87     QOpenGLContext *ctx;
88 };
89
90 QQuickContext2DTexture::QQuickContext2DTexture()
91     : m_context(0)
92     , m_item(0)
93     , m_dirtyCanvas(false)
94     , m_canvasWindowChanged(false)
95     , m_dirtyTexture(false)
96     , m_smooth(true)
97     , m_antialiasing(false)
98     , m_tiledCanvas(false)
99     , m_painting(false)
100 {
101 }
102
103 QQuickContext2DTexture::~QQuickContext2DTexture()
104 {
105    clearTiles();
106 }
107
108 QSize QQuickContext2DTexture::textureSize() const
109 {
110     return m_canvasWindow.size();
111 }
112
113 void QQuickContext2DTexture::markDirtyTexture()
114 {
115     m_dirtyTexture = true;
116     updateTexture();
117     emit textureChanged();
118 }
119
120 bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
121 {
122     if (m_canvasSize != size) {
123         m_canvasSize = size;
124         m_dirtyCanvas = true;
125         return true;
126     }
127     return false;
128 }
129
130 bool QQuickContext2DTexture::setTileSize(const QSize &size)
131 {
132     if (m_tileSize != size) {
133         m_tileSize = size;
134         m_dirtyCanvas = true;
135         return true;
136     }
137     return false;
138 }
139
140 void QQuickContext2DTexture::setSmooth(bool smooth)
141 {
142     m_smooth = smooth;
143 }
144
145 void QQuickContext2DTexture::setAntialiasing(bool antialiasing)
146 {
147     m_antialiasing = antialiasing;
148 }
149
150 void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
151 {
152     m_item = item;
153     m_context = (QQuickContext2D*)item->rawContext(); // FIXME
154     m_state = m_context->state;
155 }
156
157 bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
158 {
159     if (m_canvasWindow != r) {
160         m_canvasWindow = r;
161         m_canvasWindowChanged = true;
162         return true;
163     }
164     return false;
165 }
166
167 bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
168 {
169     bool doDirty = false;
170     if (m_tiledCanvas) {
171         foreach (QQuickContext2DTile* t, m_tiles) {
172             bool dirty = t->rect().intersected(r).isValid();
173             t->markDirty(dirty);
174             if (dirty)
175                 doDirty = true;
176         }
177     } else {
178         doDirty = m_canvasWindow.intersected(r).isValid();
179     }
180     return doDirty;
181 }
182
183 void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing)
184 {
185     QSize ts = tileSize;
186     if (ts.width() > canvasSize.width())
187         ts.setWidth(canvasSize.width());
188
189     if (ts.height() > canvasSize.height())
190         ts.setHeight(canvasSize.height());
191
192     setCanvasSize(canvasSize);
193     setTileSize(ts);
194     setCanvasWindow(canvasWindow);
195
196     if (canvasSize == canvasWindow.size()) {
197         m_tiledCanvas = false;
198         m_dirtyCanvas = false;
199     } else {
200         m_tiledCanvas = true;
201     }
202
203     if (dirtyRect.isValid())
204         setDirtyRect(dirtyRect);
205
206     setSmooth(smooth);
207     setAntialiasing(antialiasing);
208 }
209
210 void QQuickContext2DTexture::paintWithoutTiles(QQuickContext2DCommandBuffer *ccb)
211 {
212     if (!ccb || ccb->isEmpty())
213         return;
214
215     QPaintDevice* device = beginPainting();
216     if (!device) {
217         endPainting();
218         return;
219     }
220
221     QPainter p;
222     p.begin(device);
223     if (m_antialiasing)
224         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing, true);
225     else
226         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing, false);
227
228     if (m_smooth)
229         p.setRenderHint(QPainter::SmoothPixmapTransform, true);
230     else
231         p.setRenderHint(QPainter::SmoothPixmapTransform, false);
232
233     p.setCompositionMode(QPainter::CompositionMode_SourceOver);
234
235     ccb->replay(&p, m_state);
236     endPainting();
237     markDirtyTexture();
238 }
239
240 bool QQuickContext2DTexture::canvasDestroyed()
241 {
242     return m_item == 0;
243 }
244
245 void QQuickContext2DTexture::paint(QQuickContext2DCommandBuffer *ccb)
246 {
247     if (canvasDestroyed()) {
248         delete ccb;
249         return;
250     }
251
252     GLAcquireContext currentContext(m_context->glContext(), m_context->surface());
253
254     if (!m_tiledCanvas) {
255         paintWithoutTiles(ccb);
256         delete ccb;
257         return;
258     }
259
260     QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
261     if (!tiledRegion.isEmpty()) {
262         QRect dirtyRect;
263         foreach (QQuickContext2DTile* tile, m_tiles) {
264             if (tile->dirty()) {
265                 if (dirtyRect.isEmpty())
266                     dirtyRect = tile->rect();
267                 else
268                     dirtyRect |= tile->rect();
269             }
270         }
271
272         if (beginPainting()) {
273             QQuickContext2D::State oldState = m_state;
274             foreach (QQuickContext2DTile* tile, m_tiles) {
275                 if (tile->dirty()) {
276                     ccb->replay(tile->createPainter(m_smooth, m_antialiasing), oldState);
277                     tile->drawFinished();
278                     tile->markDirty(false);
279                 }
280                 compositeTile(tile);
281             }
282             endPainting();
283             m_state = oldState;
284             markDirtyTexture();
285         }
286     }
287     delete ccb;
288 }
289
290 QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
291 {
292     if (window.isEmpty())
293         return QRect();
294
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;
299
300     const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
301     const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
302
303     return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
304 }
305
306 QRect QQuickContext2DTexture::createTiles(const QRect& window)
307 {
308     QList<QQuickContext2DTile*> oldTiles = m_tiles;
309     m_tiles.clear();
310
311     if (window.isEmpty()) {
312         m_dirtyCanvas = false;
313         return QRect();
314     }
315
316     QRect r = tiledRect(window, adjustedTileSize(m_tileSize));
317
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;
322
323
324     const int htiles = r.width() / tw;
325     const int vtiles = r.height() / th;
326
327     for (int yy = 0; yy < vtiles; ++yy) {
328         for (int xx = 0; xx < htiles; ++xx) {
329             int ht = xx + h1;
330             int vt = yy + v1;
331
332             QQuickContext2DTile* tile = 0;
333
334             QPoint pos(ht * tw, vt * th);
335             QRect rect(pos, m_tileSize);
336
337             for (int i = 0; i < oldTiles.size(); i++) {
338                 if (oldTiles[i]->rect() == rect) {
339                     tile = oldTiles.takeAt(i);
340                     break;
341                 }
342             }
343
344             if (!tile)
345                 tile = createTile();
346
347             tile->setRect(rect);
348             m_tiles.append(tile);
349         }
350     }
351
352     qDeleteAll(oldTiles);
353
354     m_dirtyCanvas = false;
355     return r;
356 }
357
358 void QQuickContext2DTexture::clearTiles()
359 {
360     qDeleteAll(m_tiles);
361     m_tiles.clear();
362 }
363
364 QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts)
365 {
366     return ts;
367 }
368
369 static inline QSize npotAdjustedSize(const QSize &size)
370 {
371     static bool checked = false;
372     static bool npotSupported = false;
373
374     if (!checked) {
375         npotSupported = QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
376         checked = true;
377     }
378
379     if (npotSupported) {
380         return QSize(qMax(QT_MINIMUM_FBO_SIZE, size.width()),
381                      qMax(QT_MINIMUM_FBO_SIZE, size.height()));
382     }
383
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())));
386 }
387
388 QQuickContext2DFBOTexture::QQuickContext2DFBOTexture()
389     : QQuickContext2DTexture()
390     , m_fbo(0)
391     , m_multisampledFbo(0)
392     , m_paint_device(0)
393 {
394 }
395
396 QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture()
397 {
398     if (m_multisampledFbo)
399         m_multisampledFbo->release();
400     else if (m_fbo)
401         m_fbo->release();
402
403     delete m_fbo;
404     delete m_multisampledFbo;
405     delete m_paint_device;
406 }
407
408 QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts)
409 {
410     return npotAdjustedSize(ts);
411 }
412
413 void QQuickContext2DFBOTexture::bind()
414 {
415     glBindTexture(GL_TEXTURE_2D, textureId());
416     updateBindOptions();
417 }
418
419 QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const
420 {
421     return QRectF(0
422                 , 0
423                 , qreal(m_canvasWindow.width()) / m_fboSize.width()
424                 , qreal(m_canvasWindow.height()) / m_fboSize.height());
425 }
426
427
428 int QQuickContext2DFBOTexture::textureId() const
429 {
430     return m_fbo? m_fbo->texture() : 0;
431 }
432
433
434 bool QQuickContext2DFBOTexture::updateTexture()
435 {
436     bool textureUpdated = m_dirtyTexture;
437     m_dirtyTexture = false;
438     return textureUpdated;
439 }
440
441 QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const
442 {
443     return new QQuickContext2DFBOTile();
444 }
445
446 bool QQuickContext2DFBOTexture::doMultisampling() const
447 {
448     static bool extensionsChecked = false;
449     static bool multisamplingSupported = false;
450
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;
456     }
457
458     return multisamplingSupported  && m_antialiasing;
459 }
460
461 void QQuickContext2DFBOTexture::grabImage(const QRectF& rf)
462 {
463     Q_ASSERT(rf.isValid());
464
465     if (!m_fbo) {
466         m_context->setGrabbedImage(QImage());
467         return;
468     }
469
470     QImage grabbed;
471     {
472         GLAcquireContext ctx(m_context->glContext(), m_context->surface());
473         grabbed = m_fbo->toImage().mirrored().copy(rf.toRect());
474     }
475
476     m_context->setGrabbedImage(grabbed);
477 }
478
479 void QQuickContext2DFBOTexture::compositeTile(QQuickContext2DTile* tile)
480 {
481     QQuickContext2DFBOTile* t = static_cast<QQuickContext2DFBOTile*>(tile);
482     QRect target = t->rect().intersected(m_canvasWindow);
483     if (target.isValid()) {
484         QRect source = target;
485
486         source.moveTo(source.topLeft() - t->rect().topLeft());
487         target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
488
489         QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
490     }
491 }
492
493 QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const
494 {
495     return QQuickCanvasItem::FramebufferObject;
496 }
497
498 QPaintDevice* QQuickContext2DFBOTexture::beginPainting()
499 {
500     QQuickContext2DTexture::beginPainting();
501
502     if (m_canvasWindow.size().isEmpty()) {
503         delete m_fbo;
504         delete m_multisampledFbo;
505         delete m_paint_device;
506         m_fbo = 0;
507         m_multisampledFbo = 0;
508         m_paint_device = 0;
509         return 0;
510     } else if (!m_fbo || m_canvasWindowChanged) {
511         delete m_fbo;
512         delete m_multisampledFbo;
513         delete m_paint_device;
514         m_paint_device = 0;
515
516         m_fboSize = npotAdjustedSize(m_canvasWindow.size());
517         m_canvasWindowChanged = false;
518
519         if (doMultisampling()) {
520             {
521                 QOpenGLFramebufferObjectFormat format;
522                 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
523                 format.setSamples(8);
524                 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
525             }
526             {
527                 QOpenGLFramebufferObjectFormat format;
528                 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
529                 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
530             }
531         } else {
532             QOpenGLFramebufferObjectFormat format;
533             format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
534
535             m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
536         }
537     }
538
539     if (doMultisampling())
540         m_multisampledFbo->bind();
541     else
542         m_fbo->bind();
543
544
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;
550     }
551
552     return m_paint_device;
553 }
554
555 void QQuickContext2DFBOTexture::endPainting()
556 {
557     QQuickContext2DTexture::endPainting();
558     if (m_multisampledFbo)
559         QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo);
560 }
561
562 QQuickContext2DImageTexture::QQuickContext2DImageTexture()
563     : QQuickContext2DTexture()
564     , m_texture(0)
565 {
566 }
567
568 QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
569 {
570     if (m_texture && m_texture->thread() != QThread::currentThread())
571         m_texture->deleteLater();
572     else
573         delete m_texture;
574 }
575
576 int QQuickContext2DImageTexture::textureId() const
577 {
578     return imageTexture()->textureId();
579 }
580
581 QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
582 {
583     return QQuickCanvasItem::Image;
584 }
585
586 void QQuickContext2DImageTexture::bind()
587 {
588     imageTexture()->bind();
589 }
590
591 bool QQuickContext2DImageTexture::updateTexture()
592 {
593     bool textureUpdated = m_dirtyTexture;
594     if (m_dirtyTexture) {
595         imageTexture()->setImage(m_image);
596         m_dirtyTexture = false;
597     }
598     return textureUpdated;
599 }
600
601 QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
602 {
603     return new QQuickContext2DImageTile();
604 }
605
606 void QQuickContext2DImageTexture::grabImage(const QRectF& rf)
607 {
608     Q_ASSERT(rf.isValid());
609     Q_ASSERT(m_context);
610     QImage grabbed = m_image.copy(rf.toRect());
611     m_context->setGrabbedImage(grabbed);
612 }
613
614 QSGPlainTexture *QQuickContext2DImageTexture::imageTexture() const
615 {
616     if (!m_texture) {
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);
621     }
622     return m_texture;
623 }
624
625 QPaintDevice* QQuickContext2DImageTexture::beginPainting()
626 {
627     QQuickContext2DTexture::beginPainting();
628
629     if (m_canvasWindow.size().isEmpty())
630         return 0;
631
632     if (m_canvasWindowChanged) {
633         m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
634         m_image.fill(0x00000000);
635         m_canvasWindowChanged = false;
636     }
637
638     return &m_image;
639 }
640
641 void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
642 {
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());
650
651         m_painter.begin(&m_image);
652         m_painter.setCompositionMode(QPainter::CompositionMode_Source);
653         m_painter.drawImage(target, t->image(), source);
654         m_painter.end();
655     }
656 }
657
658 QT_END_NAMESPACE