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