1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsgdefaultglyphnode_p_p.h"
41#include <private/qsgmaterialshader_p.h>
42
43#include <qopenglshaderprogram.h>
44#include <qopenglframebufferobject.h>
45
46#include <QtGui/private/qguiapplication_p.h>
47#include <qpa/qplatformintegration.h>
48#include <private/qfontengine_p.h>
49#include <private/qopenglextensions_p.h>
50
51#include <QtQuick/qquickwindow.h>
52#include <QtQuick/private/qsgtexture_p.h>
53#include <QtQuick/private/qsgdefaultrendercontext_p.h>
54
55#include <private/qrawfont_p.h>
56#include <QtCore/qmath.h>
57
58QT_BEGIN_NAMESPACE
59
60#ifndef GL_FRAMEBUFFER_SRGB
61#define GL_FRAMEBUFFER_SRGB 0x8DB9
62#endif
63
64#ifndef GL_FRAMEBUFFER_SRGB_CAPABLE
65#define GL_FRAMEBUFFER_SRGB_CAPABLE 0x8DBA
66#endif
67
68static inline QVector4D qsg_premultiply(const QVector4D &c, float globalOpacity)
69{
70 float o = c.w() * globalOpacity;
71 return QVector4D(c.x() * o, c.y() * o, c.z() * o, o);
72}
73
74static inline qreal qt_sRGB_to_linear_RGB(qreal f)
75{
76 return f > 0.04045 ? qPow(x: (f + 0.055) / 1.055, y: 2.4) : f / 12.92;
77}
78
79static inline QVector4D qt_sRGB_to_linear_RGB(const QVector4D &color)
80{
81 return QVector4D(qt_sRGB_to_linear_RGB(f: color.x()),
82 qt_sRGB_to_linear_RGB(f: color.y()),
83 qt_sRGB_to_linear_RGB(f: color.z()),
84 color.w());
85}
86
87static inline qreal fontSmoothingGamma()
88{
89 static qreal fontSmoothingGamma = QGuiApplicationPrivate::platformIntegration()->styleHint(hint: QPlatformIntegration::FontSmoothingGamma).toReal();
90 return fontSmoothingGamma;
91}
92
93
94// ***** legacy (GL) material shader implementations
95
96static inline qreal qsg_device_pixel_ratio(QOpenGLContext *ctx)
97{
98 qreal devicePixelRatio = 1;
99 if (ctx->surface()->surfaceClass() == QSurface::Window) {
100 QWindow *w = static_cast<QWindow *>(ctx->surface());
101 if (QQuickWindow *qw = qobject_cast<QQuickWindow *>(object: w))
102 devicePixelRatio = qw->effectiveDevicePixelRatio();
103 else
104 devicePixelRatio = w->devicePixelRatio();
105 } else {
106 devicePixelRatio = ctx->screen() ? ctx->screen()->devicePixelRatio() : qGuiApp->devicePixelRatio();
107 }
108 return devicePixelRatio;
109}
110
111class QSGTextMaskShader : public QSGMaterialShader
112{
113public:
114 QSGTextMaskShader(QFontEngine::GlyphFormat glyphFormat);
115
116 void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
117 char const *const *attributeNames() const override;
118
119protected:
120 void initialize() override;
121
122 int m_projectionMatrix_id;
123 int m_modelViewMatrix_id;
124 int m_color_id;
125 int m_textureScale_id;
126 float m_devicePixelRatio;
127
128 QFontEngine::GlyphFormat m_glyphFormat;
129};
130
131char const *const *QSGTextMaskShader::attributeNames() const
132{
133 static char const *const attr[] = { "vCoord", "tCoord", nullptr };
134 return attr;
135}
136
137QSGTextMaskShader::QSGTextMaskShader(QFontEngine::GlyphFormat glyphFormat)
138 : QSGMaterialShader(*new QSGMaterialShaderPrivate)
139 , m_projectionMatrix_id(-1)
140 , m_modelViewMatrix_id(-1)
141 , m_color_id(-1)
142 , m_textureScale_id(-1)
143 , m_glyphFormat(glyphFormat)
144{
145 setShaderSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/scenegraph/shaders/textmask.vert"));
146 setShaderSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/textmask.frag"));
147}
148
149void QSGTextMaskShader::initialize()
150{
151 m_projectionMatrix_id = program()->uniformLocation(name: "projectionMatrix");
152 m_modelViewMatrix_id = program()->uniformLocation(name: "modelViewMatrix");
153 m_color_id = program()->uniformLocation(name: "color");
154 m_textureScale_id = program()->uniformLocation(name: "textureScale");
155 m_devicePixelRatio = (float) qsg_device_pixel_ratio(ctx: QOpenGLContext::currentContext());
156 program()->setUniformValue(name: "dpr", value: m_devicePixelRatio);
157}
158
159void QSGTextMaskShader::updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
160{
161 QSGTextMaskMaterial *material = static_cast<QSGTextMaskMaterial *>(newEffect);
162 QSGTextMaskMaterial *oldMaterial = static_cast<QSGTextMaskMaterial *>(oldEffect);
163 Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type());
164 bool updated = material->ensureUpToDate();
165 Q_ASSERT(material->texture());
166
167 Q_ASSERT(oldMaterial == nullptr || oldMaterial->texture());
168 if (updated
169 || oldMaterial == nullptr
170 || oldMaterial->texture()->textureId() != material->texture()->textureId()) {
171 program()->setUniformValue(location: m_textureScale_id, value: QVector2D(1.0 / material->openglGlyphCache()->width(),
172 1.0 / material->openglGlyphCache()->height()));
173 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
174 funcs->glBindTexture(GL_TEXTURE_2D, texture: material->texture()->textureId());
175
176 // Set the mag/min filters to be nearest. We only need to do this when the texture
177 // has been recreated.
178 if (updated) {
179 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
180 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
181 }
182 }
183
184 float devicePixelRatio = (float) qsg_device_pixel_ratio(ctx: QOpenGLContext::currentContext());
185 if (m_devicePixelRatio != devicePixelRatio) {
186 m_devicePixelRatio = devicePixelRatio;
187 program()->setUniformValue(name: "dpr", value: m_devicePixelRatio);
188 }
189
190 if (state.isMatrixDirty()) {
191 program()->setUniformValue(location: m_projectionMatrix_id, value: state.projectionMatrix());
192 program()->setUniformValue(location: m_modelViewMatrix_id, value: state.modelViewMatrix());
193 }
194}
195
196class QSG8BitTextMaskShader : public QSGTextMaskShader
197{
198public:
199 QSG8BitTextMaskShader(QFontEngine::GlyphFormat glyphFormat)
200 : QSGTextMaskShader(glyphFormat)
201 {
202 setShaderSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/8bittextmask.frag"));
203 }
204
205 void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
206};
207
208void QSG8BitTextMaskShader::updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
209{
210 QSGTextMaskShader::updateState(state, newEffect, oldEffect);
211 QSGTextMaskMaterial *material = static_cast<QSGTextMaskMaterial *>(newEffect);
212 QSGTextMaskMaterial *oldMaterial = static_cast<QSGTextMaskMaterial *>(oldEffect);
213
214 if (oldMaterial == nullptr || material->color() != oldMaterial->color() || state.isOpacityDirty()) {
215 QVector4D color = qsg_premultiply(c: material->color(), globalOpacity: state.opacity());
216 program()->setUniformValue(location: m_color_id, value: color);
217 }
218}
219
220class QSG24BitTextMaskShader : public QSGTextMaskShader
221{
222public:
223 QSG24BitTextMaskShader(QFontEngine::GlyphFormat glyphFormat)
224 : QSGTextMaskShader(glyphFormat)
225 , m_useSRGB(false)
226 {
227 setShaderSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/24bittextmask.frag"));
228 }
229
230 void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
231 void initialize() override;
232 void activate() override;
233 void deactivate() override;
234
235 bool useSRGB() const;
236 uint m_useSRGB : 1;
237};
238
239void QSG24BitTextMaskShader::initialize()
240{
241 QSGTextMaskShader::initialize();
242 // 0.25 was found to be acceptable error margin by experimentation. On Mac, the gamma is 2.0,
243 // but using sRGB looks okay.
244 if (QOpenGLContext::currentContext()->hasExtension(QByteArrayLiteral("GL_ARB_framebuffer_sRGB"))
245 && m_glyphFormat == QFontEngine::Format_A32
246 && qAbs(t: fontSmoothingGamma() - 2.2) < 0.25) {
247 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
248 GLint srgbCapable = 0;
249 funcs->glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE, params: &srgbCapable);
250 if (srgbCapable)
251 m_useSRGB = true;
252 }
253}
254
255bool QSG24BitTextMaskShader::useSRGB() const
256{
257#ifdef Q_OS_MACOS
258 if (!m_useSRGB)
259 return false;
260
261 // m_useSRGB is true, but if some QOGLFBO was bound check it's texture format:
262 QOpenGLContext *ctx = QOpenGLContext::currentContext();
263 QOpenGLFramebufferObject *qfbo = QOpenGLContextPrivate::get(ctx)->qgl_current_fbo;
264 bool fboInvalid = QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid;
265 return !qfbo || fboInvalid || qfbo->format().internalTextureFormat() == GL_SRGB8_ALPHA8_EXT;
266#else
267 return m_useSRGB;
268#endif
269}
270
271void QSG24BitTextMaskShader::activate()
272{
273 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
274 funcs->glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
275 if (useSRGB())
276 funcs->glEnable(GL_FRAMEBUFFER_SRGB);
277}
278
279void QSG24BitTextMaskShader::deactivate()
280{
281 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
282 funcs->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
283 if (useSRGB())
284 funcs->glDisable(GL_FRAMEBUFFER_SRGB);
285}
286
287void QSG24BitTextMaskShader::updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
288{
289 QSGTextMaskShader::updateState(state, newEffect, oldEffect);
290 QSGTextMaskMaterial *material = static_cast<QSGTextMaskMaterial *>(newEffect);
291 QSGTextMaskMaterial *oldMaterial = static_cast<QSGTextMaskMaterial *>(oldEffect);
292
293 if (oldMaterial == nullptr || material->color() != oldMaterial->color() || state.isOpacityDirty()) {
294 QVector4D color = material->color();
295 if (useSRGB())
296 color = qt_sRGB_to_linear_RGB(color);
297 QOpenGLContext::currentContext()->functions()->glBlendColor(red: color.x(), green: color.y(), blue: color.z(), alpha: color.w());
298 color = qsg_premultiply(c: color, globalOpacity: state.opacity());
299 program()->setUniformValue(location: m_color_id, value: color.w());
300 }
301}
302
303class QSG32BitColorTextShader : public QSGTextMaskShader
304{
305public:
306 QSG32BitColorTextShader(QFontEngine::GlyphFormat glyphFormat)
307 : QSGTextMaskShader(glyphFormat)
308 {
309 setShaderSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/32bitcolortext.frag"));
310 }
311
312 void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
313};
314
315void QSG32BitColorTextShader::updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
316{
317 QSGTextMaskShader::updateState(state, newEffect, oldEffect);
318 QSGTextMaskMaterial *material = static_cast<QSGTextMaskMaterial *>(newEffect);
319 QSGTextMaskMaterial *oldMaterial = static_cast<QSGTextMaskMaterial *>(oldEffect);
320
321 if (oldMaterial == nullptr || material->color() != oldMaterial->color() || state.isOpacityDirty()) {
322 float opacity = material->color().w() * state.opacity();
323 program()->setUniformValue(location: m_color_id, value: opacity);
324 }
325}
326
327class QSGStyledTextShader : public QSG8BitTextMaskShader
328{
329public:
330 QSGStyledTextShader(QFontEngine::GlyphFormat glyphFormat)
331 : QSG8BitTextMaskShader(glyphFormat)
332 {
333 setShaderSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/scenegraph/shaders/styledtext.vert"));
334 setShaderSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/styledtext.frag"));
335 }
336
337 void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
338
339private:
340 void initialize() override;
341
342 int m_shift_id;
343 int m_styleColor_id;
344};
345
346void QSGStyledTextShader::initialize()
347{
348 QSG8BitTextMaskShader::initialize();
349 m_shift_id = program()->uniformLocation(name: "shift");
350 m_styleColor_id = program()->uniformLocation(name: "styleColor");
351}
352
353void QSGStyledTextShader::updateState(const RenderState &state,
354 QSGMaterial *newEffect,
355 QSGMaterial *oldEffect)
356{
357 Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type());
358
359 QSGStyledTextMaterial *material = static_cast<QSGStyledTextMaterial *>(newEffect);
360 QSGStyledTextMaterial *oldMaterial = static_cast<QSGStyledTextMaterial *>(oldEffect);
361
362 if (oldMaterial == nullptr || oldMaterial->styleShift() != material->styleShift())
363 program()->setUniformValue(location: m_shift_id, value: material->styleShift());
364
365 if (oldMaterial == nullptr || material->color() != oldMaterial->color() || state.isOpacityDirty()) {
366 QVector4D color = qsg_premultiply(c: material->color(), globalOpacity: state.opacity());
367 program()->setUniformValue(location: m_color_id, value: color);
368 }
369
370 if (oldMaterial == nullptr || material->styleColor() != oldMaterial->styleColor() || state.isOpacityDirty()) {
371 QVector4D styleColor = qsg_premultiply(c: material->styleColor(), globalOpacity: state.opacity());
372 program()->setUniformValue(location: m_styleColor_id, value: styleColor);
373 }
374
375 bool updated = material->ensureUpToDate();
376 Q_ASSERT(material->texture());
377
378 Q_ASSERT(oldMaterial == nullptr || oldMaterial->texture());
379 if (updated
380 || oldMaterial == nullptr
381 || oldMaterial->texture()->textureId() != material->texture()->textureId()) {
382 program()->setUniformValue(location: m_textureScale_id, value: QVector2D(1.0 / material->openglGlyphCache()->width(),
383 1.0 / material->openglGlyphCache()->height()));
384 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
385 funcs->glBindTexture(GL_TEXTURE_2D, texture: material->texture()->textureId());
386
387 // Set the mag/min filters to be nearest. We only need to do this when the texture
388 // has been recreated.
389 if (updated) {
390 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
391 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
392 }
393 }
394
395 if (state.isMatrixDirty()) {
396 program()->setUniformValue(location: m_projectionMatrix_id, value: state.projectionMatrix());
397 program()->setUniformValue(location: m_modelViewMatrix_id, value: state.modelViewMatrix());
398 }
399}
400
401class QSGOutlinedTextShader : public QSGStyledTextShader
402{
403public:
404 QSGOutlinedTextShader(QFontEngine::GlyphFormat glyphFormat)
405 : QSGStyledTextShader(glyphFormat)
406 {
407 setShaderSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/scenegraph/shaders/outlinedtext.vert"));
408 setShaderSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/outlinedtext.frag"));
409 }
410};
411
412
413// ***** RHI shader implementations
414
415class QSGTextMaskRhiShader : public QSGMaterialRhiShader
416{
417public:
418 QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat);
419
420 bool updateUniformData(RenderState &state,
421 QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
422 void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
423 QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
424
425protected:
426 QFontEngine::GlyphFormat m_glyphFormat;
427};
428
429QSGTextMaskRhiShader::QSGTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat)
430 : m_glyphFormat(glyphFormat)
431{
432 setShaderFileName(stage: VertexStage,
433 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.vert.qsb"));
434 setShaderFileName(stage: FragmentStage,
435 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/textmask.frag.qsb"));
436}
437
438enum UbufOffset {
439 ModelViewMatrixOffset = 0,
440 ProjectionMatrixOffset = ModelViewMatrixOffset + 64,
441 ColorOffset = ProjectionMatrixOffset + 64,
442 TextureScaleOffset = ColorOffset + 16,
443 DprOffset = TextureScaleOffset + 8,
444
445 // + 1 float padding (vec4 must be aligned to 16)
446 StyleColorOffset = DprOffset + 4 + 4,
447 ShiftOffset = StyleColorOffset + 16
448};
449
450bool QSGTextMaskRhiShader::updateUniformData(RenderState &state,
451 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
452{
453 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
454 QSGTextMaskMaterial *mat = static_cast<QSGTextMaskMaterial *>(newMaterial);
455 QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
456
457 // updateUniformData() is called before updateSampledImage() by the
458 // renderer. Hence updating the glyph cache stuff here.
459 const bool updated = mat->ensureUpToDate();
460 Q_ASSERT(mat->texture());
461 Q_ASSERT(oldMat == nullptr || oldMat->texture());
462
463 bool changed = false;
464 QByteArray *buf = state.uniformData();
465 Q_ASSERT(buf->size() >= DprOffset + 4);
466
467 if (state.isMatrixDirty()) {
468 const QMatrix4x4 mv = state.modelViewMatrix();
469 memcpy(dest: buf->data() + ModelViewMatrixOffset, src: mv.constData(), n: 64);
470 const QMatrix4x4 p = state.projectionMatrix();
471 memcpy(dest: buf->data() + ProjectionMatrixOffset, src: p.constData(), n: 64);
472
473 changed = true;
474 }
475
476 QRhiTexture *oldRtex = oldMat ? QSGTexturePrivate::get(t: oldMat->texture())->rhiTexture() : nullptr;
477 QRhiTexture *newRtex = QSGTexturePrivate::get(t: mat->texture())->rhiTexture();
478 if (updated || !oldMat || oldRtex != newRtex) {
479 const QVector2D textureScale = QVector2D(1.0f / mat->rhiGlyphCache()->width(),
480 1.0f / mat->rhiGlyphCache()->height());
481 memcpy(dest: buf->data() + TextureScaleOffset, src: &textureScale, n: 8);
482 changed = true;
483 }
484
485 if (!oldMat) {
486 float dpr = state.devicePixelRatio();
487 memcpy(dest: buf->data() + DprOffset, src: &dpr, n: 4);
488 }
489
490 // move texture uploads/copies onto the renderer's soon-to-be-committed list
491 mat->rhiGlyphCache()->commitResourceUpdates(mergeInto: state.resourceUpdateBatch());
492
493 return changed;
494}
495
496void QSGTextMaskRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
497 QSGMaterial *newMaterial, QSGMaterial *)
498{
499 Q_UNUSED(state);
500 if (binding != 1)
501 return;
502
503 QSGTextMaskMaterial *mat = static_cast<QSGTextMaskMaterial *>(newMaterial);
504 QSGTexture *t = mat->texture();
505 t->setFiltering(QSGTexture::Nearest);
506 *texture = t;
507}
508
509class QSG8BitTextMaskRhiShader : public QSGTextMaskRhiShader
510{
511public:
512 QSG8BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
513 : QSGTextMaskRhiShader(glyphFormat)
514 {
515 if (alphaTexture)
516 setShaderFileName(stage: FragmentStage,
517 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask_a.frag.qsb"));
518 else
519 setShaderFileName(stage: FragmentStage,
520 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/8bittextmask.frag.qsb"));
521 }
522
523 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
524};
525
526bool QSG8BitTextMaskRhiShader::updateUniformData(RenderState &state,
527 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
528{
529 bool changed = QSGTextMaskRhiShader::updateUniformData(state, newMaterial, oldMaterial);
530
531 QSGTextMaskMaterial *mat = static_cast<QSGTextMaskMaterial *>(newMaterial);
532 QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
533
534 QByteArray *buf = state.uniformData();
535 Q_ASSERT(buf->size() >= ColorOffset + 16);
536
537 if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
538 const QVector4D color = qsg_premultiply(c: mat->color(), globalOpacity: state.opacity());
539 memcpy(dest: buf->data() + ColorOffset, src: &color, n: 16);
540 changed = true;
541 }
542
543 return changed;
544}
545
546class QSG24BitTextMaskRhiShader : public QSGTextMaskRhiShader
547{
548public:
549 QSG24BitTextMaskRhiShader(QFontEngine::GlyphFormat glyphFormat)
550 : QSGTextMaskRhiShader(glyphFormat)
551 {
552 setFlag(flags: UpdatesGraphicsPipelineState, on: true);
553 setShaderFileName(stage: FragmentStage,
554 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/24bittextmask.frag.qsb"));
555 }
556
557 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
558 bool updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps,
559 QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
560};
561
562// ### gamma correction (sRGB) Unsurprisingly, the GL approach is not portable
563// to anything else - it just does not work that way, there is no opt-in/out
564// switch and magic winsys-provided maybe-sRGB buffers. When requesting an sRGB
565// QRhiSwapChain (which we do not do), it is full sRGB, with the sRGB
566// framebuffer update and blending always on... Could we do gamma correction in
567// the shader for text? (but that's bad for blending?)
568
569bool QSG24BitTextMaskRhiShader::updateUniformData(RenderState &state,
570 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
571{
572 bool changed = QSGTextMaskRhiShader::updateUniformData(state, newMaterial, oldMaterial);
573
574 QSGTextMaskMaterial *mat = static_cast<QSGTextMaskMaterial *>(newMaterial);
575 QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
576
577 QByteArray *buf = state.uniformData();
578 Q_ASSERT(buf->size() >= ColorOffset + 16);
579
580 if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
581 // shader takes vec4 but uses alpha only; coloring happens via the blend constant
582 const QVector4D color = qsg_premultiply(c: mat->color(), globalOpacity: state.opacity());
583 memcpy(dest: buf->data() + ColorOffset, src: &color, n: 16);
584 changed = true;
585 }
586
587 return changed;
588}
589
590bool QSG24BitTextMaskRhiShader::updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps,
591 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
592{
593 Q_UNUSED(state);
594 Q_UNUSED(oldMaterial);
595 QSGTextMaskMaterial *mat = static_cast<QSGTextMaskMaterial *>(newMaterial);
596
597 ps->blendEnable = true;
598 ps->srcColor = GraphicsPipelineState::ConstantColor;
599 ps->dstColor = GraphicsPipelineState::OneMinusSrcColor;
600
601 QVector4D color = qsg_premultiply(c: mat->color(), globalOpacity: state.opacity());
602 // if (useSRGB())
603 // color = qt_sRGB_to_linear_RGB(color);
604
605 // this is dynamic state but it's - magic! - taken care of by the renderer
606 ps->blendConstant = QColor::fromRgbF(r: color.x(), g: color.y(), b: color.z(), a: color.w());
607
608 return true;
609}
610
611class QSG32BitColorTextRhiShader : public QSGTextMaskRhiShader
612{
613public:
614 QSG32BitColorTextRhiShader(QFontEngine::GlyphFormat glyphFormat)
615 : QSGTextMaskRhiShader(glyphFormat)
616 {
617 setShaderFileName(stage: FragmentStage,
618 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/32bitcolortext.frag.qsb"));
619 }
620
621 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
622};
623
624bool QSG32BitColorTextRhiShader::updateUniformData(RenderState &state,
625 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
626{
627 bool changed = QSGTextMaskRhiShader::updateUniformData(state, newMaterial, oldMaterial);
628
629 QSGTextMaskMaterial *mat = static_cast<QSGTextMaskMaterial *>(newMaterial);
630 QSGTextMaskMaterial *oldMat = static_cast<QSGTextMaskMaterial *>(oldMaterial);
631
632 QByteArray *buf = state.uniformData();
633 Q_ASSERT(buf->size() >= ColorOffset + 16);
634
635 if (oldMat == nullptr || mat->color() != oldMat->color() || state.isOpacityDirty()) {
636 // shader takes vec4 but uses alpha only
637 const QVector4D color(0, 0, 0, mat->color().w() * state.opacity());
638 memcpy(dest: buf->data() + ColorOffset, src: &color, n: 16);
639 changed = true;
640 }
641
642 return changed;
643}
644
645class QSGStyledTextRhiShader : public QSG8BitTextMaskRhiShader
646{
647public:
648 QSGStyledTextRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
649 : QSG8BitTextMaskRhiShader(glyphFormat, alphaTexture)
650 {
651 setShaderFileName(stage: VertexStage,
652 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.vert.qsb"));
653 if (alphaTexture)
654 setShaderFileName(stage: FragmentStage,
655 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext_a.frag.qsb"));
656 else
657 setShaderFileName(stage: FragmentStage,
658 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/styledtext.frag.qsb"));
659 }
660
661 bool updateUniformData(RenderState &state,
662 QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
663};
664
665bool QSGStyledTextRhiShader::updateUniformData(RenderState &state,
666 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
667{
668 bool changed = QSG8BitTextMaskRhiShader::updateUniformData(state, newMaterial, oldMaterial);
669
670 QSGStyledTextMaterial *mat = static_cast<QSGStyledTextMaterial *>(newMaterial);
671 QSGStyledTextMaterial *oldMat = static_cast<QSGStyledTextMaterial *>(oldMaterial);
672
673 QByteArray *buf = state.uniformData();
674 Q_ASSERT(buf->size() >= ShiftOffset + 8);
675
676 if (oldMat == nullptr || mat->styleColor() != oldMat->styleColor() || state.isOpacityDirty()) {
677 const QVector4D styleColor = qsg_premultiply(c: mat->styleColor(), globalOpacity: state.opacity());
678 memcpy(dest: buf->data() + StyleColorOffset, src: &styleColor, n: 16);
679 changed = true;
680 }
681
682 if (oldMat == nullptr || oldMat->styleShift() != mat->styleShift()) {
683 const QVector2D v = mat->styleShift();
684 memcpy(dest: buf->data() + ShiftOffset, src: &v, n: 8);
685 changed = true;
686 }
687
688 return changed;
689}
690
691class QSGOutlinedTextRhiShader : public QSGStyledTextRhiShader
692{
693public:
694 QSGOutlinedTextRhiShader(QFontEngine::GlyphFormat glyphFormat, bool alphaTexture)
695 : QSGStyledTextRhiShader(glyphFormat, alphaTexture)
696 {
697 setShaderFileName(stage: VertexStage,
698 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.vert.qsb"));
699 if (alphaTexture)
700 setShaderFileName(stage: FragmentStage,
701 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext_a.frag.qsb"));
702 else
703 setShaderFileName(stage: FragmentStage,
704 QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/outlinedtext.frag.qsb"));
705 }
706};
707
708
709// ***** common material stuff
710
711QSGTextMaskMaterial::QSGTextMaskMaterial(QSGRenderContext *rc, const QVector4D &color, const QRawFont &font, QFontEngine::GlyphFormat glyphFormat)
712 : m_rc(qobject_cast<QSGDefaultRenderContext *>(object: rc))
713 , m_texture(nullptr)
714 , m_glyphCache(nullptr)
715 , m_font(font)
716 , m_color(color)
717{
718 init(glyphFormat);
719}
720
721QSGTextMaskMaterial::~QSGTextMaskMaterial()
722{
723 delete m_texture;
724}
725
726void QSGTextMaskMaterial::setColor(const QVector4D &color)
727{
728 if (m_color == color)
729 return;
730
731 m_color = color;
732
733 // If it is an RGB cache, then the pen color is actually part of the cache key
734 // so it has to be updated
735 if (m_glyphCache != nullptr && m_glyphCache->glyphFormat() == QFontEngine::Format_ARGB)
736 updateCache(glyphFormat: QFontEngine::Format_ARGB);
737}
738
739void QSGTextMaskMaterial::init(QFontEngine::GlyphFormat glyphFormat)
740{
741 Q_ASSERT(m_font.isValid());
742
743 setFlag(flags: SupportsRhiShader, on: true);
744 setFlag(flags: Blending, on: true);
745
746 Q_ASSERT(m_rc);
747 m_rhi = m_rc->rhi();
748
749 updateCache(glyphFormat);
750}
751
752void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat)
753{
754 // The following piece of code will read/write to the font engine's caches,
755 // potentially from different threads. However, this is safe because this
756 // code is only called from QQuickItem::updatePaintNode() which is called
757 // only when the GUI is blocked, and multiple threads will call it in
758 // sequence. See also QSGRenderContext::invalidate
759
760 QRawFontPrivate *fontD = QRawFontPrivate::get(font: m_font);
761 if (QFontEngine *fontEngine = fontD->fontEngine) {
762 if (glyphFormat == QFontEngine::Format_None) {
763 glyphFormat = fontEngine->glyphFormat != QFontEngine::Format_None
764 ? fontEngine->glyphFormat
765 : QFontEngine::Format_A32;
766 }
767
768 QOpenGLContext *ctx = nullptr;
769 qreal devicePixelRatio;
770 void *cacheKey;
771 if (m_rhi) {
772 cacheKey = m_rhi;
773 // Get the dpr the modern way. This value retrieved via the
774 // rendercontext matches what RenderState::devicePixelRatio()
775 // exposes to the material shaders later on.
776 devicePixelRatio = m_rc->currentDevicePixelRatio();
777 } else {
778 ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext());
779 Q_ASSERT(ctx != nullptr);
780 cacheKey = ctx;
781 devicePixelRatio = qsg_device_pixel_ratio(ctx); // this is technically incorrect, see other branch above
782 }
783
784 QTransform glyphCacheTransform = QTransform::fromScale(dx: devicePixelRatio, dy: devicePixelRatio);
785 if (!fontEngine->supportsTransformation(transform: glyphCacheTransform))
786 glyphCacheTransform = QTransform();
787
788 QColor color = glyphFormat == QFontEngine::Format_ARGB ? QColor::fromRgbF(r: m_color.x(), g: m_color.y(), b: m_color.z(), a: m_color.w()) : QColor();
789 m_glyphCache = fontEngine->glyphCache(key: cacheKey, format: glyphFormat, transform: glyphCacheTransform, color);
790 if (!m_glyphCache || int(m_glyphCache->glyphFormat()) != glyphFormat) {
791 if (m_rhi)
792 m_glyphCache = new QSGRhiTextureGlyphCache(m_rhi, glyphFormat, glyphCacheTransform, color);
793 else
794 m_glyphCache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform, color);
795
796 fontEngine->setGlyphCache(key: cacheKey, data: m_glyphCache.data());
797 m_rc->registerFontengineForCleanup(engine: fontEngine);
798 }
799 }
800}
801
802void QSGTextMaskMaterial::populate(const QPointF &p,
803 const QVector<quint32> &glyphIndexes,
804 const QVector<QPointF> &glyphPositions,
805 QSGGeometry *geometry,
806 QRectF *boundingRect,
807 QPointF *baseLine,
808 const QMargins &margins)
809{
810 Q_ASSERT(m_font.isValid());
811 QPointF position(p.x(), p.y() - m_font.ascent());
812 QVector<QFixedPoint> fixedPointPositions;
813 const int glyphPositionsSize = glyphPositions.size();
814 fixedPointPositions.reserve(asize: glyphPositionsSize);
815 for (int i=0; i < glyphPositionsSize; ++i)
816 fixedPointPositions.append(t: QFixedPoint::fromPointF(p: position + glyphPositions.at(i)));
817
818 QTextureGlyphCache *cache = glyphCache();
819
820 QRawFontPrivate *fontD = QRawFontPrivate::get(font: m_font);
821 cache->populate(fontEngine: fontD->fontEngine, numGlyphs: glyphIndexes.size(), glyphs: glyphIndexes.constData(),
822 positions: fixedPointPositions.data());
823 cache->fillInPendingGlyphs();
824
825 int margin = fontD->fontEngine->glyphMargin(format: cache->glyphFormat());
826
827 qreal glyphCacheScaleX = cache->transform().m11();
828 qreal glyphCacheScaleY = cache->transform().m22();
829 qreal glyphCacheInverseScaleX = 1.0 / glyphCacheScaleX;
830 qreal glyphCacheInverseScaleY = 1.0 / glyphCacheScaleY;
831 qreal scaledMargin = margin * glyphCacheInverseScaleX;
832
833 Q_ASSERT(geometry->indexType() == GL_UNSIGNED_SHORT);
834 geometry->allocate(vertexCount: glyphIndexes.size() * 4, indexCount: glyphIndexes.size() * 6);
835 QVector4D *vp = (QVector4D *)geometry->vertexDataAsTexturedPoint2D();
836 Q_ASSERT(geometry->sizeOfVertex() == sizeof(QVector4D));
837 ushort *ip = geometry->indexDataAsUShort();
838
839 bool supportsSubPixelPositions = fontD->fontEngine->supportsSubPixelPositions();
840 for (int i=0; i<glyphIndexes.size(); ++i) {
841 QPointF glyphPosition = glyphPositions.at(i) + position;
842 QFixed subPixelPosition;
843 if (supportsSubPixelPositions)
844 subPixelPosition = fontD->fontEngine->subPixelPositionForX(x: QFixed::fromReal(r: glyphPosition.x()));
845
846 QTextureGlyphCache::GlyphAndSubPixelPosition glyph(glyphIndexes.at(i), subPixelPosition);
847 const QTextureGlyphCache::Coord &c = cache->coords.value(akey: glyph);
848
849 // On a retina screen the glyph positions are not pre-scaled (as opposed to
850 // eg. the raster paint engine). To ensure that we get the same behavior as
851 // the raster engine (and CoreText itself) when it comes to rounding of the
852 // coordinates, we need to apply the scale factor before rounding, and then
853 // apply the inverse scale to get back to the coordinate system of the node.
854
855 qreal x = (qFloor(v: glyphPosition.x() * glyphCacheScaleX) * glyphCacheInverseScaleX) +
856 (c.baseLineX * glyphCacheInverseScaleX) - scaledMargin;
857 qreal y = (qRound(d: glyphPosition.y() * glyphCacheScaleY) * glyphCacheInverseScaleY) -
858 (c.baseLineY * glyphCacheInverseScaleY) - scaledMargin;
859
860 qreal w = c.w * glyphCacheInverseScaleX;
861 qreal h = c.h * glyphCacheInverseScaleY;
862
863 *boundingRect |= QRectF(x + scaledMargin, y + scaledMargin, w, h);
864
865 float cx1 = x - margins.left();
866 float cx2 = x + w + margins.right();
867 float cy1 = y - margins.top();
868 float cy2 = y + h + margins.bottom();
869
870 float tx1 = c.x - margins.left();
871 float tx2 = c.x + c.w + margins.right();
872 float ty1 = c.y - margins.top();
873 float ty2 = c.y + c.h + margins.bottom();
874
875 if (baseLine->isNull())
876 *baseLine = glyphPosition;
877
878 vp[4 * i + 0] = QVector4D(cx1, cy1, tx1, ty1);
879 vp[4 * i + 1] = QVector4D(cx2, cy1, tx2, ty1);
880 vp[4 * i + 2] = QVector4D(cx1, cy2, tx1, ty2);
881 vp[4 * i + 3] = QVector4D(cx2, cy2, tx2, ty2);
882
883 int o = i * 4;
884 ip[6 * i + 0] = o + 0;
885 ip[6 * i + 1] = o + 2;
886 ip[6 * i + 2] = o + 3;
887 ip[6 * i + 3] = o + 3;
888 ip[6 * i + 4] = o + 1;
889 ip[6 * i + 5] = o + 0;
890 }
891}
892
893QSGMaterialType *QSGTextMaskMaterial::type() const
894{
895 static QSGMaterialType argb, rgb, gray;
896 switch (glyphCache()->glyphFormat()) {
897 case QFontEngine::Format_ARGB:
898 return &argb;
899 case QFontEngine::Format_A32:
900 return &rgb;
901 case QFontEngine::Format_A8:
902 default:
903 return &gray;
904 }
905}
906
907QTextureGlyphCache *QSGTextMaskMaterial::glyphCache() const
908{
909 return static_cast<QTextureGlyphCache *>(m_glyphCache.data());
910}
911
912QOpenGLTextureGlyphCache *QSGTextMaskMaterial::openglGlyphCache() const
913{
914 return static_cast<QOpenGLTextureGlyphCache *>(glyphCache());
915}
916
917QSGRhiTextureGlyphCache *QSGTextMaskMaterial::rhiGlyphCache() const
918{
919 return static_cast<QSGRhiTextureGlyphCache *>(glyphCache());
920}
921
922QSGMaterialShader *QSGTextMaskMaterial::createShader() const
923{
924 if (flags().testFlag(flag: RhiShaderWanted)) {
925 QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
926 const QFontEngine::GlyphFormat glyphFormat = gc->glyphFormat();
927 switch (glyphFormat) {
928 case QFontEngine::Format_ARGB:
929 return new QSG32BitColorTextRhiShader(glyphFormat);
930 case QFontEngine::Format_A32:
931 return new QSG24BitTextMaskRhiShader(glyphFormat);
932 case QFontEngine::Format_A8:
933 default:
934 return new QSG8BitTextMaskRhiShader(glyphFormat, gc->eightBitFormatIsAlphaSwizzled());
935 }
936 } else {
937 switch (QFontEngine::GlyphFormat glyphFormat = glyphCache()->glyphFormat()) {
938 case QFontEngine::Format_ARGB:
939 return new QSG32BitColorTextShader(glyphFormat);
940 case QFontEngine::Format_A32:
941 return new QSG24BitTextMaskShader(glyphFormat);
942 case QFontEngine::Format_A8:
943 default:
944 return new QSG8BitTextMaskShader(glyphFormat);
945 }
946 }
947}
948
949static inline int qsg_colorDiff(const QVector4D &a, const QVector4D &b)
950{
951 if (a.x() != b.x())
952 return a.x() > b.x() ? 1 : -1;
953 if (a.y() != b.y())
954 return a.y() > b.y() ? 1 : -1;
955 if (a.z() != b.z())
956 return a.z() > b.z() ? 1 : -1;
957 if (a.w() != b.w())
958 return a.w() > b.w() ? 1 : -1;
959 return 0;
960}
961
962int QSGTextMaskMaterial::compare(const QSGMaterial *o) const
963{
964 Q_ASSERT(o && type() == o->type());
965 const QSGTextMaskMaterial *other = static_cast<const QSGTextMaskMaterial *>(o);
966 if (m_glyphCache != other->m_glyphCache)
967 return m_glyphCache.data() < other->m_glyphCache.data() ? -1 : 1;
968 return qsg_colorDiff(a: m_color, b: other->m_color);
969}
970
971bool QSGTextMaskMaterial::ensureUpToDate()
972{
973 if (m_rhi) {
974 QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
975 QSize glyphCacheSize(gc->width(), gc->height());
976 if (glyphCacheSize != m_size) {
977 if (m_texture)
978 delete m_texture;
979 m_texture = new QSGPlainTexture;
980 m_texture->setTexture(gc->texture());
981 m_texture->setTextureSize(QSize(gc->width(), gc->height()));
982 m_texture->setOwnsTexture(false);
983 m_size = glyphCacheSize;
984 return true;
985 }
986 return false;
987
988 } else {
989 QSize glyphCacheSize(openglGlyphCache()->width(), openglGlyphCache()->height());
990 if (glyphCacheSize != m_size) {
991 if (m_texture)
992 delete m_texture;
993 m_texture = new QSGPlainTexture();
994 m_texture->setTextureId(openglGlyphCache()->texture());
995 m_texture->setTextureSize(QSize(openglGlyphCache()->width(), openglGlyphCache()->height()));
996 m_texture->setOwnsTexture(false);
997 m_size = glyphCacheSize;
998 return true;
999 }
1000 return false;
1001 }
1002}
1003
1004
1005QSGStyledTextMaterial::QSGStyledTextMaterial(QSGRenderContext *rc, const QRawFont &font)
1006 : QSGTextMaskMaterial(rc, QVector4D(), font, QFontEngine::Format_A8)
1007{
1008}
1009
1010QSGMaterialType *QSGStyledTextMaterial::type() const
1011{
1012 static QSGMaterialType type;
1013 return &type;
1014}
1015
1016QSGMaterialShader *QSGStyledTextMaterial::createShader() const
1017{
1018 if (flags().testFlag(flag: RhiShaderWanted)) {
1019 QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
1020 return new QSGStyledTextRhiShader(gc->glyphFormat(), gc->eightBitFormatIsAlphaSwizzled());
1021 } else {
1022 return new QSGStyledTextShader(glyphCache()->glyphFormat());
1023 }
1024}
1025
1026int QSGStyledTextMaterial::compare(const QSGMaterial *o) const
1027{
1028 const QSGStyledTextMaterial *other = static_cast<const QSGStyledTextMaterial *>(o);
1029
1030 if (m_styleShift != other->m_styleShift)
1031 return m_styleShift.y() - other->m_styleShift.y();
1032
1033 int diff = qsg_colorDiff(a: m_styleColor, b: other->m_styleColor);
1034 if (diff == 0)
1035 return QSGTextMaskMaterial::compare(o);
1036 return diff;
1037}
1038
1039
1040QSGOutlinedTextMaterial::QSGOutlinedTextMaterial(QSGRenderContext *rc, const QRawFont &font)
1041 : QSGStyledTextMaterial(rc, font)
1042{
1043}
1044
1045QSGMaterialType *QSGOutlinedTextMaterial::type() const
1046{
1047 static QSGMaterialType type;
1048 return &type;
1049}
1050
1051QSGMaterialShader *QSGOutlinedTextMaterial::createShader() const
1052{
1053 if (flags().testFlag(flag: RhiShaderWanted)) {
1054 QSGRhiTextureGlyphCache *gc = rhiGlyphCache();
1055 return new QSGOutlinedTextRhiShader(gc->glyphFormat(), gc->eightBitFormatIsAlphaSwizzled());
1056 } else {
1057 return new QSGOutlinedTextShader(glyphCache()->glyphFormat());
1058 }
1059}
1060
1061QT_END_NAMESPACE
1062

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