1 #include "qsgcontext2dtexture_p.h"
2 #include "qsgcontext2dtile_p.h"
3 #include "qsgcanvasitem_p.h"
5 #include "private/qsgtexture_p.h"
6 #include "qsgcontext2dcommandbuffer_p.h"
8 #include <QtOpenGL/QGLFramebufferObject>
9 #include <QtOpenGL/QGLFramebufferObjectFormat>
10 #include <QtCore/QThread>
12 #define QT_MINIMUM_FBO_SIZE 64
14 static inline int qt_next_power_of_two(int v)
27 Q_GLOBAL_STATIC(QThread, globalCanvasThreadRenderInstance)
30 QSGContext2DTexture::QSGContext2DTexture()
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)
40 , m_tiledCanvas(false)
41 , m_doGrabImage(false)
46 QSGContext2DTexture::~QSGContext2DTexture()
51 QSize QSGContext2DTexture::textureSize() const
53 return m_canvasWindow.size();
56 void QSGContext2DTexture::markDirtyTexture()
59 m_dirtyTexture = true;
61 emit textureChanged();
64 bool QSGContext2DTexture::setCanvasSize(const QSize &size)
66 if (m_canvasSize != size) {
74 bool QSGContext2DTexture::setTileSize(const QSize &size)
76 if (m_tileSize != size) {
84 void QSGContext2DTexture::setSmooth(bool smooth)
89 void QSGContext2DTexture::setItem(QSGCanvasItem* item)
97 } else if (m_item != item) {
100 m_context = item->context();
101 m_state = m_context->state;
103 connect(this, SIGNAL(textureChanged()), m_item, SIGNAL(painted()), Qt::QueuedConnection);
104 canvasChanged(item->canvasSize().toSize()
106 , item->canvasWindow().toAlignedRect()
107 , item->canvasWindow().toAlignedRect()
112 bool QSGContext2DTexture::setCanvasWindow(const QRect& r)
114 if (m_canvasWindow != r) {
119 bool QSGContext2DTexture::setDirtyRect(const QRect &r)
121 bool doDirty = false;
122 foreach (QSGContext2DTile* t, m_tiles) {
123 bool dirty = t->rect().intersected(r).isValid();
131 void QSGContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
136 if (ts.width() > canvasSize.width())
137 ts.setWidth(canvasSize.width());
139 if (ts.height() > canvasSize.height())
140 ts.setHeight(canvasSize.height());
142 bool canvasChanged = setCanvasSize(canvasSize);
143 bool tileChanged = setTileSize(ts);
145 bool doDirty = false;
146 if (canvasSize == canvasWindow.size()) {
147 m_tiledCanvas = false;
148 m_dirtyCanvas = false;
150 m_tiledCanvas = true;
151 if (dirtyRect.isValid())
152 doDirty = setDirtyRect(dirtyRect);
155 bool windowChanged = setCanvasWindow(canvasWindow);
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);
168 void QSGContext2DTexture::paintWithoutTiles()
170 QSGContext2DCommandBuffer* ccb = m_context->buffer();
172 if (ccb->isEmpty() && m_threadRendering && !m_doGrabImage) {
175 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, QRectF(0, 0, m_canvasSize.width(), m_canvasSize.height())));
179 if (ccb->isEmpty()) {
183 QPaintDevice* device = beginPainting();
192 p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
193 | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
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);
205 bool QSGContext2DTexture::canvasDestroyed()
207 bool noCanvas = false;
209 noCanvas = m_item == 0;
214 void QSGContext2DTexture::paint()
216 if (canvasDestroyed())
219 if (!m_tiledCanvas) {
222 QSGContext2D::State oldState = m_state;
223 QSGContext2DCommandBuffer* ccb = m_context->buffer();
226 QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
229 if (!tiledRegion.isEmpty()) {
230 if (m_threadRendering && !m_doGrabImage) {
234 foreach (QSGContext2DTile* tile, m_tiles) {
236 if (dirtyRect.isEmpty())
237 dirtyRect = tile->rect();
239 dirtyRect |= tile->rect();
244 if (dirtyRect.isValid()) {
247 QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, dirtyRect));
253 if (beginPainting()) {
254 foreach (QSGContext2DTile* tile, m_tiles) {
255 bool dirtyTile = false, dirtyCanvas = false, smooth = false;
258 dirtyTile = tile->dirty();
260 dirtyCanvas = m_dirtyCanvas;
263 //canvas size or tile size may change during painting tiles
265 if (m_threadRendering)
266 QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
269 } else if (dirtyTile) {
270 m_state = ccb->replay(tile->createPainter(smooth), oldState);
273 tile->markDirty(false);
287 QRect QSGContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
289 if (window.isEmpty())
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;
297 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
298 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
300 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
303 QRect QSGContext2DTexture::createTiles(const QRect& window)
305 QList<QSGContext2DTile*> oldTiles = m_tiles;
308 if (window.isEmpty()) {
309 m_dirtyCanvas = false;
313 QRect r = tiledRect(window, m_tileSize);
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;
321 const int htiles = r.width() / tw;
322 const int vtiles = r.height() / th;
324 for (int yy = 0; yy < vtiles; ++yy) {
325 for (int xx = 0; xx < htiles; ++xx) {
329 QSGContext2DTile* tile = 0;
331 QPoint pos(ht * tw, vt * th);
332 QRect rect(pos, m_tileSize);
334 for (int i = 0; i < oldTiles.size(); i++) {
335 if (oldTiles[i]->rect() == rect) {
336 tile = oldTiles.takeAt(i);
345 m_tiles.append(tile);
349 qDeleteAll(oldTiles);
351 m_dirtyCanvas = false;
355 void QSGContext2DTexture::clearTiles()
361 QSGContext2DFBOTexture::QSGContext2DFBOTexture()
362 : QSGContext2DTexture()
365 m_threadRendering = false;
368 bool QSGContext2DFBOTexture::setCanvasSize(const QSize &size)
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())));
373 if (m_canvasSize != s) {
375 m_dirtyCanvas = true;
381 bool QSGContext2DFBOTexture::setTileSize(const QSize &size)
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) {
387 m_dirtyCanvas = true;
393 bool QSGContext2DFBOTexture::setCanvasWindow(const QRect& canvasWindow)
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())));
399 bool doChanged = false;
400 if (m_fboSize != s) {
405 if (m_canvasWindow != canvasWindow)
406 m_canvasWindow = canvasWindow;
411 void QSGContext2DFBOTexture::bind()
413 glBindTexture(GL_TEXTURE_2D, textureId());
417 QRectF QSGContext2DFBOTexture::textureSubRect() const
421 , qreal(m_canvasWindow.width()) / m_fboSize.width()
422 , qreal(-m_canvasWindow.height()) / m_fboSize.height());
426 int QSGContext2DFBOTexture::textureId() const
428 return m_fbo? m_fbo->texture() : 0;
432 bool QSGContext2DFBOTexture::updateTexture()
434 if (!m_context->buffer()->isEmpty()) {
438 bool textureUpdated = m_dirtyTexture;
440 m_dirtyTexture = false;
444 m_condition.wakeOne();
445 m_doGrabImage = false;
447 return textureUpdated;
450 QSGContext2DTile* QSGContext2DFBOTexture::createTile() const
452 return new QSGContext2DFBOTile();
455 void QSGContext2DFBOTexture::grabImage()
458 m_grabedImage = m_fbo->toImage();
462 QImage QSGContext2DFBOTexture::toImage(const QRectF& region)
464 #define QML_CONTEXT2D_WAIT_MAX 5000
466 m_doGrabImage = true;
472 bool ok = m_condition.wait(&m_mutex, QML_CONTEXT2D_WAIT_MAX);
477 if (region.isValid())
478 grabbed = m_grabedImage.copy(region.toRect());
480 grabbed = m_grabedImage;
481 m_grabedImage = QImage();
485 void QSGContext2DFBOTexture::compositeTile(QSGContext2DTile* tile)
487 QSGContext2DFBOTile* t = static_cast<QSGContext2DFBOTile*>(tile);
488 QRect target = t->rect().intersect(m_canvasWindow);
489 if (target.isValid()) {
490 QRect source = target;
492 source.moveTo(source.topLeft() - t->rect().topLeft());
493 target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
495 QGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
498 QSGCanvasItem::RenderTarget QSGContext2DFBOTexture::renderTarget() const
500 return QSGCanvasItem::FramebufferObject;
502 QPaintDevice* QSGContext2DFBOTexture::beginPainting()
504 QSGContext2DTexture::beginPainting();
506 if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
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);
516 glDisable(GL_DEPTH_TEST);
519 m_fbo = new QGLFramebufferObject(m_fboSize, format);
520 glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
521 updateBindOptions(false);
526 void qt_quit_context2d_render_thread()
528 QThread* thread = globalCanvasThreadRenderInstance();
533 QSGContext2DImageTexture::QSGContext2DImageTexture(bool threadRendering)
534 : QSGContext2DTexture()
535 , m_texture(new QSGPlainTexture())
537 m_texture->setOwnsTexture(true);
538 m_texture->setHasMipmaps(false);
540 m_threadRendering = threadRendering;
542 if (m_threadRendering) {
543 QThread* thread = globalCanvasThreadRenderInstance();
544 moveToThread(thread);
546 if (!thread->isRunning()) {
547 qAddPostRoutine(qt_quit_context2d_render_thread);
553 QSGContext2DImageTexture::~QSGContext2DImageTexture()
555 m_texture->deleteLater();
558 int QSGContext2DImageTexture::textureId() const
560 return m_texture->textureId();
563 void QSGContext2DImageTexture::lock()
565 if (m_threadRendering)
568 void QSGContext2DImageTexture::unlock()
570 if (m_threadRendering)
574 void QSGContext2DImageTexture::wait()
576 if (m_threadRendering)
577 m_waitCondition.wait(&m_mutex);
580 void QSGContext2DImageTexture::wake()
582 if (m_threadRendering)
583 m_waitCondition.wakeOne();
586 bool QSGContext2DImageTexture::supportDirectRendering() const
588 return !m_threadRendering;
591 QSGCanvasItem::RenderTarget QSGContext2DImageTexture::renderTarget() const
593 return QSGCanvasItem::Image;
596 void QSGContext2DImageTexture::bind()
601 bool QSGContext2DImageTexture::updateTexture()
604 bool textureUpdated = m_dirtyTexture;
605 if (m_dirtyTexture) {
606 m_texture->setImage(m_image);
607 m_dirtyTexture = false;
610 return textureUpdated;
613 QSGContext2DTile* QSGContext2DImageTexture::createTile() const
615 return new QSGContext2DImageTile();
618 void QSGContext2DImageTexture::grabImage(const QRect& r)
620 m_doGrabImage = true;
622 m_doGrabImage = false;
623 m_grabedImage = m_image.copy(r);
626 QImage QSGContext2DImageTexture::toImage(const QRectF& region)
628 QRect r = region.isValid() ? region.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
629 if (threadRendering()) {
631 QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
633 QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
635 QImage image = m_grabedImage;
636 m_grabedImage = QImage();
640 QPaintDevice* QSGContext2DImageTexture::beginPainting()
642 QSGContext2DTexture::beginPainting();
644 if (m_canvasWindow.size().isEmpty())
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);
656 void QSGContext2DImageTexture::compositeTile(QSGContext2DTile* tile)
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());
667 m_painter.begin(&m_image);
668 m_painter.setCompositionMode(QPainter::CompositionMode_Source);
669 m_painter.drawImage(target, t->image(), source);