1 | // Copyright (C) 2019 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qsgrhidistancefieldglyphcache_p.h" |
5 | #include "qsgcontext_p.h" |
6 | #include "qsgdefaultrendercontext_p.h" |
7 | #include <QtGui/private/qdistancefield_p.h> |
8 | #include <QtCore/qelapsedtimer.h> |
9 | #include <QtQml/private/qqmlglobal_p.h> |
10 | #include <qmath.h> |
11 | #include <qendian.h> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | DEFINE_BOOL_CONFIG_OPTION(qmlUseGlyphCacheWorkaround, QML_USE_GLYPHCACHE_WORKAROUND) |
16 | DEFINE_BOOL_CONFIG_OPTION(qsgPreferFullSizeGlyphCacheTextures, QSG_PREFER_FULLSIZE_GLYPHCACHE_TEXTURES) |
17 | |
18 | #if !defined(QSG_RHI_DISTANCEFIELD_GLYPH_CACHE_PADDING) |
19 | # define QSG_RHI_DISTANCEFIELD_GLYPH_CACHE_PADDING 2 |
20 | #endif |
21 | |
22 | QSGRhiDistanceFieldGlyphCache::QSGRhiDistanceFieldGlyphCache(QSGDefaultRenderContext *rc, |
23 | const QRawFont &font, |
24 | int renderTypeQuality) |
25 | : QSGDistanceFieldGlyphCache(font, renderTypeQuality) |
26 | , m_rc(rc) |
27 | , m_rhi(rc->rhi()) |
28 | { |
29 | // Load a pregenerated cache if the font contains one |
30 | loadPregeneratedCache(font); |
31 | } |
32 | |
33 | QSGRhiDistanceFieldGlyphCache::~QSGRhiDistanceFieldGlyphCache() |
34 | { |
35 | for (const TextureInfo &t : std::as_const(t&: m_textures)) |
36 | m_rc->deferredReleaseGlyphCacheTexture(texture: t.texture); |
37 | |
38 | delete m_areaAllocator; |
39 | } |
40 | |
41 | void QSGRhiDistanceFieldGlyphCache::requestGlyphs(const QSet<glyph_t> &glyphs) |
42 | { |
43 | QList<GlyphPosition> glyphPositions; |
44 | QVector<glyph_t> glyphsToRender; |
45 | |
46 | if (m_areaAllocator == nullptr) |
47 | m_areaAllocator = new QSGAreaAllocator(QSize(maxTextureSize(), m_maxTextureCount * maxTextureSize())); |
48 | |
49 | for (QSet<glyph_t>::const_iterator it = glyphs.constBegin(); it != glyphs.constEnd() ; ++it) { |
50 | glyph_t glyphIndex = *it; |
51 | |
52 | int padding = QSG_RHI_DISTANCEFIELD_GLYPH_CACHE_PADDING; |
53 | QRectF boundingRect = glyphData(glyph: glyphIndex).boundingRect; |
54 | int glyphWidth = qCeil(v: boundingRect.width() + distanceFieldRadius() * 2); |
55 | int glyphHeight = qCeil(v: boundingRect.height() + distanceFieldRadius() * 2); |
56 | QSize glyphSize(glyphWidth + padding * 2, glyphHeight + padding * 2); |
57 | QRect alloc = m_areaAllocator->allocate(size: glyphSize); |
58 | |
59 | if (alloc.isNull()) { |
60 | // Unallocate unused glyphs until we can allocated the new glyph |
61 | while (alloc.isNull() && !m_unusedGlyphs.isEmpty()) { |
62 | glyph_t unusedGlyph = *m_unusedGlyphs.constBegin(); |
63 | |
64 | TexCoord unusedCoord = glyphTexCoord(glyph: unusedGlyph); |
65 | QRectF unusedGlyphBoundingRect = glyphData(glyph: unusedGlyph).boundingRect; |
66 | int unusedGlyphWidth = qCeil(v: unusedGlyphBoundingRect.width() + distanceFieldRadius() * 2); |
67 | int unusedGlyphHeight = qCeil(v: unusedGlyphBoundingRect.height() + distanceFieldRadius() * 2); |
68 | m_areaAllocator->deallocate(rect: QRect(unusedCoord.x - padding, |
69 | unusedCoord.y - padding, |
70 | padding * 2 + unusedGlyphWidth, |
71 | padding * 2 + unusedGlyphHeight)); |
72 | |
73 | m_unusedGlyphs.remove(value: unusedGlyph); |
74 | m_glyphsTexture.remove(key: unusedGlyph); |
75 | removeGlyph(glyph: unusedGlyph); |
76 | |
77 | alloc = m_areaAllocator->allocate(size: glyphSize); |
78 | } |
79 | |
80 | // Not enough space left for this glyph... skip to the next one |
81 | if (alloc.isNull()) |
82 | continue; |
83 | } |
84 | |
85 | TextureInfo *tex = textureInfo(index: alloc.y() / maxTextureSize()); |
86 | alloc = QRect(alloc.x(), alloc.y() % maxTextureSize(), alloc.width(), alloc.height()); |
87 | |
88 | tex->allocatedArea |= alloc; |
89 | Q_ASSERT(tex->padding == padding || tex->padding < 0); |
90 | tex->padding = padding; |
91 | |
92 | GlyphPosition p; |
93 | p.glyph = glyphIndex; |
94 | p.position = alloc.topLeft() + QPoint(padding, padding); |
95 | |
96 | glyphPositions.append(t: p); |
97 | glyphsToRender.append(t: glyphIndex); |
98 | m_glyphsTexture.insert(key: glyphIndex, value: tex); |
99 | } |
100 | |
101 | setGlyphsPosition(glyphPositions); |
102 | markGlyphsToRender(glyphs: glyphsToRender); |
103 | } |
104 | |
105 | bool QSGRhiDistanceFieldGlyphCache::isActive() const |
106 | { |
107 | return !m_referencedGlyphs.empty(); |
108 | } |
109 | |
110 | void QSGRhiDistanceFieldGlyphCache::storeGlyphs(const QList<QDistanceField> &glyphs) |
111 | { |
112 | typedef QHash<TextureInfo *, QVector<glyph_t> > GlyphTextureHash; |
113 | typedef GlyphTextureHash::const_iterator GlyphTextureHashConstIt; |
114 | |
115 | GlyphTextureHash glyphTextures; |
116 | |
117 | QVarLengthArray<QRhiTextureUploadEntry, 32> uploads; |
118 | for (int i = 0; i < glyphs.size(); ++i) { |
119 | QDistanceField glyph = glyphs.at(i); |
120 | glyph_t glyphIndex = glyph.glyph(); |
121 | TexCoord c = glyphTexCoord(glyph: glyphIndex); |
122 | TextureInfo *texInfo = m_glyphsTexture.value(key: glyphIndex); |
123 | |
124 | resizeTexture(texInfo, width: texInfo->allocatedArea.width(), height: texInfo->allocatedArea.height()); |
125 | |
126 | glyphTextures[texInfo].append(t: glyphIndex); |
127 | |
128 | int padding = texInfo->padding; |
129 | int expectedWidth = qCeil(v: c.width + c.xMargin * 2); |
130 | glyph = glyph.copy(x: -padding, y: -padding, |
131 | w: expectedWidth + padding * 2, h: glyph.height() + padding * 2); |
132 | |
133 | if (useTextureResizeWorkaround()) { |
134 | uchar *inBits = glyph.scanLine(0); |
135 | uchar *outBits = texInfo->image.scanLine(int(c.y) - padding) + int(c.x) - padding; |
136 | for (int y = 0; y < glyph.height(); ++y) { |
137 | memcpy(dest: outBits, src: inBits, n: glyph.width()); |
138 | inBits += glyph.width(); |
139 | outBits += texInfo->image.width(); |
140 | } |
141 | } |
142 | |
143 | QRhiTextureSubresourceUploadDescription subresDesc(glyph.constBits(), glyph.width() * glyph.height()); |
144 | subresDesc.setSourceSize(QSize(glyph.width(), glyph.height())); |
145 | subresDesc.setDestinationTopLeft(QPoint(c.x - padding, c.y - padding)); |
146 | texInfo->uploads.append(t: QRhiTextureUploadEntry(0, 0, subresDesc)); |
147 | } |
148 | |
149 | QRhiResourceUpdateBatch *resourceUpdates = m_rc->glyphCacheResourceUpdates(); |
150 | for (int i = 0; i < glyphs.size(); ++i) { |
151 | TextureInfo *texInfo = m_glyphsTexture.value(key: glyphs.at(i).glyph()); |
152 | if (!texInfo->uploads.isEmpty()) { |
153 | QRhiTextureUploadDescription desc; |
154 | desc.setEntries(first: texInfo->uploads.cbegin(), last: texInfo->uploads.cend()); |
155 | resourceUpdates->uploadTexture(tex: texInfo->texture, desc); |
156 | texInfo->uploads.clear(); |
157 | } |
158 | } |
159 | |
160 | for (GlyphTextureHashConstIt i = glyphTextures.constBegin(), cend = glyphTextures.constEnd(); i != cend; ++i) { |
161 | Texture t; |
162 | t.texture = i.key()->texture; |
163 | t.size = i.key()->size; |
164 | setGlyphsTexture(glyphs: i.value(), tex: t); |
165 | } |
166 | } |
167 | |
168 | void QSGRhiDistanceFieldGlyphCache::referenceGlyphs(const QSet<glyph_t> &glyphs) |
169 | { |
170 | m_referencedGlyphs += glyphs; |
171 | m_unusedGlyphs -= glyphs; |
172 | } |
173 | |
174 | void QSGRhiDistanceFieldGlyphCache::releaseGlyphs(const QSet<glyph_t> &glyphs) |
175 | { |
176 | m_referencedGlyphs -= glyphs; |
177 | m_unusedGlyphs += glyphs; |
178 | } |
179 | |
180 | void QSGRhiDistanceFieldGlyphCache::createTexture(TextureInfo *texInfo, |
181 | int width, |
182 | int height) |
183 | { |
184 | QByteArray zeroBuf(width * height, 0); |
185 | createTexture(texInfo, width, height, pixels: zeroBuf.constData()); |
186 | } |
187 | |
188 | void QSGRhiDistanceFieldGlyphCache::createTexture(TextureInfo *texInfo, |
189 | int width, |
190 | int height, |
191 | const void *pixels) |
192 | { |
193 | if (useTextureResizeWorkaround() && texInfo->image.isNull()) { |
194 | texInfo->image = QDistanceField(width, height); |
195 | memcpy(dest: texInfo->image.bits(), src: pixels, n: width * height); |
196 | } |
197 | |
198 | texInfo->texture = m_rhi->newTexture(format: QRhiTexture::RED_OR_ALPHA8, pixelSize: QSize(width, height), sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource); |
199 | if (texInfo->texture->create()) { |
200 | QRhiResourceUpdateBatch *resourceUpdates = m_rc->glyphCacheResourceUpdates(); |
201 | QRhiTextureSubresourceUploadDescription subresDesc(pixels, width * height); |
202 | subresDesc.setSourceSize(QSize(width, height)); |
203 | resourceUpdates->uploadTexture(tex: texInfo->texture, desc: QRhiTextureUploadEntry(0, 0, subresDesc)); |
204 | } else { |
205 | qWarning(msg: "Failed to create distance field glyph cache" ); |
206 | } |
207 | |
208 | texInfo->size = QSize(width, height); |
209 | } |
210 | |
211 | void QSGRhiDistanceFieldGlyphCache::resizeTexture(TextureInfo *texInfo, int width, int height) |
212 | { |
213 | int oldWidth = texInfo->size.width(); |
214 | int oldHeight = texInfo->size.height(); |
215 | if (width == oldWidth && height == oldHeight) |
216 | return; |
217 | |
218 | QRhiTexture *oldTexture = texInfo->texture; |
219 | createTexture(texInfo, width, height); |
220 | |
221 | if (!oldTexture) |
222 | return; |
223 | |
224 | updateRhiTexture(oldTex: oldTexture, newTex: texInfo->texture, newTexSize: texInfo->size); |
225 | |
226 | QRhiResourceUpdateBatch *resourceUpdates = m_rc->glyphCacheResourceUpdates(); |
227 | if (useTextureResizeWorkaround()) { |
228 | QRhiTextureSubresourceUploadDescription subresDesc(texInfo->image.constBits(), |
229 | oldWidth * oldHeight); |
230 | subresDesc.setSourceSize(QSize(oldWidth, oldHeight)); |
231 | resourceUpdates->uploadTexture(tex: texInfo->texture, desc: QRhiTextureUploadEntry(0, 0, subresDesc)); |
232 | texInfo->image = texInfo->image.copy(x: 0, y: 0, w: width, h: height); |
233 | } else { |
234 | resourceUpdates->copyTexture(dst: texInfo->texture, src: oldTexture); |
235 | } |
236 | |
237 | m_rc->deferredReleaseGlyphCacheTexture(texture: oldTexture); |
238 | } |
239 | |
240 | bool QSGRhiDistanceFieldGlyphCache::useTextureResizeWorkaround() const |
241 | { |
242 | static bool set = false; |
243 | static bool useWorkaround = false; |
244 | if (!set) { |
245 | useWorkaround = m_rhi->backend() == QRhi::OpenGLES2 || qmlUseGlyphCacheWorkaround(); |
246 | set = true; |
247 | } |
248 | return useWorkaround; |
249 | } |
250 | |
251 | bool QSGRhiDistanceFieldGlyphCache::createFullSizeTextures() const |
252 | { |
253 | return qsgPreferFullSizeGlyphCacheTextures() && glyphCount() > QT_DISTANCEFIELD_HIGHGLYPHCOUNT(); |
254 | } |
255 | |
256 | int QSGRhiDistanceFieldGlyphCache::maxTextureSize() const |
257 | { |
258 | if (!m_maxTextureSize) |
259 | m_maxTextureSize = m_rhi->resourceLimit(limit: QRhi::TextureSizeMax); |
260 | return m_maxTextureSize; |
261 | } |
262 | |
263 | namespace { |
264 | struct Qtdf { |
265 | // We need these structs to be tightly packed, but some compilers we use do not |
266 | // support #pragma pack(1), so we need to hardcode the offsets/sizes in the |
267 | // file format |
268 | enum TableSize { |
269 | = 14, |
270 | GlyphRecordSize = 46, |
271 | TextureRecordSize = 17 |
272 | }; |
273 | |
274 | enum Offset { |
275 | // Header |
276 | majorVersion = 0, |
277 | minorVersion = 1, |
278 | pixelSize = 2, |
279 | textureSize = 4, |
280 | flags = 8, |
281 | = 9, |
282 | numGlyphs = 10, |
283 | |
284 | // Glyph record |
285 | glyphIndex = 0, |
286 | textureOffsetX = 4, |
287 | textureOffsetY = 8, |
288 | textureWidth = 12, |
289 | textureHeight = 16, |
290 | xMargin = 20, |
291 | yMargin = 24, |
292 | boundingRectX = 28, |
293 | boundingRectY = 32, |
294 | boundingRectWidth = 36, |
295 | boundingRectHeight = 40, |
296 | textureIndex = 44, |
297 | |
298 | // Texture record |
299 | allocatedX = 0, |
300 | allocatedY = 4, |
301 | allocatedWidth = 8, |
302 | allocatedHeight = 12, |
303 | texturePadding = 16 |
304 | |
305 | }; |
306 | |
307 | template <typename T> |
308 | static inline T fetch(const char *data, Offset offset) |
309 | { |
310 | return qFromBigEndian<T>(data + int(offset)); |
311 | } |
312 | }; |
313 | } |
314 | |
315 | bool QSGRhiDistanceFieldGlyphCache::loadPregeneratedCache(const QRawFont &font) |
316 | { |
317 | // The pregenerated data must be loaded first, otherwise the area allocator |
318 | // will be wrong |
319 | if (m_areaAllocator != nullptr) { |
320 | qWarning(msg: "Font cache must be loaded before cache is used" ); |
321 | return false; |
322 | } |
323 | |
324 | static QElapsedTimer timer; |
325 | |
326 | bool profile = QSG_LOG_TIME_GLYPH().isDebugEnabled(); |
327 | if (profile) |
328 | timer.start(); |
329 | |
330 | QByteArray qtdfTable = font.fontTable(tagName: "qtdf" ); |
331 | if (qtdfTable.isEmpty()) |
332 | return false; |
333 | |
334 | typedef QHash<TextureInfo *, QVector<glyph_t> > GlyphTextureHash; |
335 | |
336 | GlyphTextureHash glyphTextures; |
337 | |
338 | if (uint(qtdfTable.size()) < Qtdf::HeaderSize) { |
339 | qWarning(msg: "Invalid qtdf table in font '%s'" , |
340 | qPrintable(font.familyName())); |
341 | return false; |
342 | } |
343 | |
344 | const char *qtdfTableStart = qtdfTable.constData(); |
345 | const char *qtdfTableEnd = qtdfTableStart + qtdfTable.size(); |
346 | |
347 | int padding = 0; |
348 | int textureCount = 0; |
349 | { |
350 | quint8 majorVersion = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::majorVersion); |
351 | quint8 minorVersion = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::minorVersion); |
352 | if (majorVersion != 5 || minorVersion != 12) { |
353 | qWarning(msg: "Invalid version of qtdf table %d.%d in font '%s'" , |
354 | majorVersion, |
355 | minorVersion, |
356 | qPrintable(font.familyName())); |
357 | return false; |
358 | } |
359 | |
360 | qreal pixelSize = qreal(Qtdf::fetch<quint16>(data: qtdfTableStart, offset: Qtdf::pixelSize)); |
361 | m_maxTextureSize = Qtdf::fetch<quint32>(data: qtdfTableStart, offset: Qtdf::textureSize); |
362 | m_doubleGlyphResolution = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::flags) == 1; |
363 | padding = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::headerPadding); |
364 | |
365 | if (pixelSize <= 0.0) { |
366 | qWarning(msg: "Invalid pixel size in '%s'" , qPrintable(font.familyName())); |
367 | return false; |
368 | } |
369 | |
370 | if (m_maxTextureSize <= 0) { |
371 | qWarning(msg: "Invalid texture size in '%s'" , qPrintable(font.familyName())); |
372 | return false; |
373 | } |
374 | |
375 | int systemMaxTextureSize = m_rhi->resourceLimit(limit: QRhi::TextureSizeMax); |
376 | |
377 | if (m_maxTextureSize > systemMaxTextureSize) { |
378 | qWarning(msg: "System maximum texture size is %d. This is lower than the value in '%s', which is %d" , |
379 | systemMaxTextureSize, |
380 | qPrintable(font.familyName()), |
381 | m_maxTextureSize); |
382 | } |
383 | |
384 | if (padding != QSG_RHI_DISTANCEFIELD_GLYPH_CACHE_PADDING) { |
385 | qWarning(msg: "Padding mismatch in '%s'. Font requires %d, but Qt is compiled with %d." , |
386 | qPrintable(font.familyName()), |
387 | padding, |
388 | QSG_RHI_DISTANCEFIELD_GLYPH_CACHE_PADDING); |
389 | } |
390 | |
391 | m_referenceFont.setPixelSize(pixelSize); |
392 | |
393 | quint32 glyphCount = Qtdf::fetch<quint32>(data: qtdfTableStart, offset: Qtdf::numGlyphs); |
394 | m_unusedGlyphs.reserve(asize: glyphCount); |
395 | |
396 | const char *allocatorData = qtdfTableStart + Qtdf::HeaderSize; |
397 | { |
398 | m_areaAllocator = new QSGAreaAllocator(QSize(0, 0)); |
399 | allocatorData = m_areaAllocator->deserialize(data: allocatorData, size: qtdfTableEnd - allocatorData); |
400 | if (allocatorData == nullptr) |
401 | return false; |
402 | } |
403 | |
404 | if (m_areaAllocator->size().height() % m_maxTextureSize != 0) { |
405 | qWarning(msg: "Area allocator size mismatch in '%s'" , qPrintable(font.familyName())); |
406 | return false; |
407 | } |
408 | |
409 | textureCount = m_areaAllocator->size().height() / m_maxTextureSize; |
410 | m_maxTextureCount = qMax(a: m_maxTextureCount, b: textureCount); |
411 | |
412 | const char *textureRecord = allocatorData; |
413 | for (int i = 0; i < textureCount; ++i, textureRecord += Qtdf::TextureRecordSize) { |
414 | if (qtdfTableEnd - textureRecord < Qtdf::TextureRecordSize) { |
415 | qWarning(msg: "qtdf table too small in font '%s'." , |
416 | qPrintable(font.familyName())); |
417 | return false; |
418 | } |
419 | |
420 | TextureInfo *tex = textureInfo(index: i); |
421 | tex->allocatedArea.setX(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedX)); |
422 | tex->allocatedArea.setY(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedY)); |
423 | tex->allocatedArea.setWidth(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedWidth)); |
424 | tex->allocatedArea.setHeight(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedHeight)); |
425 | tex->padding = Qtdf::fetch<quint8>(data: textureRecord, offset: Qtdf::texturePadding); |
426 | } |
427 | |
428 | const char *glyphRecord = textureRecord; |
429 | for (quint32 i = 0; i < glyphCount; ++i, glyphRecord += Qtdf::GlyphRecordSize) { |
430 | if (qtdfTableEnd - glyphRecord < Qtdf:: GlyphRecordSize) { |
431 | qWarning(msg: "qtdf table too small in font '%s'." , |
432 | qPrintable(font.familyName())); |
433 | return false; |
434 | } |
435 | |
436 | glyph_t glyph = Qtdf::fetch<quint32>(data: glyphRecord, offset: Qtdf::glyphIndex); |
437 | m_unusedGlyphs.insert(value: glyph); |
438 | |
439 | GlyphData &glyphData = emptyData(glyph); |
440 | |
441 | #define FROM_FIXED_POINT(value) \ |
442 | (((qreal)value)/(qreal)65536) |
443 | |
444 | glyphData.texCoord.x = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetX)); |
445 | glyphData.texCoord.y = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetY)); |
446 | glyphData.texCoord.width = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureWidth)); |
447 | glyphData.texCoord.height = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureHeight)); |
448 | glyphData.texCoord.xMargin = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::xMargin)); |
449 | glyphData.texCoord.yMargin = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::yMargin)); |
450 | glyphData.boundingRect.setX(FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectX))); |
451 | glyphData.boundingRect.setY(FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectY))); |
452 | glyphData.boundingRect.setWidth(FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::boundingRectWidth))); |
453 | glyphData.boundingRect.setHeight(FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::boundingRectHeight))); |
454 | |
455 | #undef FROM_FIXED_POINT |
456 | |
457 | int textureIndex = Qtdf::fetch<quint16>(data: glyphRecord, offset: Qtdf::textureIndex); |
458 | if (textureIndex < 0 || textureIndex >= textureCount) { |
459 | qWarning(msg: "Invalid texture index %d (texture count == %d) in '%s'" , |
460 | textureIndex, |
461 | textureCount, |
462 | qPrintable(font.familyName())); |
463 | return false; |
464 | } |
465 | |
466 | |
467 | TextureInfo *texInfo = textureInfo(index: textureIndex); |
468 | m_glyphsTexture.insert(key: glyph, value: texInfo); |
469 | |
470 | glyphTextures[texInfo].append(t: glyph); |
471 | } |
472 | |
473 | const uchar *textureData = reinterpret_cast<const uchar *>(glyphRecord); |
474 | for (int i = 0; i < textureCount; ++i) { |
475 | |
476 | TextureInfo *texInfo = textureInfo(index: i); |
477 | |
478 | int width = texInfo->allocatedArea.width(); |
479 | int height = texInfo->allocatedArea.height(); |
480 | qint64 size = qint64(width) * height; |
481 | if (qtdfTableEnd - reinterpret_cast<const char *>(textureData) < size) { |
482 | qWarning(msg: "qtdf table too small in font '%s'." , |
483 | qPrintable(font.familyName())); |
484 | return false; |
485 | } |
486 | |
487 | createTexture(texInfo, width, height, pixels: textureData); |
488 | |
489 | QVector<glyph_t> glyphs = glyphTextures.value(key: texInfo); |
490 | |
491 | Texture t; |
492 | t.texture = texInfo->texture; |
493 | t.size = texInfo->size; |
494 | |
495 | setGlyphsTexture(glyphs, tex: t); |
496 | |
497 | textureData += size; |
498 | } |
499 | } |
500 | |
501 | if (profile) { |
502 | quint64 now = timer.elapsed(); |
503 | qCDebug(QSG_LOG_TIME_GLYPH, |
504 | "distancefield: %d pre-generated glyphs loaded in %dms" , |
505 | int(m_unusedGlyphs.size()), |
506 | int(now)); |
507 | } |
508 | |
509 | return true; |
510 | } |
511 | |
512 | void QSGRhiDistanceFieldGlyphCache::commitResourceUpdates(QRhiResourceUpdateBatch *mergeInto) |
513 | { |
514 | if (QRhiResourceUpdateBatch *resourceUpdates = m_rc->maybeGlyphCacheResourceUpdates()) { |
515 | mergeInto->merge(other: resourceUpdates); |
516 | m_rc->resetGlyphCacheResources(); |
517 | } |
518 | } |
519 | |
520 | bool QSGRhiDistanceFieldGlyphCache::eightBitFormatIsAlphaSwizzled() const |
521 | { |
522 | // return true when the shaders for 8-bit formats need .a instead of .r |
523 | // when sampling the texture |
524 | return !m_rhi->isFeatureSupported(feature: QRhi::RedOrAlpha8IsRed); |
525 | } |
526 | |
527 | bool QSGRhiDistanceFieldGlyphCache::screenSpaceDerivativesSupported() const |
528 | { |
529 | return m_rhi->isFeatureSupported(feature: QRhi::ScreenSpaceDerivatives); |
530 | } |
531 | |
532 | #if defined(QSG_DISTANCEFIELD_CACHE_DEBUG) |
533 | void QSGRhiDistanceFieldGlyphCache::saveTexture(QRhiTexture *texture, const QString &nameBase) const |
534 | { |
535 | quint64 textureId = texture->nativeTexture().object; |
536 | QString fileName = nameBase + QLatin1Char('_') + QString::number(textureId, 16); |
537 | fileName.replace(QLatin1Char('/'), QLatin1Char('_')); |
538 | fileName.replace(QLatin1Char(' '), QLatin1Char('_')); |
539 | fileName.append(QLatin1String(".png" )); |
540 | |
541 | QRhiReadbackResult *rbResult = new QRhiReadbackResult; |
542 | rbResult->completed = [rbResult, fileName] { |
543 | const QSize size = rbResult->pixelSize; |
544 | const qint64 numPixels = qint64(size.width()) * size.height(); |
545 | if (numPixels == rbResult->data.size()) { |
546 | // 1 bpp data, may be packed; copy it to ensure QImage scanline alignment |
547 | QImage image(size, QImage::Format_Grayscale8); |
548 | const char *p = rbResult->data.constData(); |
549 | for (int i = 0; i < size.height(); i++) |
550 | memcpy(image.scanLine(i), p + (i * size.width()), size.width()); |
551 | image.save(fileName); |
552 | } else if (4 * numPixels == rbResult->data.size()) { |
553 | // 4 bpp data |
554 | const uchar *p = reinterpret_cast<const uchar *>(rbResult->data.constData()); |
555 | QImage image(p, size.width(), size.height(), QImage::Format_RGBA8888); |
556 | image.save(fileName); |
557 | } else { |
558 | qWarning("Unhandled data format in glyph texture" ); |
559 | } |
560 | delete rbResult; |
561 | }; |
562 | |
563 | QRhiReadbackDescription rb(texture); |
564 | QRhiResourceUpdateBatch *resourceUpdates = m_rc->glyphCacheResourceUpdates(); |
565 | resourceUpdates->readBackTexture(rb, rbResult); |
566 | } |
567 | #endif |
568 | |
569 | QT_END_NAMESPACE |
570 | |