1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qopengltextureglyphcache_p.h"
5#include <private/qopenglpaintengine_p.h>
6#include "private/qopenglengineshadersource_p.h"
7#include <private/qopenglextensions_p.h>
8#include <qrgb.h>
9#include <private/qdrawhelper_p.h>
10
11QT_BEGIN_NAMESPACE
12
13
14static int next_qopengltextureglyphcache_serial_number()
15{
16 Q_CONSTINIT static QBasicAtomicInt serial = Q_BASIC_ATOMIC_INITIALIZER(0);
17 return 1 + serial.fetchAndAddRelaxed(valueToAdd: 1);
18}
19
20QOpenGLTextureGlyphCache::QOpenGLTextureGlyphCache(QFontEngine::GlyphFormat format, const QTransform &matrix, const QColor &color)
21 : QImageTextureGlyphCache(format, matrix, color)
22 , m_textureResource(nullptr)
23 , pex(nullptr)
24 , m_blitProgram(nullptr)
25 , m_filterMode(Nearest)
26 , m_serialNumber(next_qopengltextureglyphcache_serial_number())
27 , m_buffer(QOpenGLBuffer::VertexBuffer)
28{
29#ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG
30 qDebug(" -> QOpenGLTextureGlyphCache() %p for context %p.", this, QOpenGLContext::currentContext());
31#endif
32 m_vertexCoordinateArray[0] = -1.0f;
33 m_vertexCoordinateArray[1] = -1.0f;
34 m_vertexCoordinateArray[2] = 1.0f;
35 m_vertexCoordinateArray[3] = -1.0f;
36 m_vertexCoordinateArray[4] = 1.0f;
37 m_vertexCoordinateArray[5] = 1.0f;
38 m_vertexCoordinateArray[6] = -1.0f;
39 m_vertexCoordinateArray[7] = 1.0f;
40
41 m_textureCoordinateArray[0] = 0.0f;
42 m_textureCoordinateArray[1] = 0.0f;
43 m_textureCoordinateArray[2] = 1.0f;
44 m_textureCoordinateArray[3] = 0.0f;
45 m_textureCoordinateArray[4] = 1.0f;
46 m_textureCoordinateArray[5] = 1.0f;
47 m_textureCoordinateArray[6] = 0.0f;
48 m_textureCoordinateArray[7] = 1.0f;
49}
50
51QOpenGLTextureGlyphCache::~QOpenGLTextureGlyphCache()
52{
53#ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG
54 qDebug(" -> ~QOpenGLTextureGlyphCache() %p.", this);
55#endif
56 clear();
57}
58
59#if !QT_CONFIG(opengles2)
60static inline bool isCoreProfile()
61{
62 return QOpenGLContext::currentContext()->format().profile() == QSurfaceFormat::CoreProfile;
63}
64#endif
65
66void QOpenGLTextureGlyphCache::createTextureData(int width, int height)
67{
68 QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext());
69 if (ctx == nullptr) {
70 qWarning(msg: "QOpenGLTextureGlyphCache::createTextureData: Called with no context");
71 return;
72 }
73
74 // create in QImageTextureGlyphCache baseclass is meant to be called
75 // only to create the initial image and does not preserve the content,
76 // so we don't call when this function is called from resize.
77 if (ctx->d_func()->workaround_brokenFBOReadBack && image().isNull())
78 QImageTextureGlyphCache::createTextureData(width, height);
79
80 // Make the lower glyph texture size 16 x 16.
81 if (width < 16)
82 width = 16;
83 if (height < 16)
84 height = 16;
85
86 if (m_textureResource && !m_textureResource->m_texture) {
87 delete m_textureResource;
88 m_textureResource = nullptr;
89 }
90
91 if (!m_textureResource)
92 m_textureResource = new QOpenGLGlyphTexture(ctx);
93
94 QOpenGLFunctions *funcs = ctx->functions();
95 funcs->glGenTextures(n: 1, textures: &m_textureResource->m_texture);
96 funcs->glBindTexture(GL_TEXTURE_2D, texture: m_textureResource->m_texture);
97
98 m_textureResource->m_width = width;
99 m_textureResource->m_height = height;
100
101 if (m_format == QFontEngine::Format_A32 || m_format == QFontEngine::Format_ARGB) {
102 QVarLengthArray<uchar> data(width * height * 4);
103 for (int i = 0; i < data.size(); ++i)
104 data[i] = 0;
105 funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width, height, border: 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels: &data[0]);
106 } else {
107 QVarLengthArray<uchar> data(width * height);
108 for (int i = 0; i < data.size(); ++i)
109 data[i] = 0;
110#if !QT_CONFIG(opengles2)
111 const GLint internalFormat = isCoreProfile() ? GL_R8 : GL_ALPHA;
112 const GLenum format = isCoreProfile() ? GL_RED : GL_ALPHA;
113#else
114 const GLint internalFormat = GL_ALPHA;
115 const GLenum format = GL_ALPHA;
116#endif
117 funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, internalformat: internalFormat, width, height, border: 0, format, GL_UNSIGNED_BYTE, pixels: &data[0]);
118 }
119
120 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
121 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
122 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
123 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
124 m_filterMode = Nearest;
125
126 if (!m_buffer.isCreated()) {
127 m_buffer.create();
128 m_buffer.bind();
129 static GLfloat buf[sizeof(m_vertexCoordinateArray) + sizeof(m_textureCoordinateArray)];
130 memcpy(dest: buf, src: m_vertexCoordinateArray, n: sizeof(m_vertexCoordinateArray));
131 memcpy(dest: buf + (sizeof(m_vertexCoordinateArray) / sizeof(GLfloat)),
132 src: m_textureCoordinateArray,
133 n: sizeof(m_textureCoordinateArray));
134 m_buffer.allocate(data: buf, count: sizeof(buf));
135 m_buffer.release();
136 }
137
138 if (!m_vao.isCreated())
139 m_vao.create();
140}
141
142void QOpenGLTextureGlyphCache::setupVertexAttribs()
143{
144 m_buffer.bind();
145 m_blitProgram->setAttributeBuffer(location: int(QT_VERTEX_COORDS_ATTR), GL_FLOAT, offset: 0, tupleSize: 2);
146 m_blitProgram->setAttributeBuffer(location: int(QT_TEXTURE_COORDS_ATTR), GL_FLOAT, offset: sizeof(m_vertexCoordinateArray), tupleSize: 2);
147 m_blitProgram->enableAttributeArray(location: int(QT_VERTEX_COORDS_ATTR));
148 m_blitProgram->enableAttributeArray(location: int(QT_TEXTURE_COORDS_ATTR));
149 m_buffer.release();
150}
151
152static void load_glyph_image_to_texture(QOpenGLContext *ctx,
153 QImage &img,
154 GLuint texture,
155 int tx, int ty)
156{
157 QOpenGLFunctions *funcs = ctx->functions();
158
159 const int imgWidth = img.width();
160 const int imgHeight = img.height();
161
162 if (img.format() == QImage::Format_Mono) {
163 img = img.convertToFormat(f: QImage::Format_Grayscale8);
164 } else if (img.depth() == 32) {
165 if (img.format() == QImage::Format_RGB32
166 // We need to make the alpha component equal to the average of the RGB values.
167 // This is needed when drawing sub-pixel antialiased text on translucent targets.
168#if Q_BYTE_ORDER == Q_BIG_ENDIAN
169 || img.format() == QImage::Format_ARGB32_Premultiplied
170#else
171 || (img.format() == QImage::Format_ARGB32_Premultiplied
172 && ctx->isOpenGLES())
173#endif
174 ) {
175 for (int y = 0; y < imgHeight; ++y) {
176 QRgb *src = (QRgb *) img.scanLine(y);
177 for (int x = 0; x < imgWidth; ++x) {
178 int r = qRed(rgb: src[x]);
179 int g = qGreen(rgb: src[x]);
180 int b = qBlue(rgb: src[x]);
181 int avg;
182 if (img.format() == QImage::Format_RGB32)
183 avg = (r + g + b + 1) / 3; // "+1" for rounding.
184 else // Format_ARGB_Premultiplied
185 avg = qAlpha(rgb: src[x]);
186
187 src[x] = qRgba(r, g, b, a: avg);
188 // swizzle the bits to accommodate for the GL_RGBA upload.
189#if Q_BYTE_ORDER != Q_BIG_ENDIAN
190 if (ctx->isOpenGLES())
191#endif
192 src[x] = ARGB2RGBA(x: src[x]);
193 }
194 }
195 }
196 }
197
198 funcs->glBindTexture(GL_TEXTURE_2D, texture);
199 if (img.depth() == 32) {
200#if QT_CONFIG(opengles2)
201 GLenum fmt = GL_RGBA;
202#else
203 GLenum fmt = ctx->isOpenGLES() ? GL_RGBA : GL_BGRA;
204#endif // QT_CONFIG(opengles2)
205
206#if Q_BYTE_ORDER == Q_BIG_ENDIAN
207 fmt = GL_RGBA;
208#endif
209 funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: tx, yoffset: ty, width: imgWidth, height: imgHeight, format: fmt, GL_UNSIGNED_BYTE, pixels: img.constBits());
210 } else {
211 // The scanlines in image are 32-bit aligned, even for mono or 8-bit formats. This
212 // is good because it matches the default of 4 bytes for GL_UNPACK_ALIGNMENT.
213#if !QT_CONFIG(opengles2)
214 const GLenum format = isCoreProfile() ? GL_RED : GL_ALPHA;
215#else
216 const GLenum format = GL_ALPHA;
217#endif
218 funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: tx, yoffset: ty, width: imgWidth, height: imgHeight, format, GL_UNSIGNED_BYTE, pixels: img.constBits());
219 }
220}
221
222static void load_glyph_image_region_to_texture(QOpenGLContext *ctx,
223 const QImage &srcImg,
224 int x, int y,
225 int w, int h,
226 GLuint texture,
227 int tx, int ty)
228{
229 Q_ASSERT(x + w <= srcImg.width() && y + h <= srcImg.height());
230
231 QImage img;
232 if (x != 0 || y != 0 || w != srcImg.width() || h != srcImg.height())
233 img = srcImg.copy(x, y, w, h);
234 else
235 img = srcImg;
236
237 load_glyph_image_to_texture(ctx, img, texture, tx, ty);
238}
239
240void QOpenGLTextureGlyphCache::resizeTextureData(int width, int height)
241{
242 QOpenGLContext *ctx = QOpenGLContext::currentContext();
243 if (ctx == nullptr) {
244 qWarning(msg: "QOpenGLTextureGlyphCache::resizeTextureData: Called with no context");
245 return;
246 }
247
248 QOpenGLFunctions *funcs = ctx->functions();
249 GLint oldFbo;
250 funcs->glGetIntegerv(GL_FRAMEBUFFER_BINDING, params: &oldFbo);
251
252 int oldWidth = m_textureResource->m_width;
253 int oldHeight = m_textureResource->m_height;
254
255 // Make the lower glyph texture size 16 x 16.
256 if (width < 16)
257 width = 16;
258 if (height < 16)
259 height = 16;
260
261 GLuint oldTexture = m_textureResource->m_texture;
262 createTextureData(width, height);
263
264 if (ctx->d_func()->workaround_brokenFBOReadBack) {
265 QImageTextureGlyphCache::resizeTextureData(width, height);
266 load_glyph_image_region_to_texture(ctx, srcImg: image(), x: 0, y: 0, w: qMin(a: oldWidth, b: width), h: qMin(a: oldHeight, b: height),
267 texture: m_textureResource->m_texture, tx: 0, ty: 0);
268 return;
269 }
270
271 // ### the QTextureGlyphCache API needs to be reworked to allow
272 // ### resizeTextureData to fail
273
274 funcs->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: m_textureResource->m_fbo);
275
276 GLuint tmp_texture;
277 funcs->glGenTextures(n: 1, textures: &tmp_texture);
278 funcs->glBindTexture(GL_TEXTURE_2D, texture: tmp_texture);
279 funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: oldWidth, height: oldHeight, border: 0,
280 GL_RGBA, GL_UNSIGNED_BYTE, pixels: nullptr);
281 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
282 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
283 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
284 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
285 m_filterMode = Nearest;
286 funcs->glBindTexture(GL_TEXTURE_2D, texture: 0);
287 funcs->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
288 GL_TEXTURE_2D, texture: tmp_texture, level: 0);
289
290 funcs->glActiveTexture(GL_TEXTURE0 + QT_IMAGE_TEXTURE_UNIT);
291 funcs->glBindTexture(GL_TEXTURE_2D, texture: oldTexture);
292
293 if (pex != nullptr)
294 pex->transferMode(newMode: BrushDrawingMode);
295
296 funcs->glDisable(GL_STENCIL_TEST);
297 funcs->glDisable(GL_DEPTH_TEST);
298 funcs->glDisable(GL_SCISSOR_TEST);
299 funcs->glDisable(GL_BLEND);
300
301 funcs->glViewport(x: 0, y: 0, width: oldWidth, height: oldHeight);
302
303 QOpenGLShaderProgram *blitProgram = nullptr;
304 if (pex == nullptr) {
305 if (m_blitProgram == nullptr) {
306 m_blitProgram = new QOpenGLShaderProgram;
307 const bool isCoreProfile = ctx->format().profile() == QSurfaceFormat::CoreProfile;
308
309 {
310 QString source;
311#ifdef Q_OS_WASM
312 source.append(QLatin1StringView(isCoreProfile ? qopenglslUntransformedPositionVertexShader_core : qopenglslUntransformedPositionVertexShader));
313 source.append(QLatin1StringView(isCoreProfile ? qopenglslMainWithTexCoordsVertexShader_core : qopenglslMainWithTexCoordsVertexShader));
314#else
315 source.append(s: QLatin1StringView(isCoreProfile ? qopenglslMainWithTexCoordsVertexShader_core : qopenglslMainWithTexCoordsVertexShader));
316 source.append(s: QLatin1StringView(isCoreProfile ? qopenglslUntransformedPositionVertexShader_core : qopenglslUntransformedPositionVertexShader));
317#endif
318 m_blitProgram->addCacheableShaderFromSourceCode(type: QOpenGLShader::Vertex, source);
319 }
320
321 {
322 QString source;
323#ifdef Q_OS_WASM
324 source.append(QLatin1StringView(isCoreProfile ? qopenglslImageSrcFragmentShader_core : qopenglslImageSrcFragmentShader));
325 source.append(QLatin1StringView(isCoreProfile ? qopenglslMainFragmentShader_core : qopenglslMainFragmentShader));
326#else
327 source.append(s: QLatin1StringView(isCoreProfile ? qopenglslMainFragmentShader_core : qopenglslMainFragmentShader));
328 source.append(s: QLatin1StringView(isCoreProfile ? qopenglslImageSrcFragmentShader_core : qopenglslImageSrcFragmentShader));
329#endif
330 m_blitProgram->addCacheableShaderFromSourceCode(type: QOpenGLShader::Fragment, source);
331 }
332
333 m_blitProgram->bindAttributeLocation(name: "vertexCoordsArray", location: QT_VERTEX_COORDS_ATTR);
334 m_blitProgram->bindAttributeLocation(name: "textureCoordArray", location: QT_TEXTURE_COORDS_ATTR);
335
336 m_blitProgram->link();
337
338 if (m_vao.isCreated()) {
339 m_vao.bind();
340 setupVertexAttribs();
341 }
342 }
343
344 if (m_vao.isCreated())
345 m_vao.bind();
346 else
347 setupVertexAttribs();
348
349 m_blitProgram->bind();
350 blitProgram = m_blitProgram;
351
352 } else {
353 pex->uploadData(arrayIndex: QT_VERTEX_COORDS_ATTR, data: m_vertexCoordinateArray, count: 8);
354 pex->uploadData(arrayIndex: QT_TEXTURE_COORDS_ATTR, data: m_textureCoordinateArray, count: 8);
355
356 pex->shaderManager->useBlitProgram();
357 blitProgram = pex->shaderManager->blitProgram();
358 }
359
360 blitProgram->setUniformValue(name: "imageTexture", QT_IMAGE_TEXTURE_UNIT);
361
362 funcs->glDrawArrays(GL_TRIANGLE_FAN, first: 0, count: 4);
363
364 funcs->glBindTexture(GL_TEXTURE_2D, texture: m_textureResource->m_texture);
365
366 funcs->glCopyTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: 0, yoffset: 0, x: 0, y: 0, width: oldWidth, height: oldHeight);
367
368 funcs->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
369 GL_RENDERBUFFER, renderbuffer: 0);
370 funcs->glDeleteTextures(n: 1, textures: &tmp_texture);
371 funcs->glDeleteTextures(n: 1, textures: &oldTexture);
372
373 funcs->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: (GLuint)oldFbo);
374
375 if (pex != nullptr) {
376 funcs->glViewport(x: 0, y: 0, width: pex->width, height: pex->height);
377 pex->updateClipScissorTest();
378 } else {
379 if (m_vao.isCreated()) {
380 m_vao.release();
381 } else {
382 m_blitProgram->disableAttributeArray(location: int(QT_VERTEX_COORDS_ATTR));
383 m_blitProgram->disableAttributeArray(location: int(QT_TEXTURE_COORDS_ATTR));
384 }
385 }
386}
387
388void QOpenGLTextureGlyphCache::fillTexture(const Coord &c,
389 glyph_t glyph,
390 const QFixedPoint &subPixelPosition)
391{
392 QOpenGLContext *ctx = QOpenGLContext::currentContext();
393 if (ctx == nullptr) {
394 qWarning(msg: "QOpenGLTextureGlyphCache::fillTexture: Called with no context");
395 return;
396 }
397
398 if (ctx->d_func()->workaround_brokenFBOReadBack) {
399 QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition);
400 load_glyph_image_region_to_texture(ctx, srcImg: image(), x: c.x, y: c.y, w: c.w, h: c.h, texture: m_textureResource->m_texture, tx: c.x, ty: c.y);
401 return;
402 }
403
404 QImage mask = textureMapForGlyph(g: glyph, subPixelPosition);
405 load_glyph_image_to_texture(ctx, img&: mask, texture: m_textureResource->m_texture, tx: c.x, ty: c.y);
406}
407
408int QOpenGLTextureGlyphCache::glyphPadding() const
409{
410 if (m_format == QFontEngine::Format_Mono)
411 return 8;
412 else
413 return 1;
414}
415
416int QOpenGLTextureGlyphCache::maxTextureWidth() const
417{
418 QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext());
419 if (ctx == nullptr)
420 return QImageTextureGlyphCache::maxTextureWidth();
421 else
422 return ctx->d_func()->maxTextureSize();
423}
424
425int QOpenGLTextureGlyphCache::maxTextureHeight() const
426{
427 QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext());
428 if (ctx == nullptr)
429 return QImageTextureGlyphCache::maxTextureHeight();
430
431 if (ctx->d_func()->workaround_brokenTexSubImage)
432 return qMin(a: 1024, b: ctx->d_func()->maxTextureSize());
433 else
434 return ctx->d_func()->maxTextureSize();
435}
436
437void QOpenGLTextureGlyphCache::clear()
438{
439 if (m_textureResource)
440 m_textureResource->free();
441 m_textureResource = nullptr;
442
443 delete m_blitProgram;
444 m_blitProgram = nullptr;
445
446 m_w = 0;
447 m_h = 0;
448 m_cx = 0;
449 m_cy = 0;
450 m_currentRowHeight = 0;
451 coords.clear();
452}
453
454QT_END_NAMESPACE
455

source code of qtbase/src/opengl/qopengltextureglyphcache.cpp