6e2c7c3e3cbc3f0d7c8f24b87e20988d8b64366e
[profile/ivi/qtdeclarative.git] / src / declarative / items / context2d / qsgcontext2dtexture.cpp
1 #include "qsgcontext2dtexture_p.h"
2 #include "qsgcontext2dtile_p.h"
3 #include "qsgcanvasitem_p.h"
4 #include "qsgitem_p.h"
5 #include "private/qsgtexture_p.h"
6 #include "qsgcontext2dcommandbuffer_p.h"
7
8 #include <QtOpenGL/QGLFramebufferObject>
9 #include <QtOpenGL/QGLFramebufferObjectFormat>
10 #include <QtCore/QThread>
11
12 #define QT_MINIMUM_FBO_SIZE 64
13
14 static inline int qt_next_power_of_two(int v)
15 {
16     v--;
17     v |= v >> 1;
18     v |= v >> 2;
19     v |= v >> 4;
20     v |= v >> 8;
21     v |= v >> 16;
22     ++v;
23     return v;
24 }
25
26
27 Q_GLOBAL_STATIC(QThread, globalCanvasThreadRenderInstance)
28
29
30 QSGContext2DTexture::QSGContext2DTexture()
31     : QSGDynamicTexture()
32     , m_context(0)
33     , m_canvasSize(QSize(1, 1))
34     , m_tileSize(QSize(1, 1))
35     , m_canvasWindow(QRect(0, 0, 1, 1))
36     , m_dirtyCanvas(false)
37     , m_dirtyTexture(false)
38     , m_threadRendering(false)
39     , m_smooth(false)
40     , m_tiledCanvas(false)
41     , m_doGrabImage(false)
42     , m_painting(false)
43 {
44 }
45
46 QSGContext2DTexture::~QSGContext2DTexture()
47 {
48    clearTiles();
49 }
50
51 QSize QSGContext2DTexture::textureSize() const
52 {
53     return m_canvasWindow.size();
54 }
55
56 void QSGContext2DTexture::markDirtyTexture()
57 {
58     lock();
59     m_dirtyTexture = true;
60     unlock();
61     emit textureChanged();
62 }
63
64 bool QSGContext2DTexture::setCanvasSize(const QSize &size)
65 {
66     if (m_canvasSize != size) {
67         m_canvasSize = size;
68         m_dirtyCanvas = true;
69         return true;
70     }
71     return false;
72 }
73
74 bool QSGContext2DTexture::setTileSize(const QSize &size)
75 {
76     if (m_tileSize != size) {
77         m_tileSize = size;
78         m_dirtyCanvas = true;
79         return true;
80     }
81     return false;
82 }
83
84 void QSGContext2DTexture::setSmooth(bool smooth)
85 {
86     m_smooth = smooth;
87 }
88
89 void QSGContext2DTexture::setItem(QSGCanvasItem* item)
90 {
91     if (!item) {
92         lock();
93         m_item = 0;
94         m_context = 0;
95         unlock();
96         wake();
97     } else if (m_item != item) {
98         lock();
99         m_item = item;
100         m_context = item->context();
101         m_state = m_context->state;
102         unlock();
103         connect(this, SIGNAL(textureChanged()), m_item, SIGNAL(painted()), Qt::QueuedConnection);
104         canvasChanged(item->canvasSize().toSize()
105                     , item->tileSize()
106                     , item->canvasWindow().toAlignedRect()
107                     , item->canvasWindow().toAlignedRect()
108                     , item->smooth());
109     }
110 }
111
112 bool QSGContext2DTexture::setCanvasWindow(const QRect& r)
113 {
114     if (m_canvasWindow != r) {
115         m_canvasWindow = r;
116     }
117 }
118
119 bool QSGContext2DTexture::setDirtyRect(const QRect &r)
120 {
121     bool doDirty = false;
122     foreach (QSGContext2DTile* t, m_tiles) {
123         bool dirty = t->rect().intersected(r).isValid();
124         t->markDirty(dirty);
125         if (dirty)
126             doDirty = true;
127     }
128     return doDirty;
129 }
130
131 void QSGContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
132 {
133     lock();
134
135     QSize ts = tileSize;
136     if (ts.width() > canvasSize.width())
137         ts.setWidth(canvasSize.width());
138
139     if (ts.height() > canvasSize.height())
140         ts.setHeight(canvasSize.height());
141
142     bool canvasChanged = setCanvasSize(canvasSize);
143     bool tileChanged = setTileSize(ts);
144
145     bool doDirty = false;
146     if (canvasSize == canvasWindow.size()) {
147         m_tiledCanvas = false;
148         m_dirtyCanvas = false;
149     } else {
150         m_tiledCanvas = true;
151         if (dirtyRect.isValid())
152             doDirty = setDirtyRect(dirtyRect);
153     }
154
155     bool windowChanged = setCanvasWindow(canvasWindow);
156
157     if (windowChanged || doDirty) {
158         if (m_threadRendering)
159             QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
160         else if (supportDirectRendering())
161             QMetaObject::invokeMethod(this, "paint", Qt::DirectConnection);
162     }
163
164     setSmooth(smooth);
165     unlock();
166 }
167
168 void QSGContext2DTexture::paintWithoutTiles()
169 {
170     QSGContext2DCommandBuffer* ccb = m_context->buffer();
171
172     if (ccb->isEmpty() && m_threadRendering && !m_doGrabImage) {
173         lock();
174         if (m_item)
175             QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, QRectF(0, 0, m_canvasSize.width(), m_canvasSize.height())));
176         wait();
177         unlock();
178     }
179     if (ccb->isEmpty()) {
180         return;
181     }
182
183     QPaintDevice* device = beginPainting();
184     if (!device) {
185         endPainting();
186         return;
187     }
188
189     QPainter p;
190     p.begin(device);
191     if (m_smooth)
192         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
193                                | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
194     else
195         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
196                                  | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false);
197     p.setCompositionMode(QPainter::CompositionMode_SourceOver);
198     m_state = ccb->replay(&p, m_state);
199
200     ccb->clear();
201     markDirtyTexture();
202     endPainting();
203 }
204
205 bool QSGContext2DTexture::canvasDestroyed()
206 {
207     bool noCanvas = false;
208     lock();
209     noCanvas = m_item == 0;
210     unlock();
211     return noCanvas;
212 }
213
214 void QSGContext2DTexture::paint()
215 {
216     if (canvasDestroyed())
217         return;
218
219     if (!m_tiledCanvas) {
220         paintWithoutTiles();
221     } else {
222         QSGContext2D::State oldState = m_state;
223         QSGContext2DCommandBuffer* ccb = m_context->buffer();
224
225         lock();
226         QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
227         unlock();
228
229         if (!tiledRegion.isEmpty()) {
230             if (m_threadRendering && !m_doGrabImage) {
231                 QRect dirtyRect;
232
233                 lock();
234                 foreach (QSGContext2DTile* tile, m_tiles) {
235                     if (tile->dirty()) {
236                         if (dirtyRect.isEmpty())
237                             dirtyRect = tile->rect();
238                         else
239                             dirtyRect |= tile->rect();
240                     }
241                 }
242                 unlock();
243
244                 if (dirtyRect.isValid()) {
245                     lock();
246                     if (m_item)
247                         QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, dirtyRect));
248                     wait();
249                     unlock();
250                 }
251             }
252
253             if (beginPainting()) {
254                 foreach (QSGContext2DTile* tile, m_tiles) {
255                     bool dirtyTile = false, dirtyCanvas = false, smooth = false;
256
257                     lock();
258                     dirtyTile = tile->dirty();
259                     smooth = m_smooth;
260                     dirtyCanvas  = m_dirtyCanvas;
261                     unlock();
262
263                     //canvas size or tile size may change during painting tiles
264                     if (dirtyCanvas) {
265                         if (m_threadRendering)
266                             QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
267                         endPainting();
268                         return;
269                     } else if (dirtyTile) {
270                         m_state = ccb->replay(tile->createPainter(smooth), oldState);
271
272                         lock();
273                         tile->markDirty(false);
274                         unlock();
275                     }
276
277                     compositeTile(tile);
278                 }
279                 ccb->clear();
280                 endPainting();
281                 markDirtyTexture();
282             }
283         }
284     }
285 }
286
287 QRect QSGContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
288 {
289     if (window.isEmpty())
290         return QRect();
291
292     const int tw = tileSize.width();
293     const int th = tileSize.height();
294     const int h1 = window.left() / tw;
295     const int v1 = window.top() / th;
296
297     const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
298     const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
299
300     return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
301 }
302
303 QRect QSGContext2DTexture::createTiles(const QRect& window)
304 {
305     QList<QSGContext2DTile*> oldTiles = m_tiles;
306     m_tiles.clear();
307
308     if (window.isEmpty()) {
309         m_dirtyCanvas = false;
310         return QRect();
311     }
312
313     QRect r = tiledRect(window, m_tileSize);
314
315     const int tw = m_tileSize.width();
316     const int th = m_tileSize.height();
317     const int h1 = window.left() / tw;
318     const int v1 = window.top() / th;
319
320
321     const int htiles = r.width() / tw;
322     const int vtiles = r.height() / th;
323
324     for (int yy = 0; yy < vtiles; ++yy) {
325         for (int xx = 0; xx < htiles; ++xx) {
326             int ht = xx + h1;
327             int vt = yy + v1;
328
329             QSGContext2DTile* tile = 0;
330
331             QPoint pos(ht * tw, vt * th);
332             QRect rect(pos, m_tileSize);
333
334             for (int i = 0; i < oldTiles.size(); i++) {
335                 if (oldTiles[i]->rect() == rect) {
336                     tile = oldTiles.takeAt(i);
337                     break;
338                 }
339             }
340
341             if (!tile)
342                 tile = createTile();
343
344             tile->setRect(rect);
345             m_tiles.append(tile);
346         }
347     }
348
349     qDeleteAll(oldTiles);
350
351     m_dirtyCanvas = false;
352     return r;
353 }
354
355 void QSGContext2DTexture::clearTiles()
356 {
357     qDeleteAll(m_tiles);
358     m_tiles.clear();
359 }
360
361 QSGContext2DFBOTexture::QSGContext2DFBOTexture()
362     : QSGContext2DTexture()
363     , m_fbo(0)
364 {
365     m_threadRendering = false;
366 }
367
368 bool QSGContext2DFBOTexture::setCanvasSize(const QSize &size)
369 {
370     QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
371                   , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
372
373     if (m_canvasSize != s) {
374         m_canvasSize = s;
375         m_dirtyCanvas = true;
376         return true;
377     }
378     return false;
379 }
380
381 bool QSGContext2DFBOTexture::setTileSize(const QSize &size)
382 {
383     QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
384                   , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
385     if (m_tileSize != s) {
386         m_tileSize = s;
387         m_dirtyCanvas = true;
388         return true;
389     }
390     return false;
391 }
392
393 bool QSGContext2DFBOTexture::setCanvasWindow(const QRect& canvasWindow)
394 {
395     QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().width()))
396                   , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().height())));
397
398
399     bool doChanged = false;
400     if (m_fboSize != s) {
401         m_fboSize = s;
402         doChanged = true;
403     }
404
405     if (m_canvasWindow != canvasWindow)
406         m_canvasWindow = canvasWindow;
407
408     return doChanged;
409 }
410
411 void QSGContext2DFBOTexture::bind()
412 {
413     glBindTexture(GL_TEXTURE_2D, textureId());
414     updateBindOptions();
415 }
416
417 QRectF QSGContext2DFBOTexture::textureSubRect() const
418 {
419     return QRectF(0
420                 , 1
421                 , qreal(m_canvasWindow.width()) / m_fboSize.width()
422                 , qreal(-m_canvasWindow.height()) / m_fboSize.height());
423 }
424
425
426 int QSGContext2DFBOTexture::textureId() const
427 {
428     return m_fbo? m_fbo->texture() : 0;
429 }
430
431
432 bool QSGContext2DFBOTexture::updateTexture()
433 {
434     if (!m_context->buffer()->isEmpty()) {
435         paint();
436     }
437
438     bool textureUpdated = m_dirtyTexture;
439
440     m_dirtyTexture = false;
441
442     if (m_doGrabImage) {
443         grabImage();
444         m_condition.wakeOne();
445         m_doGrabImage = false;
446     }
447     return textureUpdated;
448 }
449
450 QSGContext2DTile* QSGContext2DFBOTexture::createTile() const
451 {
452     return new QSGContext2DFBOTile();
453 }
454
455 void QSGContext2DFBOTexture::grabImage()
456 {
457     if (m_fbo) {
458         m_grabedImage = m_fbo->toImage();
459     }
460 }
461
462 QImage QSGContext2DFBOTexture::toImage(const QRectF& region)
463 {
464 #define QML_CONTEXT2D_WAIT_MAX 5000
465
466     m_doGrabImage = true;
467     if (m_item)
468         m_item->update();
469
470     QImage grabbed;
471     m_mutex.lock();
472     bool ok = m_condition.wait(&m_mutex, QML_CONTEXT2D_WAIT_MAX);
473
474     if (!ok)
475         grabbed = QImage();
476
477     if (region.isValid())
478         grabbed = m_grabedImage.copy(region.toRect());
479     else
480         grabbed = m_grabedImage;
481     m_grabedImage = QImage();
482     return grabbed;
483 }
484
485 void QSGContext2DFBOTexture::compositeTile(QSGContext2DTile* tile)
486 {
487     QSGContext2DFBOTile* t = static_cast<QSGContext2DFBOTile*>(tile);
488     QRect target = t->rect().intersect(m_canvasWindow);
489     if (target.isValid()) {
490         QRect source = target;
491
492         source.moveTo(source.topLeft() - t->rect().topLeft());
493         target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
494
495         QGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
496     }
497 }
498 QSGCanvasItem::RenderTarget QSGContext2DFBOTexture::renderTarget() const
499 {
500     return QSGCanvasItem::FramebufferObject;
501 }
502 QPaintDevice* QSGContext2DFBOTexture::beginPainting()
503 {
504     QSGContext2DTexture::beginPainting();
505
506     if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
507         delete m_fbo;
508         m_fbo = 0;
509     } else if (!m_fbo || m_fbo->size() != m_fboSize) {
510         QGLFramebufferObjectFormat format;
511         format.setAttachment(QGLFramebufferObject::CombinedDepthStencil);
512         format.setInternalTextureFormat(GL_RGBA);
513         format.setMipmap(false);
514         format.setTextureTarget(GL_TEXTURE_2D);
515         delete m_fbo;
516         glDisable(GL_DEPTH_TEST);
517         glDepthMask(false);
518
519         m_fbo = new QGLFramebufferObject(m_fboSize, format);
520         glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
521         updateBindOptions(false);
522     }
523     return m_fbo;
524 }
525
526 void qt_quit_context2d_render_thread()
527 {
528     QThread* thread = globalCanvasThreadRenderInstance();
529     thread->quit();
530     thread->wait();
531 }
532
533 QSGContext2DImageTexture::QSGContext2DImageTexture(bool threadRendering)
534     : QSGContext2DTexture()
535     , m_texture(new QSGPlainTexture())
536 {
537     m_texture->setOwnsTexture(true);
538     m_texture->setHasMipmaps(false);
539
540     m_threadRendering = threadRendering;
541
542     if (m_threadRendering) {
543         QThread* thread = globalCanvasThreadRenderInstance();
544         moveToThread(thread);
545
546         if (!thread->isRunning()) {
547             qAddPostRoutine(qt_quit_context2d_render_thread);
548             thread->start();
549         }
550     }
551 }
552
553 QSGContext2DImageTexture::~QSGContext2DImageTexture()
554 {
555     m_texture->deleteLater();
556 }
557
558 int QSGContext2DImageTexture::textureId() const
559 {
560     return m_texture->textureId();
561 }
562
563 void QSGContext2DImageTexture::lock()
564 {
565     if (m_threadRendering)
566         m_mutex.lock();
567 }
568 void QSGContext2DImageTexture::unlock()
569 {
570     if (m_threadRendering)
571         m_mutex.unlock();
572 }
573
574 void QSGContext2DImageTexture::wait()
575 {
576     if (m_threadRendering)
577         m_waitCondition.wait(&m_mutex);
578 }
579
580 void QSGContext2DImageTexture::wake()
581 {
582     if (m_threadRendering)
583         m_waitCondition.wakeOne();
584 }
585
586 bool QSGContext2DImageTexture::supportDirectRendering() const
587 {
588     return !m_threadRendering;
589 }
590
591 QSGCanvasItem::RenderTarget QSGContext2DImageTexture::renderTarget() const
592 {
593     return QSGCanvasItem::Image;
594 }
595
596 void QSGContext2DImageTexture::bind()
597 {
598     m_texture->bind();
599 }
600
601 bool QSGContext2DImageTexture::updateTexture()
602 {
603     lock();
604     bool textureUpdated = m_dirtyTexture;
605     if (m_dirtyTexture) {
606         m_texture->setImage(m_image);
607         m_dirtyTexture = false;
608     }
609     unlock();
610     return textureUpdated;
611 }
612
613 QSGContext2DTile* QSGContext2DImageTexture::createTile() const
614 {
615     return new QSGContext2DImageTile();
616 }
617
618 void QSGContext2DImageTexture::grabImage(const QRect& r)
619 {
620     m_doGrabImage = true;
621     paint();
622     m_doGrabImage = false;
623     m_grabedImage = m_image.copy(r);
624 }
625
626 QImage QSGContext2DImageTexture::toImage(const QRectF& region)
627 {
628     QRect r = region.isValid() ? region.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
629     if (threadRendering()) {
630         wake();
631         QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
632     } else {
633         QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
634     }
635     QImage image = m_grabedImage;
636     m_grabedImage = QImage();
637     return image;
638 }
639
640 QPaintDevice* QSGContext2DImageTexture::beginPainting()
641 {
642      QSGContext2DTexture::beginPainting();
643
644     if (m_canvasWindow.size().isEmpty())
645         return 0;
646
647     lock();
648     if (m_image.size() != m_canvasWindow.size()) {
649         m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
650         m_image.fill(Qt::transparent);
651     }
652     unlock();
653     return &m_image;
654 }
655
656 void QSGContext2DImageTexture::compositeTile(QSGContext2DTile* tile)
657 {
658     Q_ASSERT(!tile->dirty());
659     QSGContext2DImageTile* t = static_cast<QSGContext2DImageTile*>(tile);
660     QRect target = t->rect().intersect(m_canvasWindow);
661     if (target.isValid()) {
662         QRect source = target;
663         source.moveTo(source.topLeft() - t->rect().topLeft());
664         target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
665
666         lock();
667         m_painter.begin(&m_image);
668         m_painter.setCompositionMode(QPainter::CompositionMode_Source);
669         m_painter.drawImage(target, t->image(), source);
670         m_painter.end();
671         unlock();
672     }
673 }