1// Copyright (C) 2016 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 "qsgdistancefieldglyphnode_p.h"
5#include "qsgdistancefieldglyphnode_p_p.h"
6#include <QtQuick/private/qsgcontext_p.h>
7
8QT_BEGIN_NAMESPACE
9
10Q_LOGGING_CATEGORY(lcSgText, "qt.scenegraph.text")
11
12qint64 QSGDistanceFieldGlyphNode::m_totalAllocation = 0;
13
14QSGDistanceFieldGlyphNode::QSGDistanceFieldGlyphNode(QSGRenderContext *context)
15 : m_glyphNodeType(RootGlyphNode)
16 , m_context(context)
17 , m_material(nullptr)
18 , m_glyph_cache(nullptr)
19 , m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0)
20 , m_style(QQuickText::Normal)
21 , m_antialiasingMode(GrayAntialiasing)
22 , m_texture(nullptr)
23 , m_renderTypeQuality(-1)
24 , m_dirtyGeometry(false)
25 , m_dirtyMaterial(false)
26{
27 m_geometry.setDrawingMode(QSGGeometry::DrawTriangles);
28 setGeometry(&m_geometry);
29#ifdef QSG_RUNTIME_DESCRIPTION
30 qsgnode_set_description(node: this, description: QLatin1String("glyphs"));
31#endif
32}
33
34QSGDistanceFieldGlyphNode::~QSGDistanceFieldGlyphNode()
35{
36 delete m_material;
37
38 if (m_glyphNodeType == SubGlyphNode)
39 return;
40
41 if (m_glyph_cache) {
42 m_glyph_cache->release(glyphs: m_glyphs.glyphIndexes());
43 m_glyph_cache->unregisterGlyphNode(node: this);
44 m_glyph_cache->unregisterOwnerElement(ownerElement: ownerElement());
45 }
46}
47
48void QSGDistanceFieldGlyphNode::setColor(const QColor &color)
49{
50 m_color = color;
51 if (m_material != nullptr) {
52 m_material->setColor(color);
53 markDirty(bits: DirtyMaterial);
54 } else {
55 m_dirtyMaterial = true;
56 }
57}
58
59void QSGDistanceFieldGlyphNode::setRenderTypeQuality(int renderTypeQuality)
60{
61 if (renderTypeQuality == m_renderTypeQuality)
62 return;
63
64 m_renderTypeQuality = renderTypeQuality;
65}
66
67void QSGDistanceFieldGlyphNode::setPreferredAntialiasingMode(AntialiasingMode mode)
68{
69 if (mode == m_antialiasingMode)
70 return;
71 m_antialiasingMode = mode;
72 m_dirtyMaterial = true;
73}
74
75void QSGDistanceFieldGlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &glyphs)
76{
77 QRawFont font = glyphs.rawFont();
78 m_originalPosition = position;
79 m_position = QPointF(position.x(), position.y() - font.ascent());
80 m_glyphs = glyphs;
81
82 m_dirtyGeometry = true;
83 m_dirtyMaterial = true;
84 setFlag(UsePreprocess);
85
86 QSGDistanceFieldGlyphCache *oldCache = m_glyph_cache;
87 m_glyph_cache = m_context->distanceFieldGlyphCache(font: m_glyphs.rawFont(), renderTypeQuality: m_renderTypeQuality);
88
89 if (m_glyphNodeType == SubGlyphNode)
90 return;
91
92 if (m_glyph_cache != oldCache) {
93 Q_ASSERT(ownerElement() != nullptr);
94 if (oldCache) {
95 oldCache->unregisterGlyphNode(node: this);
96 oldCache->unregisterOwnerElement(ownerElement: ownerElement());
97 }
98 m_glyph_cache->registerGlyphNode(node: this);
99 m_glyph_cache->registerOwnerElement(ownerElement: ownerElement());
100 }
101 m_glyph_cache->populate(glyphs: glyphs.glyphIndexes());
102
103 const QVector<quint32> glyphIndexes = m_glyphs.glyphIndexes();
104 for (int i = 0; i < glyphIndexes.size(); ++i)
105 m_allGlyphIndexesLookup.insert(value: glyphIndexes.at(i));
106 qCDebug(lcSgText, "inserting %" PRIdQSIZETYPE " glyphs, %" PRIdQSIZETYPE " unique",
107 glyphIndexes.size(),
108 m_allGlyphIndexesLookup.size());
109#ifdef QSG_RUNTIME_DESCRIPTION
110 qsgnode_set_description(node: this, description: QString::number(glyphs.glyphIndexes().count()) + QStringLiteral(" DF glyphs: ") +
111 m_glyphs.rawFont().familyName() + QStringLiteral(" ") + QString::number(m_glyphs.rawFont().pixelSize()));
112#endif
113}
114
115void QSGDistanceFieldGlyphNode::setStyle(QQuickText::TextStyle style)
116{
117 if (m_style == style)
118 return;
119 m_style = style;
120 m_dirtyMaterial = true;
121}
122
123void QSGDistanceFieldGlyphNode::setStyleColor(const QColor &color)
124{
125 if (m_styleColor == color)
126 return;
127 m_styleColor = color;
128 m_dirtyMaterial = true;
129}
130
131void QSGDistanceFieldGlyphNode::update()
132{
133 if (m_dirtyMaterial)
134 updateMaterial();
135}
136
137void QSGDistanceFieldGlyphNode::preprocess()
138{
139 if (m_dirtyGeometry)
140 updateGeometry();
141
142 setFlag(UsePreprocess, false);
143}
144
145void QSGDistanceFieldGlyphNode::invalidateGlyphs(const QVector<quint32> &glyphs)
146{
147 if (m_dirtyGeometry)
148 return;
149
150 for (int i = 0; i < glyphs.size(); ++i) {
151 if (m_allGlyphIndexesLookup.contains(value: glyphs.at(i))) {
152 m_dirtyGeometry = true;
153 setFlag(UsePreprocess);
154 return;
155 }
156 }
157}
158
159void QSGDistanceFieldGlyphNode::updateGeometry()
160{
161 Q_ASSERT(m_glyph_cache);
162
163 // Remove previously created sub glyph nodes
164 // We assume all the children are sub glyph nodes
165 QSGNode *subnode = firstChild();
166 QSGNode *nextNode = nullptr;
167 while (subnode) {
168 nextNode = subnode->nextSibling();
169 delete subnode;
170 subnode = nextNode;
171 }
172
173 QSGGeometry *g = geometry();
174
175 Q_ASSERT(g->indexType() == QSGGeometry::UnsignedShortType);
176 m_glyphsInOtherTextures.clear();
177
178 const QVector<quint32> indexes = m_glyphs.glyphIndexes();
179 const QVector<QPointF> positions = m_glyphs.positions();
180 qreal fontPixelSize = m_glyphs.rawFont().pixelSize();
181
182 // The template parameters here are assuming that most strings are short, 64
183 // characters or less.
184 QVarLengthArray<QSGGeometry::TexturedPoint2D, 256> vp;
185 QVarLengthArray<ushort, 384> ip;
186 const qsizetype maxIndexCount = (std::numeric_limits<quint16>::max() - 1) / 4; // 16383 (see below: 0xFFFF is not allowed)
187 const qsizetype maxVertexCount = maxIndexCount * 4; // 65532
188 const auto likelyGlyphCount = qMin(a: indexes.size(), b: maxIndexCount);
189 vp.reserve(sz: likelyGlyphCount * 4);
190 ip.reserve(sz: likelyGlyphCount * 6);
191
192 qreal maxTexMargin = m_glyph_cache->distanceFieldRadius();
193 qreal fontScale = m_glyph_cache->fontScale(pixelSize: fontPixelSize);
194 qreal margin = 2;
195 qreal texMargin = margin / fontScale;
196 if (texMargin > maxTexMargin) {
197 texMargin = maxTexMargin;
198 margin = maxTexMargin * fontScale;
199 }
200
201 for (int i = 0; i < indexes.size(); ++i) {
202 const int glyphIndex = indexes.at(i);
203 QSGDistanceFieldGlyphCache::TexCoord c = m_glyph_cache->glyphTexCoord(glyph: glyphIndex);
204
205 if (c.isNull())
206 continue;
207
208 const QPointF position = positions.at(i);
209
210 const QSGDistanceFieldGlyphCache::Texture *texture = m_glyph_cache->glyphTexture(glyph: glyphIndex);
211 if (texture->texture && !m_texture)
212 m_texture = texture;
213
214 // As we use UNSIGNED_SHORT indexing in the geometry, we overload the
215 // "glyphsInOtherTextures" concept as overflow for if there are more
216 // than 65532 vertices to render, which would otherwise exceed the
217 // maximum index size. (leave 0xFFFF unused in order not to clash with
218 // primitive restart) This will cause sub-nodes to be
219 // created to handle any number of glyphs. But only the RootGlyphNode
220 // needs to do this classification; from the perspective of a SubGlyphNode,
221 // it's already done, and m_glyphs contains only pointers to ranges of
222 // indices and positions that the RootGlyphNode is storing.
223 if (m_texture != texture || vp.size() >= maxVertexCount) {
224 if (m_glyphNodeType == RootGlyphNode && texture->texture) {
225 GlyphInfo &glyphInfo = m_glyphsInOtherTextures[texture];
226 glyphInfo.indexes.append(t: glyphIndex);
227 glyphInfo.positions.append(t: position);
228 } else if (vp.size() >= maxVertexCount && m_glyphNodeType == SubGlyphNode) {
229 break; // out of this loop over indices, because we won't add any more vertices
230 }
231 continue;
232 }
233
234 QSGDistanceFieldGlyphCache::Metrics metrics = m_glyph_cache->glyphMetrics(glyph: glyphIndex, pixelSize: fontPixelSize);
235
236 if (!metrics.isNull() && !c.isNull()) {
237 metrics.width += margin * 2;
238 metrics.height += margin * 2;
239 metrics.baselineX -= margin;
240 metrics.baselineY += margin;
241 c.xMargin -= texMargin;
242 c.yMargin -= texMargin;
243 c.width += texMargin * 2;
244 c.height += texMargin * 2;
245 }
246
247 qreal x = position.x() + metrics.baselineX + m_position.x();
248 qreal y = position.y() - metrics.baselineY + m_position.y();
249
250 m_boundingRect |= QRectF(x, y, metrics.width, metrics.height);
251
252 float cx1 = x;
253 float cx2 = x + metrics.width;
254 float cy1 = y;
255 float cy2 = y + metrics.height;
256
257 float tx1 = c.x + c.xMargin;
258 float tx2 = tx1 + c.width;
259 float ty1 = c.y + c.yMargin;
260 float ty2 = ty1 + c.height;
261
262 if (m_baseLine.isNull())
263 m_baseLine = position;
264
265 int o = vp.size();
266
267 QSGGeometry::TexturedPoint2D v1;
268 v1.set(nx: cx1, ny: cy1, ntx: tx1, nty: ty1);
269 QSGGeometry::TexturedPoint2D v2;
270 v2.set(nx: cx2, ny: cy1, ntx: tx2, nty: ty1);
271 QSGGeometry::TexturedPoint2D v3;
272 v3.set(nx: cx1, ny: cy2, ntx: tx1, nty: ty2);
273 QSGGeometry::TexturedPoint2D v4;
274 v4.set(nx: cx2, ny: cy2, ntx: tx2, nty: ty2);
275 vp.append(t: v1);
276 vp.append(t: v2);
277 vp.append(t: v3);
278 vp.append(t: v4);
279
280 ip.append(t: o + 0);
281 ip.append(t: o + 2);
282 ip.append(t: o + 3);
283 ip.append(t: o + 3);
284 ip.append(t: o + 1);
285 ip.append(t: o + 0);
286 }
287
288 if (m_glyphNodeType == SubGlyphNode) {
289 Q_ASSERT(m_glyphsInOtherTextures.isEmpty());
290 } else {
291 if (!m_glyphsInOtherTextures.isEmpty())
292 qCDebug(lcSgText, "%" PRIdQSIZETYPE " 'other' textures", m_glyphsInOtherTextures.size());
293 QHash<const QSGDistanceFieldGlyphCache::Texture *, GlyphInfo>::const_iterator ite = m_glyphsInOtherTextures.constBegin();
294 while (ite != m_glyphsInOtherTextures.constEnd()) {
295 QGlyphRun subNodeGlyphRun(m_glyphs);
296 for (int i = 0; i < ite->indexes.size(); i += maxIndexCount) {
297 int len = qMin(a: maxIndexCount, b: ite->indexes.size() - i);
298 subNodeGlyphRun.setRawData(glyphIndexArray: ite->indexes.constData() + i, glyphPositionArray: ite->positions.constData() + i, size: len);
299 qCDebug(lcSgText) << "subNodeGlyphRun has" << len << "positions:"
300 << *(ite->positions.constData() + i) << "->" << *(ite->positions.constData() + i + len - 1);
301
302 QSGDistanceFieldGlyphNode *subNode = new QSGDistanceFieldGlyphNode(m_context);
303 subNode->setGlyphNodeType(SubGlyphNode);
304 subNode->setColor(m_color);
305 subNode->setStyle(m_style);
306 subNode->setStyleColor(m_styleColor);
307 subNode->setPreferredAntialiasingMode(m_antialiasingMode);
308 subNode->setGlyphs(position: m_originalPosition, glyphs: subNodeGlyphRun);
309 subNode->update();
310 subNode->updateGeometry(); // we have to explicitly call this now as preprocess won't be called before it's rendered
311 appendChildNode(node: subNode);
312 }
313 ++ite;
314 }
315 }
316
317 m_totalAllocation += vp.size() * sizeof(QSGGeometry::TexturedPoint2D) + ip.size() * sizeof(quint16);
318 qCDebug(lcSgText) << "allocating for" << vp.size() << "vtx (reserved" << likelyGlyphCount * 4 << "):" << vp.size() * sizeof(QSGGeometry::TexturedPoint2D)
319 << "bytes;" << ip.size() << "idx:" << ip.size() * sizeof(quint16) << "bytes; total bytes so far" << m_totalAllocation;
320 g->allocate(vertexCount: vp.size(), indexCount: ip.size());
321 memcpy(dest: g->vertexDataAsTexturedPoint2D(), src: vp.constData(), n: vp.size() * sizeof(QSGGeometry::TexturedPoint2D));
322 memcpy(dest: g->indexDataAsUShort(), src: ip.constData(), n: ip.size() * sizeof(quint16));
323
324 setBoundingRect(m_boundingRect);
325 markDirty(bits: DirtyGeometry);
326 m_dirtyGeometry = false;
327
328 m_material->setTexture(m_texture);
329}
330
331void QSGDistanceFieldGlyphNode::updateMaterial()
332{
333 delete m_material;
334
335 if (m_style == QQuickText::Normal) {
336 switch (m_antialiasingMode) {
337 case HighQualitySubPixelAntialiasing:
338 m_material = new QSGHiQSubPixelDistanceFieldTextMaterial;
339 break;
340 case LowQualitySubPixelAntialiasing:
341 m_material = new QSGLoQSubPixelDistanceFieldTextMaterial;
342 break;
343 case GrayAntialiasing:
344 default:
345 m_material = new QSGDistanceFieldTextMaterial;
346 break;
347 }
348 } else {
349 QSGDistanceFieldStyledTextMaterial *material;
350 if (m_style == QQuickText::Outline) {
351 material = new QSGDistanceFieldOutlineTextMaterial;
352 } else {
353 QSGDistanceFieldShiftedStyleTextMaterial *sMaterial = new QSGDistanceFieldShiftedStyleTextMaterial;
354 if (m_style == QQuickText::Raised)
355 sMaterial->setShift(QPointF(0.0, 1.0));
356 else
357 sMaterial->setShift(QPointF(0.0, -1.0));
358 material = sMaterial;
359 }
360 material->setStyleColor(m_styleColor);
361 m_material = material;
362 }
363
364 m_material->setGlyphCache(m_glyph_cache);
365 if (m_glyph_cache)
366 m_material->setFontScale(m_glyph_cache->fontScale(pixelSize: m_glyphs.rawFont().pixelSize()));
367 m_material->setColor(m_color);
368 setMaterial(m_material);
369 m_dirtyMaterial = false;
370}
371
372QT_END_NAMESPACE
373

source code of qtdeclarative/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp