Refactor context2d thread logic
[profile/ivi/qtdeclarative.git] / src / quick / items / context2d / qquickcontext2dcommandbuffer.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 "qquickcontext2dcommandbuffer_p.h"
43 #include "qquickcanvasitem_p.h"
44 #include <qqml.h>
45 #include <QtCore/QMutex>
46 #include <QtQuick/qsgtexture.h>
47 #include <QtGui/QOpenGLContext>
48 #include <QtGui/QPaintEngine>
49 #include <QtGui/private/qopenglpaintengine_p.h>
50
51 #define HAS_SHADOW(offsetX, offsetY, blur, color) (color.isValid() && color.alpha() && (blur || offsetX || offsetY))
52
53 QT_BEGIN_NAMESPACE
54
55 void qt_image_boxblur(QImage& image, int radius, bool quality);
56
57 static QImage makeShadowImage(const QImage& image, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
58 {
59     QImage shadowImg(image.width() + blur + qAbs(offsetX),
60                      image.height() + blur + qAbs(offsetY),
61                      QImage::Format_ARGB32_Premultiplied);
62     shadowImg.fill(0);
63     QPainter tmpPainter(&shadowImg);
64     tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
65     qreal shadowX = offsetX > 0? offsetX : 0;
66     qreal shadowY = offsetY > 0? offsetY : 0;
67
68     tmpPainter.drawImage(shadowX, shadowY, image);
69     tmpPainter.end();
70
71     if (blur > 0)
72         qt_image_boxblur(shadowImg, blur/2, true);
73
74     // blacken the image with shadow color...
75     tmpPainter.begin(&shadowImg);
76     tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
77     tmpPainter.fillRect(shadowImg.rect(), color);
78     tmpPainter.end();
79     return shadowImg;
80 }
81
82 static void fillRectShadow(QPainter* p, QRectF shadowRect, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
83 {
84     QRectF r = shadowRect;
85     r.moveTo(0, 0);
86
87     QImage shadowImage(r.size().width() + 1, r.size().height() + 1, QImage::Format_ARGB32_Premultiplied);
88     QPainter tp;
89     tp.begin(&shadowImage);
90     tp.fillRect(r, p->brush());
91     tp.end();
92     shadowImage = makeShadowImage(shadowImage, offsetX, offsetY, blur, color);
93
94     qreal dx = shadowRect.left() + (offsetX < 0? offsetX:0);
95     qreal dy = shadowRect.top() + (offsetY < 0? offsetY:0);
96
97     p->drawImage(dx, dy, shadowImage);
98     p->fillRect(shadowRect, p->brush());
99 }
100
101 static void fillShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
102 {
103     QRectF r = path.boundingRect();
104     QImage img(r.size().width() + r.left() + 1,
105                r.size().height() + r.top() + 1,
106                QImage::Format_ARGB32_Premultiplied);
107     img.fill(0);
108     QPainter tp(&img);
109     tp.fillPath(path.translated(0, 0), p->brush());
110     tp.end();
111
112     QImage shadowImage = makeShadowImage(img, offsetX, offsetY, blur, color);
113     qreal dx = r.left() + (offsetX < 0? offsetX:0);
114     qreal dy = r.top() + (offsetY < 0? offsetY:0);
115
116     p->drawImage(dx, dy, shadowImage);
117     p->fillPath(path, p->brush());
118 }
119
120 static void strokeShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
121 {
122     QRectF r = path.boundingRect();
123     QImage img(r.size().width() + r.left() + 1,
124                r.size().height() + r.top() + 1,
125                QImage::Format_ARGB32_Premultiplied);
126     img.fill(0);
127     QPainter tp(&img);
128     tp.strokePath(path, p->pen());
129     tp.end();
130
131     QImage shadowImage = makeShadowImage(img, offsetX, offsetY, blur, color);
132     qreal dx = r.left() + (offsetX < 0? offsetX:0);
133     qreal dy = r.top() + (offsetY < 0? offsetY:0);
134     p->drawImage(dx, dy, shadowImage);
135     p->strokePath(path, p->pen());
136 }
137 static inline void drawRepeatPattern(QPainter* p, const QImage& image, const QRectF& rect, const bool repeatX, const bool repeatY)
138 {
139     // Patterns must be painted so that the top left of the first image is anchored at
140     // the origin of the coordinate space
141     if (!image.isNull()) {
142         int w = image.width();
143         int h = image.height();
144         int startX, startY;
145         QRect r(static_cast<int>(rect.x()), static_cast<int>(rect.y()), static_cast<int>(rect.width()), static_cast<int>(rect.height()));
146
147         // startX, startY is the coordinate of the first image we need to put on the left-top of the rect
148         if (repeatX && repeatY) {
149             // repeat
150             // startX, startY is at the left top side of the left-top of the rect
151             startX = r.x() >=0 ? r.x() - (r.x() % w) : r.x() - (w - qAbs(r.x()) % w);
152             startY = r.y() >=0 ? r.y() - (r.y() % h) : r.y() - (h - qAbs(r.y()) % h);
153         } else {
154            if (!repeatX && !repeatY) {
155                // no-repeat
156                // only draw the image once at orgin once, check if need to draw
157                QRect imageRect(0, 0, w, h);
158                if (imageRect.intersects(r)) {
159                    startX = 0;
160                    startY = 0;
161                } else
162                    return;
163            } else if (repeatX && !repeatY) {
164                // repeat-x
165                // startY is fixed, but startX change based on the left-top of the rect
166                QRect imageRect(r.x(), 0, r.width(), h);
167                if (imageRect.intersects(r)) {
168                    startX = r.x() >=0 ? r.x() - (r.x() % w) : r.x() - (w - qAbs(r.x()) % w);
169                    startY = 0;
170                } else
171                    return;
172            } else {
173                // repeat-y
174                // startX is fixed, but startY change based on the left-top of the rect
175                QRect imageRect(0, r.y(), w, r.height());
176                if (imageRect.intersects(r)) {
177                    startX = 0;
178                    startY = r.y() >=0 ? r.y() - (r.y() % h) : r.y() - (h - qAbs(r.y()) % h);
179                } else
180                    return;
181            }
182         }
183
184         int x = startX;
185         int y = startY;
186         do {
187             // repeat Y
188             do {
189                 // repeat X
190                 QRect   imageRect(x, y, w, h);
191                 QRect   intersectRect = imageRect.intersected(r);
192                 QPoint  destStart(intersectRect.x(), intersectRect.y());
193                 QRect   sourceRect(intersectRect.x() - imageRect.x(), intersectRect.y() - imageRect.y(), intersectRect.width(), intersectRect.height());
194
195                 p->drawImage(destStart, image, sourceRect);
196                 x += w;
197             } while (repeatX && x < r.x() + r.width());
198             x = startX;
199             y += h;
200         } while (repeatY && y < r.y() + r.height());
201     }
202 }
203
204 QPen QQuickContext2DCommandBuffer::makePen(const QQuickContext2D::State& state)
205 {
206     QPen pen;
207     pen.setWidthF(state.lineWidth);
208     pen.setCapStyle(state.lineCap);
209     pen.setJoinStyle(state.lineJoin);
210     pen.setMiterLimit(state.miterLimit);
211     pen.setBrush(state.strokeStyle);
212     return pen;
213 }
214
215 void QQuickContext2DCommandBuffer::setPainterState(QPainter* p, const QQuickContext2D::State& state, const QPen& pen)
216 {
217    p->setTransform(state.matrix * p->transform());
218
219    if (pen != p->pen())
220        p->setPen(pen);
221
222    if (state.fillStyle != p->brush())
223        p->setBrush(state.fillStyle);
224
225    if (state.font != p->font())
226        p->setFont(state.font);
227
228    if (state.globalAlpha != p->opacity()) {
229        p->setOpacity(state.globalAlpha);
230    }
231
232    if (state.globalCompositeOperation != p->compositionMode())
233        p->setCompositionMode(state.globalCompositeOperation);
234 }
235
236 static void qt_drawImage(QPainter *p, QQuickContext2D::State& state, QImage image, const QRectF& sr, const QRectF& dr, bool shadow = false)
237 {
238     Q_ASSERT(p);
239
240     if (image.isNull())
241         return;
242
243     qreal sx = sr.x();
244     qreal sy = sr.y();
245     qreal sw = sr.width();
246     qreal sh = sr.height();
247     qreal dx = dr.x();
248     qreal dy = dr.y();
249     qreal dw = dr.width();
250     qreal dh = dr.height();
251
252     if (sw == -1 || sh == -1) {
253         sw = image.width();
254         sh = image.height();
255     }
256     if (sx != 0 || sy != 0 || sw != image.width() || sh != image.height())
257         image = image.copy(sx, sy, sw, sh);
258
259     if (sw != dw || sh != dh)
260         image = image.scaled(dw, dh);
261
262     if (shadow) {
263         QImage shadow = makeShadowImage(image, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
264         qreal shadow_dx = dx + (state.shadowOffsetX < 0? state.shadowOffsetY:0);
265         qreal shadow_dy = dy + (state.shadowOffsetX < 0? state.shadowOffsetY:0);
266         p->drawImage(shadow_dx, shadow_dy, shadow);
267     }
268     //Strange OpenGL painting behavior here, without beginNativePainting/endNativePainting, only the first image is painted.
269     p->beginNativePainting();
270     p->drawImage(dx, dy, image);
271     p->endNativePainting();
272 }
273
274 void QQuickContext2DCommandBuffer::replay(QPainter* p, QQuickContext2D::State& state)
275 {
276     if (!p)
277         return;
278
279     reset();
280
281     QTransform originMatrix = p->worldTransform();
282
283     QPen pen = makePen(state);
284     setPainterState(p, state, pen);
285
286     while (hasNext()) {
287         QQuickContext2D::PaintCommand cmd = takeNextCommand();
288         switch (cmd) {
289         case QQuickContext2D::UpdateMatrix:
290         {
291             state.matrix = takeMatrix();
292             p->setWorldTransform(state.matrix * originMatrix);
293             break;
294         }
295         case QQuickContext2D::ClearRect:
296         {
297             QPainter::CompositionMode  cm = p->compositionMode();
298             qreal alpha = p->opacity();
299             p->setCompositionMode(QPainter::CompositionMode_Source);
300             p->setOpacity(0);
301             p->fillRect(takeRect(), QColor(qRgba(0, 0, 0, 0)));
302             p->setCompositionMode(cm);
303             p->setOpacity(alpha);
304             break;
305         }
306         case QQuickContext2D::FillRect:
307         {
308             QRectF r = takeRect();
309             if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
310                 fillRectShadow(p, r, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
311             else
312                 p->fillRect(r, p->brush());
313             break;
314         }
315         case QQuickContext2D::ShadowColor:
316         {
317             state.shadowColor = takeColor();
318             break;
319         }
320         case QQuickContext2D::ShadowBlur:
321         {
322             state.shadowBlur = takeShadowBlur();
323             break;
324         }
325         case QQuickContext2D::ShadowOffsetX:
326         {
327             state.shadowOffsetX = takeShadowOffsetX();
328             break;
329         }
330         case QQuickContext2D::ShadowOffsetY:
331         {
332             state.shadowOffsetY = takeShadowOffsetY();
333             break;
334         }
335         case QQuickContext2D::FillStyle:
336         {
337             state.fillStyle = takeFillStyle();
338             state.fillPatternRepeatX = takeBool();
339             state.fillPatternRepeatY = takeBool();
340             p->setBrush(state.fillStyle);
341             break;
342         }
343         case QQuickContext2D::StrokeStyle:
344         {
345             state.strokeStyle = takeStrokeStyle();
346             state.strokePatternRepeatX = takeBool();
347             state.strokePatternRepeatY = takeBool();
348             QPen nPen = p->pen();
349             nPen.setBrush(state.strokeStyle);
350             p->setPen(nPen);
351             break;
352         }
353         case QQuickContext2D::LineWidth:
354         {
355             state.lineWidth = takeLineWidth();
356             QPen nPen = p->pen();
357
358             nPen.setWidthF(state.lineWidth);
359             p->setPen(nPen);
360             break;
361         }
362         case QQuickContext2D::LineCap:
363         {
364             state.lineCap = takeLineCap();
365             QPen nPen = p->pen();
366             nPen.setCapStyle(state.lineCap);
367             p->setPen(nPen);
368             break;
369         }
370         case QQuickContext2D::LineJoin:
371         {
372             state.lineJoin = takeLineJoin();
373             QPen nPen = p->pen();
374             nPen.setJoinStyle(state.lineJoin);
375             p->setPen(nPen);
376             break;
377         }
378         case QQuickContext2D::MiterLimit:
379         {
380             state.miterLimit = takeMiterLimit();
381             QPen nPen = p->pen();
382             nPen.setMiterLimit(state.miterLimit);
383             p->setPen(nPen);
384             break;
385         }
386         case QQuickContext2D::TextAlign:
387         case QQuickContext2D::TextBaseline:
388             break;
389         case QQuickContext2D::Fill:
390         {
391             QPainterPath path = takePath();
392             path.closeSubpath();
393             if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
394                 fillShadowPath(p,path, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
395             else
396                 p->fillPath(path, p->brush());
397             break;
398         }
399         case QQuickContext2D::Stroke:
400         {
401             if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
402                 strokeShadowPath(p,takePath(), state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
403             else
404                 p->strokePath(takePath(), p->pen());
405             break;
406         }
407         case QQuickContext2D::Clip:
408         {
409             state.clipPath = takePath();
410             p->setClipping(true);
411             p->setClipPath(state.clipPath);
412             break;
413         }
414         case QQuickContext2D::GlobalAlpha:
415         {
416             state.globalAlpha = takeGlobalAlpha();
417             p->setOpacity(state.globalAlpha);
418             break;
419         }
420         case QQuickContext2D::GlobalCompositeOperation:
421         {
422             state.globalCompositeOperation = takeGlobalCompositeOperation();
423             p->setCompositionMode(state.globalCompositeOperation);
424             break;
425         }
426         case QQuickContext2D::DrawImage:
427         {
428             QRectF sr = takeRect();
429             QRectF dr = takeRect();
430             qt_drawImage(p, state, takeImage(), sr, dr, HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor));
431             break;
432         }
433         case QQuickContext2D::DrawPixmap:
434         {
435             QRectF sr = takeRect();
436             QRectF dr = takeRect();
437
438             QQmlRefPointer<QQuickCanvasPixmap> pix = takePixmap();
439             Q_ASSERT(!pix.isNull());
440
441             const bool hasShadow = HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
442             if (p->paintEngine()->type() != QPaintEngine::OpenGL2 || hasShadow){
443                 //TODO: generate shadow blur with shaders
444                 qt_drawImage(p, state, pix->image(), sr, dr, hasShadow);
445             } else if (pix->texture()){
446                 QSGTexture *tex = pix->texture();
447                 QSGDynamicTexture *dynamicTexture = qobject_cast<QSGDynamicTexture *>(tex);
448                 if (dynamicTexture)
449                     dynamicTexture->updateTexture();
450
451                 if (tex->textureId()) {
452
453                     if (sr.width() < 0)
454                         sr.setWidth(tex->textureSize().width());
455                     if (sr.height() < 0)
456                         sr.setHeight(tex->textureSize().height());
457
458                     if (dr.width() < 0)
459                         dr.setWidth(sr.width());
460                     if (dr.height() < 0)
461                         dr.setHeight(sr.height());
462
463                     qreal srBottom = sr.bottom();
464                     sr.setBottom(sr.top());
465                     sr.setTop(srBottom);
466
467                     tex->bind();
468                     if (p->paintEngine()->type() == QPaintEngine::OpenGL2) {
469                         QOpenGL2PaintEngineEx *engine = static_cast<QOpenGL2PaintEngineEx *>(p->paintEngine());
470                         engine->drawTexture(dr, tex->textureId(), tex->textureSize(), sr);
471                     }
472                 }
473             }
474             break;
475         }
476         case QQuickContext2D::GetImageData:
477         {
478             //TODO:
479             break;
480         }
481         default:
482             break;
483         }
484     }
485
486     p->end();
487 }
488
489 QQuickContext2DCommandBuffer::QQuickContext2DCommandBuffer()
490     : cmdIdx(0)
491     , intIdx(0)
492     , boolIdx(0)
493     , realIdx(0)
494     , rectIdx(0)
495     , colorIdx(0)
496     , matrixIdx(0)
497     , brushIdx(0)
498     , pathIdx(0)
499     , imageIdx(0)
500     , pixmapIdx(0)
501 {
502     static bool registered = false;
503     if (!registered) {
504         qRegisterMetaType<QQuickContext2DCommandBuffer*>("QQuickContext2DCommandBuffer*");
505         registered = true;
506     }
507 }
508
509
510 QQuickContext2DCommandBuffer::~QQuickContext2DCommandBuffer()
511 {
512 }
513
514 void QQuickContext2DCommandBuffer::clear()
515 {
516     commands.clear();
517     ints.clear();
518     bools.clear();
519     reals.clear();
520     rects.clear();
521     colors.clear();
522     matrixes.clear();
523     brushes.clear();
524     pathes.clear();
525     images.clear();
526     pixmaps.clear();
527     reset();
528 }
529
530 void QQuickContext2DCommandBuffer::reset()
531 {
532     cmdIdx = 0;
533     intIdx = 0;
534     boolIdx = 0;
535     realIdx = 0;
536     rectIdx = 0;
537     colorIdx = 0;
538     matrixIdx = 0;
539     brushIdx = 0;
540     pathIdx = 0;
541     imageIdx = 0;
542     pixmapIdx = 0;
543 }
544
545 QT_END_NAMESPACE