1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsgrhitextureglyphcache_p.h"
41#include <qrgb.h>
42#include <private/qdrawhelper_p.h>
43
44QT_BEGIN_NAMESPACE
45
46QSGRhiTextureGlyphCache::QSGRhiTextureGlyphCache(QRhi *rhi, QFontEngine::GlyphFormat format, const QTransform &matrix,
47 const QColor &color)
48 : QImageTextureGlyphCache(format, matrix, color),
49 m_rhi(rhi)
50{
51 // Some OpenGL implementations, for instance macOS, have issues with
52 // GL_ALPHA render targets. Similarly, BGRA may be problematic on GLES 2.0.
53 // So stick with plain image uploads on GL.
54 m_resizeWithTextureCopy = m_rhi->backend() != QRhi::OpenGLES2;
55}
56
57QSGRhiTextureGlyphCache::~QSGRhiTextureGlyphCache()
58{
59 if (m_resourceUpdates)
60 m_resourceUpdates->release();
61
62 delete m_texture;
63
64 // should be empty, but just in case
65 qDeleteAll(m_pendingDispose);
66}
67
68QRhiTexture *QSGRhiTextureGlyphCache::createEmptyTexture(QRhiTexture::Format format)
69{
70 QRhiTexture *t = m_rhi->newTexture(format, m_size, 1, QRhiTexture::UsedAsTransferSource);
71 if (!t->build()) {
72 qWarning("Failed to build new glyph cache texture of size %dx%d", m_size.width(), m_size.height());
73 return nullptr;
74 }
75
76 if (!m_resourceUpdates)
77 m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
78
79 // The new texture must be cleared to 0 always, this cannot be avoided
80 // otherwise artifacts will occur around the glyphs.
81 QByteArray data;
82 if (format == QRhiTexture::RED_OR_ALPHA8)
83 data.fill(0, m_size.width() * m_size.height());
84 else
85 data.fill(0, m_size.width() * m_size.height() * 4);
86 QRhiTextureSubresourceUploadDescription subresDesc(data.constData(), data.size());
87 subresDesc.setSourceSize(m_size);
88 m_resourceUpdates->uploadTexture(t, QRhiTextureUploadEntry(0, 0, subresDesc));
89
90 return t;
91}
92
93void QSGRhiTextureGlyphCache::createTextureData(int width, int height)
94{
95 width = qMax(128, width);
96 height = qMax(32, height);
97
98 if (!m_resizeWithTextureCopy)
99 QImageTextureGlyphCache::createTextureData(width, height);
100
101 m_size = QSize(width, height);
102}
103
104void QSGRhiTextureGlyphCache::resizeTextureData(int width, int height)
105{
106 width = qMax(128, width);
107 height = qMax(32, height);
108
109 if (m_size.width() >= width && m_size.height() >= height)
110 return;
111
112 m_size = QSize(width, height);
113
114 if (m_texture) {
115 QRhiTexture *t = createEmptyTexture(m_texture->format());
116 if (!t)
117 return;
118
119 if (!m_resourceUpdates)
120 m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
121
122 if (m_resizeWithTextureCopy) {
123 m_resourceUpdates->copyTexture(t, m_texture);
124 } else {
125 QImageTextureGlyphCache::resizeTextureData(width, height);
126 QImage img = image();
127 prepareGlyphImage(&img);
128 QRhiTextureSubresourceUploadDescription subresDesc(img);
129 const QSize oldSize = m_texture->pixelSize();
130 subresDesc.setSourceSize(QSize(qMin(oldSize.width(), width), qMin(oldSize.height(), height)));
131 m_resourceUpdates->uploadTexture(t, QRhiTextureUploadEntry(0, 0, subresDesc));
132 }
133
134 m_pendingDispose.insert(m_texture);
135 m_texture = t;
136 }
137}
138
139void QSGRhiTextureGlyphCache::beginFillTexture()
140{
141 Q_ASSERT(m_uploads.isEmpty());
142}
143
144void QSGRhiTextureGlyphCache::prepareGlyphImage(QImage *img)
145{
146 const int maskWidth = img->width();
147 const int maskHeight = img->height();
148 const bool supportsBgra = m_rhi->isTextureFormatSupported(QRhiTexture::BGRA8);
149 m_bgra = false;
150
151 if (img->format() == QImage::Format_Mono) {
152 *img = img->convertToFormat(QImage::Format_Grayscale8);
153 } else if (img->depth() == 32) {
154 if (img->format() == QImage::Format_RGB32 || img->format() == QImage::Format_ARGB32_Premultiplied) {
155 // We need to make the alpha component equal to the average of the RGB values.
156 // This is needed when drawing sub-pixel antialiased text on translucent targets.
157 for (int y = 0; y < maskHeight; ++y) {
158 QRgb *src = (QRgb *) img->scanLine(y);
159 for (int x = 0; x < maskWidth; ++x) {
160 int r = qRed(src[x]);
161 int g = qGreen(src[x]);
162 int b = qBlue(src[x]);
163 int avg;
164 if (img->format() == QImage::Format_RGB32)
165 avg = (r + g + b + 1) / 3; // "+1" for rounding.
166 else // Format_ARGB_Premultiplied
167 avg = qAlpha(src[x]);
168
169 src[x] = qRgba(r, g, b, avg);
170#if Q_BYTE_ORDER != Q_BIG_ENDIAN
171 if (supportsBgra) {
172 m_bgra = true;
173 } else {
174 // swizzle the bits to accommodate for the RGBA upload.
175 src[x] = ARGB2RGBA(src[x]);
176 m_bgra = false;
177 }
178#endif
179 }
180 }
181 }
182 }
183}
184
185void QSGRhiTextureGlyphCache::fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition)
186{
187 QRhiTextureSubresourceUploadDescription subresDesc;
188 QImage mask;
189
190 if (!m_resizeWithTextureCopy) {
191 QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition);
192 mask = image();
193 subresDesc.setSourceTopLeft(QPoint(c.x, c.y));
194 subresDesc.setSourceSize(QSize(c.w, c.h));
195 } else {
196 mask = textureMapForGlyph(glyph, subPixelPosition);
197 }
198
199 prepareGlyphImage(&mask);
200
201 subresDesc.setImage(mask);
202 subresDesc.setDestinationTopLeft(QPoint(c.x, c.y));
203 m_uploads.append(QRhiTextureUploadEntry(0, 0, subresDesc));
204}
205
206void QSGRhiTextureGlyphCache::endFillTexture()
207{
208 if (m_uploads.isEmpty())
209 return;
210
211 if (!m_texture) {
212 QRhiTexture::Format texFormat;
213 if (m_format == QFontEngine::Format_A32 || m_format == QFontEngine::Format_ARGB)
214 texFormat = m_bgra ? QRhiTexture::BGRA8 : QRhiTexture::RGBA8;
215 else // should be R8, but there is the OpenGL ES 2.0 nonsense
216 texFormat = QRhiTexture::RED_OR_ALPHA8;
217
218 m_texture = createEmptyTexture(texFormat);
219 if (!m_texture)
220 return;
221 }
222
223 if (!m_resourceUpdates)
224 m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
225
226 m_resourceUpdates->uploadTexture(m_texture, m_uploads);
227 m_uploads.clear();
228}
229
230int QSGRhiTextureGlyphCache::glyphPadding() const
231{
232 return 1;
233}
234
235int QSGRhiTextureGlyphCache::maxTextureWidth() const
236{
237 return m_rhi->resourceLimit(QRhi::TextureSizeMax);
238}
239
240int QSGRhiTextureGlyphCache::maxTextureHeight() const
241{
242 if (!m_resizeWithTextureCopy)
243 return qMin(1024, m_rhi->resourceLimit(QRhi::TextureSizeMax));
244
245 return m_rhi->resourceLimit(QRhi::TextureSizeMax);
246}
247
248void QSGRhiTextureGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatch *mergeInto)
249{
250 if (m_resourceUpdates) {
251 mergeInto->merge(m_resourceUpdates);
252 m_resourceUpdates->release();
253 m_resourceUpdates = nullptr;
254 }
255
256 // now let's assume the resource updates will be committed in this frame
257 for (QRhiTexture *t : m_pendingDispose)
258 t->releaseAndDestroyLater(); // will be releaseAndDestroyed after the frame is submitted -> safe
259
260 m_pendingDispose.clear();
261}
262
263bool QSGRhiTextureGlyphCache::eightBitFormatIsAlphaSwizzled() const
264{
265 // return true when the shaders for 8-bit formats need .a instead of .r
266 // when sampling the texture
267 return !m_rhi->isFeatureSupported(QRhi::RedOrAlpha8IsRed);
268}
269
270QT_END_NAMESPACE
271