| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** 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 The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/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 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qsgopenglatlastexture_p.h" |
| |
| #include <QtCore/QVarLengthArray> |
| #include <QtCore/QElapsedTimer> |
| #include <QtCore/QtMath> |
| |
| #include <QtGui/QOpenGLContext> |
| #include <QtGui/QOpenGLTexture> |
| #include <QtGui/QOpenGLFunctions> |
| #include <QtGui/QGuiApplication> |
| #include <QtGui/QScreen> |
| #include <QtGui/QSurface> |
| #include <QtGui/QWindow> |
| #include <QtGui/qpa/qplatformnativeinterface.h> |
| |
| #include <private/qqmlglobal_p.h> |
| #include <private/qsgtexture_p.h> |
| #include <private/qsgcompressedtexture_p.h> |
| #include <private/qsgcompressedatlastexture_p.h> |
| |
| #include <private/qquickprofiler_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| #ifndef GL_BGRA |
| #define GL_BGRA 0x80E1 |
| #endif |
| |
| int qt_sg_envInt(const char *name, int defaultValue); |
| |
| static QElapsedTimer qsg_renderer_timer; |
| |
| DEFINE_BOOL_CONFIG_OPTION(qsgEnableCompressedAtlas, QSG_ENABLE_COMPRESSED_ATLAS) |
| |
| namespace QSGOpenGLAtlasTexture |
| { |
| |
| Manager::Manager(const QSize &surfacePixelSize) |
| : m_atlas(nullptr) |
| { |
| QOpenGLContext *gl = QOpenGLContext::currentContext(); |
| Q_ASSERT(gl); |
| int max; |
| gl->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); |
| |
| int w = qMin(max, qt_sg_envInt("QSG_ATLAS_WIDTH", qMax(512U, qNextPowerOfTwo(surfacePixelSize.width() - 1)))); |
| int h = qMin(max, qt_sg_envInt("QSG_ATLAS_HEIGHT", qMax(512U, qNextPowerOfTwo(surfacePixelSize.height() - 1)))); |
| |
| if (gl->surface()->surfaceClass() == QSurface::Window) { |
| QWindow *window = static_cast<QWindow *>(gl->surface()); |
| // Coverwindows, optimize for memory rather than speed |
| if ((window->type() & Qt::CoverWindow) == Qt::CoverWindow) { |
| w /= 2; |
| h /= 2; |
| } |
| } |
| |
| m_atlas_size_limit = qt_sg_envInt("QSG_ATLAS_SIZE_LIMIT", qMax(w, h) / 2); |
| m_atlas_size = QSize(w, h); |
| |
| qCDebug(QSG_LOG_INFO, "opengl texture atlas dimensions: %dx%d", w, h); |
| } |
| |
| Manager::~Manager() |
| { |
| Q_ASSERT(m_atlas == nullptr); |
| Q_ASSERT(m_atlases.isEmpty()); |
| } |
| |
| void Manager::invalidate() |
| { |
| if (m_atlas) { |
| m_atlas->invalidate(); |
| m_atlas->deleteLater(); |
| m_atlas = nullptr; |
| } |
| |
| QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.begin(); |
| while (i != m_atlases.end()) { |
| i.value()->invalidate(); |
| i.value()->deleteLater(); |
| ++i; |
| } |
| m_atlases.clear(); |
| } |
| |
| QSGTexture *Manager::create(const QImage &image, bool hasAlphaChannel) |
| { |
| Texture *t = nullptr; |
| if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) { |
| if (!m_atlas) |
| m_atlas = new Atlas(m_atlas_size); |
| // t may be null for atlas allocation failure |
| t = m_atlas->create(image); |
| if (t && !hasAlphaChannel && t->hasAlphaChannel()) |
| t->setHasAlphaChannel(false); |
| } |
| return t; |
| } |
| |
| QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory) |
| { |
| QSGTexture *t = nullptr; |
| if (!qsgEnableCompressedAtlas() || !factory->m_textureData.isValid()) |
| return t; |
| |
| // TODO: further abstract the atlas and remove this restriction |
| unsigned int format = factory->m_textureData.glInternalFormat(); |
| switch (format) { |
| case QOpenGLTexture::RGB8_ETC1: |
| case QOpenGLTexture::RGB8_ETC2: |
| case QOpenGLTexture::RGBA8_ETC2_EAC: |
| case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2: |
| case QOpenGLTexture::RGB_DXT1: |
| case QOpenGLTexture::RGBA_DXT1: |
| case QOpenGLTexture::RGBA_DXT3: |
| case QOpenGLTexture::RGBA_DXT5: |
| break; |
| default: |
| return t; |
| } |
| |
| QSize size = factory->m_textureData.size(); |
| if (size.width() < m_atlas_size_limit && size.height() < m_atlas_size_limit) { |
| QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.find(format); |
| if (i == m_atlases.end()) { |
| // must be multiple of 4 |
| QSize paddedSize(((m_atlas_size.width() + 3) / 4) * 4, ((m_atlas_size.height() + 3) / 4) * 4); |
| i = m_atlases.insert(format, new QSGCompressedAtlasTexture::Atlas(paddedSize, format)); |
| } |
| |
| // must be multiple of 4 |
| QSize paddedSize(((size.width() + 3) / 4) * 4, ((size.height() + 3) / 4) * 4); |
| QByteArray data = factory->m_textureData.data(); |
| t = i.value()->create(data, factory->m_textureData.dataLength(), factory->m_textureData.dataOffset(), size, paddedSize); |
| } |
| return t; |
| } |
| |
| AtlasBase::AtlasBase(const QSize &size) |
| : m_allocator(size) |
| , m_texture_id(0) |
| , m_size(size) |
| , m_allocated(false) |
| { |
| } |
| |
| AtlasBase::~AtlasBase() |
| { |
| Q_ASSERT(!m_texture_id); |
| } |
| |
| void AtlasBase::invalidate() |
| { |
| if (m_texture_id && QOpenGLContext::currentContext()) |
| QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id); |
| m_texture_id = 0; |
| } |
| |
| int AtlasBase::textureId() const |
| { |
| if (!m_texture_id) { |
| Q_ASSERT(QOpenGLContext::currentContext()); |
| QOpenGLContext::currentContext()->functions()->glGenTextures(1, &const_cast<AtlasBase *>(this)->m_texture_id); |
| } |
| |
| return m_texture_id; |
| } |
| |
| void AtlasBase::bind(QSGTexture::Filtering filtering) |
| { |
| QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
| if (!m_allocated) { |
| m_allocated = true; |
| |
| while (funcs->glGetError() != GL_NO_ERROR) ; |
| |
| funcs->glGenTextures(1, &m_texture_id); |
| funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| #if !defined(QT_OPENGL_ES_2) |
| if (!QOpenGLContext::currentContext()->isOpenGLES()) |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); |
| #endif |
| generateTexture(); |
| |
| GLenum errorCode = funcs->glGetError(); |
| if (errorCode == GL_OUT_OF_MEMORY) { |
| qDebug("QSGTextureAtlas: texture atlas allocation failed, out of memory"); |
| funcs->glDeleteTextures(1, &m_texture_id); |
| m_texture_id = 0; |
| } else if (errorCode != GL_NO_ERROR) { |
| qDebug("QSGTextureAtlas: texture atlas allocation failed, code=%x", errorCode); |
| funcs->glDeleteTextures(1, &m_texture_id); |
| m_texture_id = 0; |
| } |
| } else { |
| funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); |
| } |
| |
| if (m_texture_id == 0) |
| return; |
| |
| // Upload all pending images.. |
| for (int i=0; i<m_pending_uploads.size(); ++i) { |
| |
| bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled(); |
| if (profileFrames) |
| qsg_renderer_timer.start(); |
| |
| Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphTexturePrepare); |
| |
| // Skip bind, convert, swizzle; they're irrelevant |
| Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, |
| QQuickProfiler::SceneGraphTexturePrepareStart, 3); |
| |
| uploadPendingTexture(i); |
| |
| Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, |
| QQuickProfiler::SceneGraphTexturePrepareUpload); |
| |
| // Skip mipmap; unused |
| Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, |
| QQuickProfiler::SceneGraphTexturePrepareUpload, 1); |
| Q_QUICK_SG_PROFILE_REPORT(QQuickProfiler::SceneGraphTexturePrepare, |
| QQuickProfiler::SceneGraphTexturePrepareMipmap); |
| } |
| |
| GLenum f = filtering == QSGTexture::Nearest ? GL_NEAREST : GL_LINEAR; |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, f); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, f); |
| |
| m_pending_uploads.clear(); |
| } |
| |
| void AtlasBase::remove(TextureBase *t) |
| { |
| QRect atlasRect = t->atlasSubRect(); |
| m_allocator.deallocate(atlasRect); |
| m_pending_uploads.removeOne(t); |
| } |
| |
| Atlas::Atlas(const QSize &size) |
| : AtlasBase(size) |
| , m_atlas_transient_image_threshold(0) |
| { |
| |
| m_internalFormat = GL_RGBA; |
| m_externalFormat = GL_BGRA; |
| |
| #ifndef QT_OPENGL_ES |
| if (QOpenGLContext::currentContext()->isOpenGLES()) { |
| #endif |
| |
| #if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) |
| QString *deviceName = |
| static_cast<QString *>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("AndroidDeviceName")); |
| static bool wrongfullyReportsBgra8888Support = deviceName != 0 |
| && (deviceName->compare(QLatin1String("samsung SM-T211"), Qt::CaseInsensitive) == 0 |
| || deviceName->compare(QLatin1String("samsung SM-T210"), Qt::CaseInsensitive) == 0 |
| || deviceName->compare(QLatin1String("samsung SM-T215"), Qt::CaseInsensitive) == 0); |
| #else |
| static bool wrongfullyReportsBgra8888Support = false; |
| // The Raspberry Pi (both 1 and 2) GPU refuses framebuffers with BGRA color attachments. |
| const GLubyte *renderer = QOpenGLContext::currentContext()->functions()->glGetString(GL_RENDERER); |
| if (renderer && strstr((const char *) renderer, "VideoCore IV")) |
| wrongfullyReportsBgra8888Support = true; |
| #endif // ANDROID |
| |
| if (qEnvironmentVariableIsSet("QSG_ATLAS_NO_BGRA_WORKAROUNDS")) |
| wrongfullyReportsBgra8888Support = false; |
| |
| const char *ext = (const char *) QOpenGLContext::currentContext()->functions()->glGetString(GL_EXTENSIONS); |
| if (ext && !wrongfullyReportsBgra8888Support |
| && (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; |
| #if defined(Q_OS_DARWIN) && !defined(Q_OS_OSX) |
| } else if (ext && strstr(ext, "GL_APPLE_texture_format_BGRA8888")) { |
| m_internalFormat = GL_RGBA; |
| m_externalFormat = GL_BGRA; |
| #endif // IOS || TVOS |
| } else { |
| m_internalFormat = m_externalFormat = GL_RGBA; |
| } |
| |
| #ifndef QT_OPENGL_ES |
| } |
| #endif |
| |
| m_use_bgra_fallback = qEnvironmentVariableIsSet("QSG_ATLAS_USE_BGRA_FALLBACK"); |
| m_debug_overlay = qEnvironmentVariableIsSet("QSG_ATLAS_OVERLAY"); |
| |
| // images smaller than this will retain their QImage. |
| // by default no images are retained (favoring memory) |
| // set to a very large value to retain all images (allowing quick removal from the atlas) |
| m_atlas_transient_image_threshold = qt_sg_envInt("QSG_ATLAS_TRANSIENT_IMAGE_THRESHOLD", 0); |
| } |
| |
| Atlas::~Atlas() |
| { |
| } |
| |
| 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 nullptr; |
| } |
| |
| 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) |
| tmp = std::move(tmp).convertToFormat(QImage::Format_RGBA8888_Premultiplied); |
| QOpenGLContext::currentContext()->functions()->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) |
| { |
| QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
| const QRect &r = texture->atlasSubRect(); |
| QImage image = texture->image(); |
| |
| if (image.isNull()) |
| return; |
| |
| 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]; |
| funcs->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]; |
| funcs->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]; |
| funcs->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]; |
| funcs->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.... |
| if (bpl != iw) { |
| int sy = r.y() + 1; |
| int ey = sy + r.height() - 2; |
| for (int y = sy; y < ey; ++y) { |
| funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + 1, y, r.width() - 2, 1, m_externalFormat, GL_UNSIGNED_BYTE, src); |
| src += bpl; |
| } |
| } else { |
| funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, m_externalFormat, GL_UNSIGNED_BYTE, src); |
| } |
| } |
| |
| void Atlas::generateTexture() |
| { |
| QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
| funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, nullptr); |
| |
| #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(); |
| |
| funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, pink.constBits()); |
| #endif |
| } |
| |
| void Atlas::uploadPendingTexture(int i) |
| { |
| Texture *t = static_cast<Texture*>(m_pending_uploads.at(i)); |
| if (m_externalFormat == GL_BGRA && |
| !m_use_bgra_fallback) { |
| uploadBgra(t); |
| } else { |
| upload(t); |
| } |
| const QSize textureSize = t->textureSize(); |
| if (textureSize.width() > m_atlas_transient_image_threshold || |
| textureSize.height() > m_atlas_transient_image_threshold) |
| t->releaseImage(); |
| |
| qCDebug(QSG_LOG_TIME_TEXTURE, "atlastexture uploaded in: %lldms (%dx%d)", |
| qsg_renderer_timer.elapsed(), |
| t->textureSize().width(), |
| t->textureSize().height()); |
| } |
| |
| TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect) |
| : m_allocated_rect(textureRect) |
| , m_atlas(atlas) |
| { |
| } |
| |
| TextureBase::~TextureBase() |
| { |
| m_atlas->remove(this); |
| } |
| |
| void TextureBase::bind() |
| { |
| m_atlas->bind(filtering()); |
| } |
| |
| Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image) |
| : TextureBase(atlas, textureRect) |
| , m_image(image) |
| , m_nonatlas_texture(nullptr) |
| , m_has_alpha(image.hasAlphaChannel()) |
| { |
| qreal w = atlas->size().width(); |
| qreal h = atlas->size().height(); |
| QRect nopad = atlasSubRectWithoutPadding(); |
| m_texture_coords_rect = QRectF(nopad.x() / w, |
| nopad.y() / h, |
| nopad.width() / w, |
| nopad.height() / h); |
| } |
| |
| Texture::~Texture() |
| { |
| if (m_nonatlas_texture) |
| delete m_nonatlas_texture; |
| } |
| |
| QSGTexture *Texture::removedFromAtlas() const |
| { |
| if (m_nonatlas_texture) { |
| m_nonatlas_texture->setMipmapFiltering(mipmapFiltering()); |
| m_nonatlas_texture->setFiltering(filtering()); |
| return m_nonatlas_texture; |
| } |
| |
| if (!m_image.isNull()) { |
| m_nonatlas_texture = new QSGPlainTexture(); |
| m_nonatlas_texture->setImage(m_image); |
| m_nonatlas_texture->setFiltering(filtering()); |
| |
| } else { |
| QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); |
| // bind the atlas texture as an fbo and extract the texture.. |
| |
| // First extract the currently bound fbo so we can restore it later. |
| GLint currentFbo; |
| f->glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFbo); |
| |
| // Create an FBO and bind the atlas texture into it. |
| GLuint fbo; |
| f->glGenFramebuffers(1, &fbo); |
| f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_atlas->textureId(), 0); |
| |
| // Create the target texture, QSGPlainTexture below will deal with the texparams, so we don't |
| // need to worry about those here. |
| GLuint texture; |
| f->glGenTextures(1, &texture); |
| f->glBindTexture(GL_TEXTURE_2D, texture); |
| QRect r = atlasSubRectWithoutPadding(); |
| // and copy atlas into our texture. |
| while (f->glGetError() != GL_NO_ERROR) ; |
| f->glCopyTexImage2D(GL_TEXTURE_2D, 0, static_cast<Atlas*>(m_atlas)->internalFormat(), r.x(), r.y(), r.width(), r.height(), 0); |
| // BGRA may have been rejected by some GLES implementations |
| if (f->glGetError() != GL_NO_ERROR) |
| f->glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, r.x(), r.y(), r.width(), r.height(), 0); |
| |
| m_nonatlas_texture = new QSGPlainTexture(); |
| m_nonatlas_texture->setTextureId(texture); |
| m_nonatlas_texture->setOwnsTexture(true); |
| m_nonatlas_texture->setHasAlphaChannel(m_has_alpha); |
| m_nonatlas_texture->setTextureSize(r.size()); |
| |
| // cleanup: unbind our atlas from the fbo, rebind the old default and delete the fbo. |
| f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); |
| f->glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) currentFbo); |
| f->glDeleteFramebuffers(1, &fbo); |
| } |
| |
| m_nonatlas_texture->setMipmapFiltering(mipmapFiltering()); |
| m_nonatlas_texture->setFiltering(filtering()); |
| return m_nonatlas_texture; |
| } |
| |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qsgopenglatlastexture_p.cpp" |