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