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