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
6#include <QtQuick/private/qsgcontext_p.h>
7
9
10Q_LOGGING_CATEGORY(lcSgText, "qt.scenegraph.text")
11
12// all SG glyph vertices and indices; only for qCDebug metrics
13static std::atomic<qint64> s_totalAllocation = 0;
14
16 : m_glyphNodeType(RootGlyphNode)
17 , m_context(context)
18 , m_material(nullptr)
19 , m_glyph_cache(nullptr)
23 , m_texture(nullptr)
24 , m_renderTypeQuality(-1)
25 , m_dirtyGeometry(false)
26 , m_dirtyMaterial(false)
27{
28 m_geometry.setDrawingMode(QSGGeometry::DrawTriangles);
29 setGeometry(&m_geometry);
30#ifdef QSG_RUNTIME_DESCRIPTION
31 qsgnode_set_description(this, QLatin1String("glyphs"));
32#endif
33}
34
36{
37 delete m_material;
38
39 if (m_glyphNodeType == SubGlyphNode)
40 return;
41
42 if (m_glyph_cache) {
43 m_glyph_cache->release(m_glyphs.glyphIndexes());
44 m_glyph_cache->unregisterGlyphNode(this);
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(DirtyMaterial);
54 } else {
55 m_dirtyMaterial = true;
56 }
57}
58
60{
61 if (renderTypeQuality == m_renderTypeQuality)
62 return;
63
64 m_renderTypeQuality = renderTypeQuality;
65}
66
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(m_glyphs.rawFont(), m_renderTypeQuality);
88
89 if (m_glyphNodeType == SubGlyphNode)
90 return;
91
92 if (m_glyph_cache != oldCache) {
93 if (oldCache)
94 oldCache->unregisterGlyphNode(this);
95
96 if (m_glyph_cache)
97 m_glyph_cache->registerGlyphNode(this);
98 }
99 if (m_glyph_cache)
100 m_glyph_cache->populate(glyphs.glyphIndexes());
101
102 const QList<quint32> glyphIndexes = m_glyphs.glyphIndexes();
103 for (int i = 0; i < glyphIndexes.size(); ++i)
104 m_allGlyphIndexesLookup.insert(glyphIndexes.at(i));
105 qCDebug(lcSgText, "inserting %" PRIdQSIZETYPE " glyphs, %" PRIdQSIZETYPE " unique",
106 glyphIndexes.size(),
107 m_allGlyphIndexesLookup.size());
108#ifdef QSG_RUNTIME_DESCRIPTION
109 qsgnode_set_description(this, QString::number(glyphs.glyphIndexes().count()) + QStringLiteral(" DF glyphs: ") +
110 m_glyphs.rawFont().familyName() + QStringLiteral(" ") + QString::number(m_glyphs.rawFont().pixelSize()));
111#endif
112}
113
114void QSGDistanceFieldGlyphNode::setStyle(QQuickText::TextStyle style)
115{
116 if (m_style == style)
117 return;
118 m_style = style;
119 m_dirtyMaterial = true;
120}
121
122void QSGDistanceFieldGlyphNode::setStyleColor(const QColor &color)
123{
124 if (m_styleColor == color)
125 return;
126 m_styleColor = color;
127 m_dirtyMaterial = true;
128}
129
131{
132 if (m_dirtyMaterial)
133 updateMaterial();
134}
135
137{
138 if (m_dirtyGeometry)
140
141 setFlag(UsePreprocess, false);
142}
143
144void QSGDistanceFieldGlyphNode::invalidateGlyphs(const QList<quint32> &glyphs)
145{
146 if (m_dirtyGeometry)
147 return;
148
149 for (int i = 0; i < glyphs.size(); ++i) {
150 if (m_allGlyphIndexesLookup.contains(glyphs.at(i))) {
151 m_dirtyGeometry = true;
152 setFlag(UsePreprocess);
153 return;
154 }
155 }
156}
157
159{
160 if (!m_glyph_cache)
161 return;
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 QList<quint32> indexes = m_glyphs.glyphIndexes();
179 const QList<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(indexes.size(), maxIndexCount);
189 vp.reserve(likelyGlyphCount * 4);
190 ip.reserve(likelyGlyphCount * 6);
191
192 qreal maxTexMargin = m_glyph_cache->distanceFieldRadius();
193 qreal fontScale = m_glyph_cache->fontScale(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(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(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(glyphIndex);
227 glyphInfo.positions.append(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(glyphIndex, 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(cx1, cy1, tx1, ty1);
269 QSGGeometry::TexturedPoint2D v2;
270 v2.set(cx2, cy1, tx2, ty1);
271 QSGGeometry::TexturedPoint2D v3;
272 v3.set(cx1, cy2, tx1, ty2);
273 QSGGeometry::TexturedPoint2D v4;
274 v4.set(cx2, cy2, tx2, ty2);
275 vp.append(v1);
276 vp.append(v2);
277 vp.append(v3);
278 vp.append(v4);
279
280 ip.append(o + 0);
281 ip.append(o + 2);
282 ip.append(o + 3);
283 ip.append(o + 3);
284 ip.append(o + 1);
285 ip.append(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(maxIndexCount, ite->indexes.size() - i);
298 subNodeGlyphRun.setRawData(ite->indexes.constData() + i, ite->positions.constData() + i, len);
299 qCDebug(lcSgText) << "subNodeGlyphRun has" << len << "positions:"
300 << *(ite->positions.constData() + i) << "->" << *(ite->positions.constData() + i + len - 1);
301
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(m_originalPosition, 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(subNode);
312 }
313 ++ite;
314 }
315 }
316
317 s_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" << s_totalAllocation;
320 g->allocate(vp.size(), ip.size());
321 memcpy(g->vertexDataAsTexturedPoint2D(), vp.constData(), vp.size() * sizeof(QSGGeometry::TexturedPoint2D));
322 memcpy(g->indexDataAsUShort(), ip.constData(), ip.size() * sizeof(quint16));
323
324 setBoundingRect(m_boundingRect);
325 markDirty(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(m_glyphs.rawFont().pixelSize()));
367 m_material->setColor(m_color);
368 setMaterial(m_material);
369 m_dirtyMaterial = false;
370}
371
372QT_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.