/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif #ifndef QSG_NO_RENDER_TIMING static bool qsg_render_timing = !qgetenv("QSG_RENDER_TIMING").isEmpty(); static QElapsedTimer qsg_renderer_timer; #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) { QOpenGLContext *gl = QOpenGLContext::currentContext(); Q_ASSERT(gl); QSurface *surface = gl->surface(); QSize surfaceSize = surface->size(); int max; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); int w = qMin(max, qsg_envInt("QSG_ATLAS_WIDTH", qMax(512, qsg_powerOfTwo(surfaceSize.width())))); int h = qMin(max, qsg_envInt("QSG_ATLAS_HEIGHT", qMax(512, qsg_powerOfTwo(surfaceSize.height())))); m_atlas_size_limit = qsg_envInt("QSG_ATLAS_SIZE_LIMIT", qMax(w, h) / 2); m_atlas_size = QSize(w, h); if (qEnvironmentVariableIsSet("QSG_INFO")) qDebug() << "QSG: texture atlas dimensions:" << w << "x" << h; } Manager::~Manager() { Q_ASSERT(m_atlas == 0); } void Manager::invalidate() { if (m_atlas) { m_atlas->invalidate(); m_atlas->deleteLater(); 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); } 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; #ifdef Q_OS_IOS } else if (strstr(ext, "GL_APPLE_texture_format_BGRA8888")) { m_internalFormat = GL_RGBA; m_externalFormat = GL_BGRA; #endif } 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() { Q_ASSERT(!m_texture_id); } void Atlas::invalidate() { Q_ASSERT(QOpenGLContext::currentContext()); if (m_texture_id) { glDeleteTextures(1, &m_texture_id); m_texture_id = 0; } } Texture *Atlas::create(const QImage &image) { // No need to lock, as manager already locked it. 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(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 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; iimage().width(), m_pending_uploads.at(i)->image().height(), (int) (qsg_renderer_timer.elapsed())); } if (QQmlProfilerService::enabled) { QQmlProfilerService::sceneGraphFrame( QQmlProfilerService::SceneGraphTexturePrepare, 0, // bind (not relevant) 0, // convert (not relevant) 0, // swizzle (not relevant) qsg_renderer_timer.nsecsElapsed(), // (upload all of the above) 0); // mipmap (not used ever...) } #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); m_nonatlas_texture->setFiltering(filtering()); } return m_nonatlas_texture; } } QT_END_NAMESPACE