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