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 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | |
14 | static 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 | |
20 | QOpenGLTextureGlyphCache::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 | |
51 | QOpenGLTextureGlyphCache::~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) |
60 | static inline bool isCoreProfile() |
61 | { |
62 | return QOpenGLContext::currentContext()->format().profile() == QSurfaceFormat::CoreProfile; |
63 | } |
64 | #endif |
65 | |
66 | void 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 | |
142 | void 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 | |
152 | static 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 | |
222 | static 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 | |
240 | void 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 | |
388 | void 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 | |
408 | int QOpenGLTextureGlyphCache::glyphPadding() const |
409 | { |
410 | if (m_format == QFontEngine::Format_Mono) |
411 | return 8; |
412 | else |
413 | return 1; |
414 | } |
415 | |
416 | int 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 | |
425 | int 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 | |
437 | void 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 | |
454 | QT_END_NAMESPACE |
455 | |