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
44 QT_BEGIN_NAMESPACE
45
QSGRhiTextureGlyphCache(QRhi * rhi,QFontEngine::GlyphFormat format,const QTransform & matrix,const QColor & color)46 QSGRhiTextureGlyphCache::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
~QSGRhiTextureGlyphCache()57 QSGRhiTextureGlyphCache::~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
createEmptyTexture(QRhiTexture::Format format)68 QRhiTexture *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
createTextureData(int width,int height)93 void 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
resizeTextureData(int width,int height)104 void 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
beginFillTexture()139 void QSGRhiTextureGlyphCache::beginFillTexture()
140 {
141 Q_ASSERT(m_uploads.isEmpty());
142 }
143
prepareGlyphImage(QImage * img)144 void QSGRhiTextureGlyphCache::prepareGlyphImage(QImage *img)
145 {
146 const int maskWidth = img->width();
147 const int maskHeight = img->height();
148 #if Q_BYTE_ORDER != Q_BIG_ENDIAN
149 const bool supportsBgra = m_rhi->isTextureFormatSupported(QRhiTexture::BGRA8);
150 #endif
151 m_bgra = false;
152
153 if (img->format() == QImage::Format_Mono) {
154 *img = img->convertToFormat(QImage::Format_Grayscale8);
155 } else if (img->depth() == 32) {
156 if (img->format() == QImage::Format_RGB32 || img->format() == QImage::Format_ARGB32_Premultiplied) {
157 // We need to make the alpha component equal to the average of the RGB values.
158 // This is needed when drawing sub-pixel antialiased text on translucent targets.
159 for (int y = 0; y < maskHeight; ++y) {
160 QRgb *src = (QRgb *) img->scanLine(y);
161 for (int x = 0; x < maskWidth; ++x) {
162 int r = qRed(src[x]);
163 int g = qGreen(src[x]);
164 int b = qBlue(src[x]);
165 int avg;
166 if (img->format() == QImage::Format_RGB32)
167 avg = (r + g + b + 1) / 3; // "+1" for rounding.
168 else // Format_ARGB_Premultiplied
169 avg = qAlpha(src[x]);
170
171 src[x] = qRgba(r, g, b, avg);
172 #if Q_BYTE_ORDER != Q_BIG_ENDIAN
173 if (supportsBgra) {
174 m_bgra = true;
175 } else {
176 // swizzle the bits to accommodate for the RGBA upload.
177 src[x] = ARGB2RGBA(src[x]);
178 m_bgra = false;
179 }
180 #endif
181 }
182 }
183 }
184 }
185 }
186
fillTexture(const Coord & c,glyph_t glyph,QFixed subPixelPosition)187 void QSGRhiTextureGlyphCache::fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition)
188 {
189 QRhiTextureSubresourceUploadDescription subresDesc;
190 QImage mask;
191
192 if (!m_resizeWithTextureCopy) {
193 QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition);
194 mask = image();
195 subresDesc.setSourceTopLeft(QPoint(c.x, c.y));
196 subresDesc.setSourceSize(QSize(c.w, c.h));
197 } else {
198 mask = textureMapForGlyph(glyph, subPixelPosition);
199 }
200
201 prepareGlyphImage(&mask);
202
203 subresDesc.setImage(mask);
204 subresDesc.setDestinationTopLeft(QPoint(c.x, c.y));
205 m_uploads.append(QRhiTextureUploadEntry(0, 0, subresDesc));
206 }
207
endFillTexture()208 void QSGRhiTextureGlyphCache::endFillTexture()
209 {
210 if (m_uploads.isEmpty())
211 return;
212
213 if (!m_texture) {
214 QRhiTexture::Format texFormat;
215 if (m_format == QFontEngine::Format_A32 || m_format == QFontEngine::Format_ARGB)
216 texFormat = m_bgra ? QRhiTexture::BGRA8 : QRhiTexture::RGBA8;
217 else // should be R8, but there is the OpenGL ES 2.0 nonsense
218 texFormat = QRhiTexture::RED_OR_ALPHA8;
219
220 m_texture = createEmptyTexture(texFormat);
221 if (!m_texture)
222 return;
223 }
224
225 if (!m_resourceUpdates)
226 m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
227
228 QRhiTextureUploadDescription desc;
229 desc.setEntries(m_uploads.cbegin(), m_uploads.cend());
230 m_resourceUpdates->uploadTexture(m_texture, desc);
231 m_uploads.clear();
232 }
233
glyphPadding() const234 int QSGRhiTextureGlyphCache::glyphPadding() const
235 {
236 return 1;
237 }
238
maxTextureWidth() const239 int QSGRhiTextureGlyphCache::maxTextureWidth() const
240 {
241 return m_rhi->resourceLimit(QRhi::TextureSizeMax);
242 }
243
maxTextureHeight() const244 int QSGRhiTextureGlyphCache::maxTextureHeight() const
245 {
246 if (!m_resizeWithTextureCopy)
247 return qMin(1024, m_rhi->resourceLimit(QRhi::TextureSizeMax));
248
249 return m_rhi->resourceLimit(QRhi::TextureSizeMax);
250 }
251
commitResourceUpdates(QRhiResourceUpdateBatch * mergeInto)252 void QSGRhiTextureGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatch *mergeInto)
253 {
254 if (m_resourceUpdates) {
255 mergeInto->merge(m_resourceUpdates);
256 m_resourceUpdates->release();
257 m_resourceUpdates = nullptr;
258 }
259
260 // now let's assume the resource updates will be committed in this frame
261 for (QRhiTexture *t : m_pendingDispose)
262 t->releaseAndDestroyLater(); // will be releaseAndDestroyed after the frame is submitted -> safe
263
264 m_pendingDispose.clear();
265 }
266
eightBitFormatIsAlphaSwizzled() const267 bool QSGRhiTextureGlyphCache::eightBitFormatIsAlphaSwizzled() const
268 {
269 // return true when the shaders for 8-bit formats need .a instead of .r
270 // when sampling the texture
271 return !m_rhi->isFeatureSupported(QRhi::RedOrAlpha8IsRed);
272 }
273
274 QT_END_NAMESPACE
275