blob: 89c9ea4d5b189b289b5a871997286e7a8c219566 [file] [log] [blame]
/****************************************************************************
**
** 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 <qrgb.h>
#include <private/qdrawhelper_p.h>
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->build()) {
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->releaseAndDestroyLater(); // will be releaseAndDestroyed 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