aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/util/qsgatlastexture.cpp
diff options
context:
space:
mode:
authorGunnar Sletta <gunnar.sletta@digia.com>2013-08-14 07:27:07 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-02 14:24:36 +0200
commitb480fa83a632b2ae5606e2870b47358328b479a2 (patch)
treebdd3e1b68a5a15a3950e13a50db911a93cdf279a /src/quick/scenegraph/util/qsgatlastexture.cpp
parent9be35c270082d1614886874e17cc3f90a7a3f489 (diff)
New scenegraph renderer and atlas textures.
The renderer tries to batch primitives together where possible, isolate non-changing subparts of the scene from changing subparts and retain vertexdata on the GPU as much as possible. Atlas textures are crucial in enabling batching. The renderer and atlas texture are described in detail in the doc page "Qt Quick Scene Graph Renderer". Change-Id: Ia476c7f0f42e1fc57a2cef528e93ee88cf8f7055 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
Diffstat (limited to 'src/quick/scenegraph/util/qsgatlastexture.cpp')
-rw-r--r--src/quick/scenegraph/util/qsgatlastexture.cpp416
1 files changed, 416 insertions, 0 deletions
diff --git a/src/quick/scenegraph/util/qsgatlastexture.cpp b/src/quick/scenegraph/util/qsgatlastexture.cpp
new file mode 100644
index 0000000000..ad90911b9c
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgatlastexture.cpp
@@ -0,0 +1,416 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgatlastexture_p.h"
+
+#include <QtCore/QVarLengthArray>
+#include <QtCore/QElapsedTimer>
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QScreen>
+
+#include <private/qsgtexture_p.h>
+
+#ifndef GL_BGRA
+#define GL_BGRA 0x80E1
+#endif
+
+
+#ifndef QSG_NO_RENDERER_TIMING
+static bool qsg_render_timing = !qgetenv("QSG_RENDERER_TIMING").isEmpty();
+#endif
+
+namespace QSGAtlasTexture
+{
+
+static inline int qsg_powerOfTwo(int v)
+{
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ ++v;
+ return v;
+}
+
+static int qsg_envInt(const char *name, int defaultValue)
+{
+ QByteArray content = qgetenv(name);
+
+ bool ok = false;
+ int value = content.toInt(&ok);
+ return ok ? value : defaultValue;
+}
+
+Manager::Manager()
+ : m_atlas(0)
+{
+ QSize screenSize = QGuiApplication::primaryScreen()->geometry().size();
+ int max;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
+ int w = qMin(max, qsg_envInt("QSG_ATLAS_WIDTH", qsg_powerOfTwo(screenSize.width())));
+ int h = qMin(max, qsg_envInt("QSG_ATLAS_HEIGHT", qsg_powerOfTwo(screenSize.height())));
+
+ m_atlas_size_limit = qsg_envInt("QSG_ATLAS_SIZE_LIMIT", qMax(w, h) / 2);
+ m_atlas_size = QSize(w, h);
+}
+
+
+Manager::~Manager()
+{
+ invalidate();
+}
+
+void Manager::invalidate()
+{
+ delete m_atlas;
+ m_atlas = 0;
+}
+
+QSGTexture *Manager::create(const QImage &image)
+{
+ QSGTexture *t = 0;
+ if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) {
+ if (!m_atlas)
+ m_atlas = new Atlas(m_atlas_size);
+ t = m_atlas->create(image);
+ if (t)
+ return t;
+ }
+
+ return t;
+}
+
+Atlas::Atlas(const QSize &size)
+ : m_allocator(size)
+ , m_texture_id(0)
+ , m_size(size)
+ , m_filtering(QSGTexture::Linear)
+ , m_allocated(false)
+{
+
+#ifdef QT_OPENGL_ES
+ const char *ext = (const char *) glGetString(GL_EXTENSIONS);
+ if (strstr(ext, "GL_EXT_bgra")
+ || strstr(ext, "GL_EXT_texture_format_BGRA8888")
+ || strstr(ext, "GL_IMG_texture_format_BGRA8888")) {
+ m_internalFormat = m_externalFormat = GL_BGRA;
+ } else {
+ m_internalFormat = m_externalFormat = GL_RGBA;
+ }
+#else
+ m_internalFormat = GL_RGBA;
+ m_externalFormat = GL_BGRA;
+#endif
+
+ m_use_bgra_fallback = qEnvironmentVariableIsSet("QSG_ATLAS_USE_BGRA_FALLBACK");
+ m_debug_overlay = qEnvironmentVariableIsSet("QSG_ATLAS_OVERLAY");
+}
+
+Atlas::~Atlas()
+{
+ if (m_texture_id)
+ glDeleteTextures(1, &m_texture_id);
+}
+
+
+Texture *Atlas::create(const QImage &image)
+{
+ QRect rect = m_allocator.allocate(QSize(image.width() + 2, image.height() + 2));
+ if (rect.width() > 0 && rect.height() > 0) {
+ Texture *t = new Texture(this, rect, image);
+ m_pending_uploads << t;
+ return t;
+ }
+ return 0;
+}
+
+
+int Atlas::textureId() const
+{
+ if (!m_texture_id) {
+ Q_ASSERT(QOpenGLContext::currentContext());
+ glGenTextures(1, &const_cast<Atlas *>(this)->m_texture_id);
+ }
+
+ return m_texture_id;
+}
+
+static void swizzleBGRAToRGBA(QImage *image)
+{
+ const int width = image->width();
+ const int height = image->height();
+ uint *p = (uint *) image->bits();
+ int stride = image->bytesPerLine() / 4;
+ for (int i = 0; i < height; ++i) {
+ for (int x = 0; x < width; ++x)
+ p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
+ p += stride;
+ }
+}
+
+void Atlas::upload(Texture *texture)
+{
+ const QImage &image = texture->image();
+ const QRect &r = texture->atlasSubRect();
+
+ QImage tmp(r.width(), r.height(), QImage::Format_ARGB32_Premultiplied);
+ {
+ QPainter p(&tmp);
+ p.setCompositionMode(QPainter::CompositionMode_Source);
+
+ int w = r.width();
+ int h = r.height();
+ int iw = image.width();
+ int ih = image.height();
+
+ p.drawImage(1, 1, image);
+ p.drawImage(1, 0, image, 0, 0, iw, 1);
+ p.drawImage(1, h - 1, image, 0, ih - 1, iw, 1);
+ p.drawImage(0, 1, image, 0, 0, 1, ih);
+ p.drawImage(w - 1, 1, image, iw - 1, 0, 1, ih);
+ p.drawImage(0, 0, image, 0, 0, 1, 1);
+ p.drawImage(0, h - 1, image, 0, ih - 1, 1, 1);
+ p.drawImage(w - 1, 0, image, iw - 1, 0, 1, 1);
+ p.drawImage(w - 1, h - 1, image, iw - 1, ih - 1, 1, 1);
+ if (m_debug_overlay) {
+ p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
+ p.fillRect(0, 0, iw, ih, QBrush(QColor::fromRgbF(1, 0, 1, 0.5)));
+ }
+ }
+
+ if (m_externalFormat == GL_RGBA)
+ swizzleBGRAToRGBA(&tmp);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y(), r.width(), r.height(), m_externalFormat, GL_UNSIGNED_BYTE, tmp.constBits());
+}
+
+void Atlas::uploadBgra(Texture *texture)
+{
+ const QRect &r = texture->atlasSubRect();
+ QImage image = texture->image();
+
+ if (image.format() != QImage::Format_ARGB32_Premultiplied
+ || image.format() != QImage::Format_RGB32) {
+ image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ }
+
+ if (m_debug_overlay) {
+ QPainter p(&image);
+ p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
+ p.fillRect(0, 0, image.width(), image.height(), QBrush(QColor::fromRgbF(0, 1, 1, 0.5)));
+ }
+
+ QVarLengthArray<quint32, 512> tmpBits(qMax(image.width() + 2, image.height() + 2));
+ int iw = image.width();
+ int ih = image.height();
+ int bpl = image.bytesPerLine() / 4;
+ const quint32 *src = (const quint32 *) image.constBits();
+ quint32 *dst = tmpBits.data();
+
+ // top row, padding corners
+ dst[0] = src[0];
+ memcpy(dst + 1, src, iw * sizeof(quint32));
+ dst[1 + iw] = src[iw-1];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y(), iw + 2, 1, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // bottom row, padded corners
+ const quint32 *lastRow = src + bpl * (ih - 1);
+ dst[0] = lastRow[0];
+ memcpy(dst + 1, lastRow, iw * sizeof(quint32));
+ dst[1 + iw] = lastRow[iw-1];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y() + ih + 1, iw + 2, 1, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // left column
+ for (int i=0; i<ih; ++i)
+ dst[i] = src[i * bpl];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y() + 1, 1, ih, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // right column
+ for (int i=0; i<ih; ++i)
+ dst[i] = src[i * bpl + iw - 1];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + iw + 1, r.y() + 1, 1, ih, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // Inner part of the image....
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, m_externalFormat, GL_UNSIGNED_BYTE, src);
+
+}
+
+bool Atlas::bind(QSGTexture::Filtering filtering)
+{
+ bool forceUpdate = false;
+ if (!m_allocated) {
+ m_allocated = true;
+
+ while (glGetError() != GL_NO_ERROR) ;
+
+ glGenTextures(1, &m_texture_id);
+ glBindTexture(GL_TEXTURE_2D, m_texture_id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, 0);
+
+#if 0
+ QImage pink(m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied);
+ pink.fill(0);
+ QPainter p(&pink);
+ QLinearGradient redGrad(0, 0, m_size.width(), 0);
+ redGrad.setColorAt(0, Qt::black);
+ redGrad.setColorAt(1, Qt::red);
+ p.fillRect(0, 0, m_size.width(), m_size.height(), redGrad);
+ p.setCompositionMode(QPainter::CompositionMode_Plus);
+ QLinearGradient blueGrad(0, 0, 0, m_size.height());
+ blueGrad.setColorAt(0, Qt::black);
+ blueGrad.setColorAt(1, Qt::blue);
+ p.fillRect(0, 0, m_size.width(), m_size.height(), blueGrad);
+ p.end();
+
+ glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, pink.constBits());
+#endif
+
+ GLenum errorCode = glGetError();
+ if (errorCode == GL_OUT_OF_MEMORY) {
+ qDebug("QSGTextureAtlas: texture atlas allocation failed, out of memory");
+ glDeleteTextures(1, &m_texture_id);
+ m_texture_id = 0;
+ } else if (errorCode != GL_NO_ERROR) {
+ qDebug("QSGTextureAtlas: texture atlas allocation failed, code=%x", errorCode);
+ glDeleteTextures(1, &m_texture_id);
+ m_texture_id = 0;
+ }
+ forceUpdate = true;
+ } else {
+ glBindTexture(GL_TEXTURE_2D, m_texture_id);
+ }
+
+ if (m_texture_id == 0)
+ return false;
+
+ // Upload all pending images..
+ for (int i=0; i<m_pending_uploads.size(); ++i) {
+
+#ifndef QSG_NO_RENDERER_TIMING
+ QElapsedTimer timer;
+ if (qsg_render_timing)
+ timer.start();
+#endif
+
+ if (m_externalFormat == GL_BGRA &&
+ !m_use_bgra_fallback) {
+ uploadBgra(m_pending_uploads.at(i));
+ } else {
+ upload(m_pending_uploads.at(i));
+ }
+#ifndef QSG_NO_RENDERER_TIMING
+ if (qsg_render_timing) {
+ printf(" - AtlasTexture(%dx%d), uploaded in %d ms\n",
+ m_pending_uploads.at(i)->image().width(),
+ m_pending_uploads.at(i)->image().height(),
+ (int) timer.elapsed());
+ }
+#endif
+ }
+
+ if (filtering != m_filtering) {
+ GLenum f = filtering == QSGTexture::Nearest ? GL_NEAREST : GL_LINEAR;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, f);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, f);
+ m_filtering = filtering;
+ }
+
+ m_pending_uploads.clear();
+
+ return forceUpdate;
+}
+
+void Atlas::remove(Texture *t)
+{
+ QRect atlasRect = t->atlasSubRect();
+ m_allocator.deallocate(atlasRect);
+
+ m_pending_uploads.removeOne(t);
+}
+
+
+
+Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image)
+ : QSGTexture()
+ , m_allocated_rect(textureRect)
+ , m_image(image)
+ , m_atlas(atlas)
+ , m_nonatlas_texture(0)
+{
+ m_allocated_rect_without_padding = m_allocated_rect.adjusted(1, 1, -1, -1);
+ float w = atlas->size().width();
+ float h = atlas->size().height();
+
+ m_texture_coords_rect = QRectF(m_allocated_rect_without_padding.x() / w,
+ m_allocated_rect_without_padding.y() / h,
+ m_allocated_rect_without_padding.width() / w,
+ m_allocated_rect_without_padding.height() / h);
+}
+
+Texture::~Texture()
+{
+ m_atlas->remove(this);
+ if (m_nonatlas_texture)
+ delete m_nonatlas_texture;
+}
+
+void Texture::bind()
+{
+ m_atlas->bind(filtering());
+}
+
+QSGTexture *Texture::removedFromAtlas() const
+{
+ if (!m_nonatlas_texture) {
+ m_nonatlas_texture = new QSGPlainTexture();
+ m_nonatlas_texture->setImage(m_image);
+ }
+ return m_nonatlas_texture;
+}
+
+}