/**************************************************************************** ** ** Copyright (C) 2019 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 "qsgrhitextureglyphcache_p.h" #include #include QT_BEGIN_NAMESPACE QSGRhiTextureGlyphCache::QSGRhiTextureGlyphCache(QRhi *rhi, QFontEngine::GlyphFormat format, const QTransform &matrix, const QColor &color) : QImageTextureGlyphCache(format, matrix, color), m_rhi(rhi) { // Some OpenGL implementations, for instance macOS, have issues with // GL_ALPHA render targets. Similarly, BGRA may be problematic on GLES 2.0. // So stick with plain image uploads on GL. m_resizeWithTextureCopy = m_rhi->backend() != QRhi::OpenGLES2; } QSGRhiTextureGlyphCache::~QSGRhiTextureGlyphCache() { if (m_resourceUpdates) m_resourceUpdates->release(); delete m_texture; // should be empty, but just in case qDeleteAll(m_pendingDispose); } QRhiTexture *QSGRhiTextureGlyphCache::createEmptyTexture(QRhiTexture::Format format) { QRhiTexture *t = m_rhi->newTexture(format, m_size, 1, QRhiTexture::UsedAsTransferSource); if (!t->create()) { qWarning("Failed to build new glyph cache texture of size %dx%d", m_size.width(), m_size.height()); return nullptr; } if (!m_resourceUpdates) m_resourceUpdates = m_rhi->nextResourceUpdateBatch(); // The new texture must be cleared to 0 always, this cannot be avoided // otherwise artifacts will occur around the glyphs. QByteArray data; if (format == QRhiTexture::RED_OR_ALPHA8) data.fill(0, m_size.width() * m_size.height()); else data.fill(0, m_size.width() * m_size.height() * 4); QRhiTextureSubresourceUploadDescription subresDesc(data.constData(), data.size()); subresDesc.setSourceSize(m_size); m_resourceUpdates->uploadTexture(t, QRhiTextureUploadEntry(0, 0, subresDesc)); return t; } void QSGRhiTextureGlyphCache::createTextureData(int width, int height) { width = qMax(128, width); height = qMax(32, height); if (!m_resizeWithTextureCopy) QImageTextureGlyphCache::createTextureData(width, height); m_size = QSize(width, height); } void QSGRhiTextureGlyphCache::resizeTextureData(int width, int height) { width = qMax(128, width); height = qMax(32, height); if (m_size.width() >= width && m_size.height() >= height) return; m_size = QSize(width, height); if (m_texture) { QRhiTexture *t = createEmptyTexture(m_texture->format()); if (!t) return; if (!m_resourceUpdates) m_resourceUpdates = m_rhi->nextResourceUpdateBatch(); if (m_resizeWithTextureCopy) { m_resourceUpdates->copyTexture(t, m_texture); } else { QImageTextureGlyphCache::resizeTextureData(width, height); QImage img = image(); prepareGlyphImage(&img); QRhiTextureSubresourceUploadDescription subresDesc(img); const QSize oldSize = m_texture->pixelSize(); subresDesc.setSourceSize(QSize(qMin(oldSize.width(), width), qMin(oldSize.height(), height))); m_resourceUpdates->uploadTexture(t, QRhiTextureUploadEntry(0, 0, subresDesc)); } m_pendingDispose.insert(m_texture); m_texture = t; } } void QSGRhiTextureGlyphCache::beginFillTexture() { Q_ASSERT(m_uploads.isEmpty()); } void QSGRhiTextureGlyphCache::prepareGlyphImage(QImage *img) { const int maskWidth = img->width(); const int maskHeight = img->height(); #if Q_BYTE_ORDER != Q_BIG_ENDIAN const bool supportsBgra = m_rhi->isTextureFormatSupported(QRhiTexture::BGRA8); #endif m_bgra = false; if (img->format() == QImage::Format_Mono) { *img = img->convertToFormat(QImage::Format_Grayscale8); } else if (img->depth() == 32) { if (img->format() == QImage::Format_RGB32 || img->format() == QImage::Format_ARGB32_Premultiplied) { // We need to make the alpha component equal to the average of the RGB values. // This is needed when drawing sub-pixel antialiased text on translucent targets. for (int y = 0; y < maskHeight; ++y) { QRgb *src = (QRgb *) img->scanLine(y); for (int x = 0; x < maskWidth; ++x) { int r = qRed(src[x]); int g = qGreen(src[x]); int b = qBlue(src[x]); int avg; if (img->format() == QImage::Format_RGB32) avg = (r + g + b + 1) / 3; // "+1" for rounding. else // Format_ARGB_Premultiplied avg = qAlpha(src[x]); src[x] = qRgba(r, g, b, avg); #if Q_BYTE_ORDER != Q_BIG_ENDIAN if (supportsBgra) { m_bgra = true; } else { // swizzle the bits to accommodate for the RGBA upload. src[x] = ARGB2RGBA(src[x]); m_bgra = false; } #endif } } } } } void QSGRhiTextureGlyphCache::fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition) { QRhiTextureSubresourceUploadDescription subresDesc; QImage mask; if (!m_resizeWithTextureCopy) { QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition); mask = image(); subresDesc.setSourceTopLeft(QPoint(c.x, c.y)); subresDesc.setSourceSize(QSize(c.w, c.h)); } else { mask = textureMapForGlyph(glyph, subPixelPosition); } prepareGlyphImage(&mask); subresDesc.setImage(mask); subresDesc.setDestinationTopLeft(QPoint(c.x, c.y)); m_uploads.append(QRhiTextureUploadEntry(0, 0, subresDesc)); } void QSGRhiTextureGlyphCache::endFillTexture() { if (m_uploads.isEmpty()) return; if (!m_texture) { QRhiTexture::Format texFormat; if (m_format == QFontEngine::Format_A32 || m_format == QFontEngine::Format_ARGB) texFormat = m_bgra ? QRhiTexture::BGRA8 : QRhiTexture::RGBA8; else // should be R8, but there is the OpenGL ES 2.0 nonsense texFormat = QRhiTexture::RED_OR_ALPHA8; m_texture = createEmptyTexture(texFormat); if (!m_texture) return; } if (!m_resourceUpdates) m_resourceUpdates = m_rhi->nextResourceUpdateBatch(); QRhiTextureUploadDescription desc; desc.setEntries(m_uploads.cbegin(), m_uploads.cend()); m_resourceUpdates->uploadTexture(m_texture, desc); m_uploads.clear(); } int QSGRhiTextureGlyphCache::glyphPadding() const { return 1; } int QSGRhiTextureGlyphCache::maxTextureWidth() const { return m_rhi->resourceLimit(QRhi::TextureSizeMax); } int QSGRhiTextureGlyphCache::maxTextureHeight() const { if (!m_resizeWithTextureCopy) return qMin(1024, m_rhi->resourceLimit(QRhi::TextureSizeMax)); return m_rhi->resourceLimit(QRhi::TextureSizeMax); } void QSGRhiTextureGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatch *mergeInto) { if (m_resourceUpdates) { mergeInto->merge(m_resourceUpdates); m_resourceUpdates->release(); m_resourceUpdates = nullptr; } // now let's assume the resource updates will be committed in this frame for (QRhiTexture *t : m_pendingDispose) t->deleteLater(); // will be deleted after the frame is submitted -> safe m_pendingDispose.clear(); } bool QSGRhiTextureGlyphCache::eightBitFormatIsAlphaSwizzled() const { // return true when the shaders for 8-bit formats need .a instead of .r // when sampling the texture return !m_rhi->isFeatureSupported(QRhi::RedOrAlpha8IsRed); } QT_END_NAMESPACE