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