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