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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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