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