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 | |
58 | QT_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 | |
68 | static 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 | |
74 | static 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 | |
79 | static 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 | |
87 | static 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 | |
96 | static 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 | |
111 | class QSGTextMaskShader : public QSGMaterialShader |
112 | { |
113 | public: |
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 | |
119 | protected: |
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 | |
131 | char const *const *QSGTextMaskShader::attributeNames() const |
132 | { |
133 | static char const *const attr[] = { "vCoord" , "tCoord" , nullptr }; |
134 | return attr; |
135 | } |
136 | |
137 | QSGTextMaskShader::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 | |
149 | void 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 | |
159 | void 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 | |
196 | class QSG8BitTextMaskShader : public QSGTextMaskShader |
197 | { |
198 | public: |
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 | |
208 | void 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 | |
220 | class QSG24BitTextMaskShader : public QSGTextMaskShader |
221 | { |
222 | public: |
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 | |
239 | void 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 | |
255 | bool 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 | |
271 | void 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 | |
279 | void 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 | |
287 | void 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 | |
303 | class QSG32BitColorTextShader : public QSGTextMaskShader |
304 | { |
305 | public: |
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 | |
315 | void 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 | |
327 | class QSGStyledTextShader : public QSG8BitTextMaskShader |
328 | { |
329 | public: |
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 | |
339 | private: |
340 | void initialize() override; |
341 | |
342 | int m_shift_id; |
343 | int m_styleColor_id; |
344 | }; |
345 | |
346 | void QSGStyledTextShader::initialize() |
347 | { |
348 | QSG8BitTextMaskShader::initialize(); |
349 | m_shift_id = program()->uniformLocation(name: "shift" ); |
350 | m_styleColor_id = program()->uniformLocation(name: "styleColor" ); |
351 | } |
352 | |
353 | void 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 | |
401 | class QSGOutlinedTextShader : public QSGStyledTextShader |
402 | { |
403 | public: |
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 | |
415 | class QSGTextMaskRhiShader : public QSGMaterialRhiShader |
416 | { |
417 | public: |
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 | |
425 | protected: |
426 | QFontEngine::GlyphFormat m_glyphFormat; |
427 | }; |
428 | |
429 | QSGTextMaskRhiShader::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 | |
438 | enum 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 | |
450 | bool 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 | |
496 | void 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 | |
509 | class QSG8BitTextMaskRhiShader : public QSGTextMaskRhiShader |
510 | { |
511 | public: |
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 | |
526 | bool 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 | |
546 | class QSG24BitTextMaskRhiShader : public QSGTextMaskRhiShader |
547 | { |
548 | public: |
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 | |
569 | bool 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 | |
590 | bool 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 | |
611 | class QSG32BitColorTextRhiShader : public QSGTextMaskRhiShader |
612 | { |
613 | public: |
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 | |
624 | bool 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 | |
645 | class QSGStyledTextRhiShader : public QSG8BitTextMaskRhiShader |
646 | { |
647 | public: |
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 | |
665 | bool 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 | |
691 | class QSGOutlinedTextRhiShader : public QSGStyledTextRhiShader |
692 | { |
693 | public: |
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 | |
711 | QSGTextMaskMaterial::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 | |
721 | QSGTextMaskMaterial::~QSGTextMaskMaterial() |
722 | { |
723 | delete m_texture; |
724 | } |
725 | |
726 | void 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 | |
739 | void 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 | |
752 | void 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 | |
802 | void 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 | |
893 | QSGMaterialType *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 | |
907 | QTextureGlyphCache *QSGTextMaskMaterial::glyphCache() const |
908 | { |
909 | return static_cast<QTextureGlyphCache *>(m_glyphCache.data()); |
910 | } |
911 | |
912 | QOpenGLTextureGlyphCache *QSGTextMaskMaterial::openglGlyphCache() const |
913 | { |
914 | return static_cast<QOpenGLTextureGlyphCache *>(glyphCache()); |
915 | } |
916 | |
917 | QSGRhiTextureGlyphCache *QSGTextMaskMaterial::rhiGlyphCache() const |
918 | { |
919 | return static_cast<QSGRhiTextureGlyphCache *>(glyphCache()); |
920 | } |
921 | |
922 | QSGMaterialShader *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 | |
949 | static 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 | |
962 | int 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 | |
971 | bool 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 | |
1005 | QSGStyledTextMaterial::QSGStyledTextMaterial(QSGRenderContext *rc, const QRawFont &font) |
1006 | : QSGTextMaskMaterial(rc, QVector4D(), font, QFontEngine::Format_A8) |
1007 | { |
1008 | } |
1009 | |
1010 | QSGMaterialType *QSGStyledTextMaterial::type() const |
1011 | { |
1012 | static QSGMaterialType type; |
1013 | return &type; |
1014 | } |
1015 | |
1016 | QSGMaterialShader *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 | |
1026 | int 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 | |
1040 | QSGOutlinedTextMaterial::QSGOutlinedTextMaterial(QSGRenderContext *rc, const QRawFont &font) |
1041 | : QSGStyledTextMaterial(rc, font) |
1042 | { |
1043 | } |
1044 | |
1045 | QSGMaterialType *QSGOutlinedTextMaterial::type() const |
1046 | { |
1047 | static QSGMaterialType type; |
1048 | return &type; |
1049 | } |
1050 | |
1051 | QSGMaterialShader *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 | |
1061 | QT_END_NAMESPACE |
1062 | |