diff options
Diffstat (limited to 'src/quick/scenegraph/util/qsgatlastexture.cpp')
-rw-r--r-- | src/quick/scenegraph/util/qsgatlastexture.cpp | 416 |
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; +} + +} |