| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2018 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 "qsgopengldistancefieldglyphcache_p.h" |
| 41 | |
| 42 | #include <QtCore/qelapsedtimer.h> |
| 43 | #include <QtCore/qbuffer.h> |
| 44 | #include <QtCore/qendian.h> |
| 45 | #include <QtQml/qqmlfile.h> |
| 46 | |
| 47 | #include <QtGui/private/qdistancefield_p.h> |
| 48 | #include <QtGui/private/qopenglcontext_p.h> |
| 49 | #include <QtQml/private/qqmlglobal_p.h> |
| 50 | #include <qopenglfunctions.h> |
| 51 | #include <qopenglframebufferobject.h> |
| 52 | #include <qmath.h> |
| 53 | #include "qsgcontext_p.h" |
| 54 | |
| 55 | |
| 56 | #if !defined(QT_OPENGL_ES_2) |
| 57 | #include <QtGui/qopenglfunctions_3_2_core.h> |
| 58 | #endif |
| 59 | |
| 60 | QT_BEGIN_NAMESPACE |
| 61 | |
| 62 | DEFINE_BOOL_CONFIG_OPTION(qmlUseGlyphCacheWorkaround, QML_USE_GLYPHCACHE_WORKAROUND) |
| 63 | DEFINE_BOOL_CONFIG_OPTION(qsgPreferFullSizeGlyphCacheTextures, QSG_PREFER_FULLSIZE_GLYPHCACHE_TEXTURES) |
| 64 | |
| 65 | #if !defined(QSG_OPENGL_DISTANCEFIELD_GLYPH_CACHE_PADDING) |
| 66 | # define QSG_OPENGL_DISTANCEFIELD_GLYPH_CACHE_PADDING 2 |
| 67 | #endif |
| 68 | |
| 69 | QSGOpenGLDistanceFieldGlyphCache::QSGOpenGLDistanceFieldGlyphCache(QOpenGLContext *c, |
| 70 | const QRawFont &font) |
| 71 | : QSGDistanceFieldGlyphCache(font) |
| 72 | , m_maxTextureWidth(0) |
| 73 | , m_maxTextureHeight(0) |
| 74 | , m_maxTextureCount(3) |
| 75 | , m_areaAllocator(nullptr) |
| 76 | , m_blitProgram(nullptr) |
| 77 | , m_blitBuffer(QOpenGLBuffer::VertexBuffer) |
| 78 | , m_fboGuard(nullptr) |
| 79 | , m_funcs(c->functions()) |
| 80 | #if !defined(QT_OPENGL_ES_2) |
| 81 | , m_coreFuncs(nullptr) |
| 82 | #endif |
| 83 | { |
| 84 | if (Q_LIKELY(m_blitBuffer.create())) { |
| 85 | m_blitBuffer.bind(); |
| 86 | static const GLfloat buffer[16] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, |
| 87 | 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; |
| 88 | m_blitBuffer.allocate(data: buffer, count: sizeof(buffer)); |
| 89 | m_blitBuffer.release(); |
| 90 | } else { |
| 91 | qWarning(msg: "Buffer creation failed" ); |
| 92 | } |
| 93 | |
| 94 | m_coreProfile = (c->format().profile() == QSurfaceFormat::CoreProfile); |
| 95 | |
| 96 | // Load a pregenerated cache if the font contains one |
| 97 | loadPregeneratedCache(font); |
| 98 | } |
| 99 | |
| 100 | QSGOpenGLDistanceFieldGlyphCache::~QSGOpenGLDistanceFieldGlyphCache() |
| 101 | { |
| 102 | for (int i = 0; i < m_textures.count(); ++i) |
| 103 | m_funcs->glDeleteTextures(n: 1, textures: &m_textures[i].texture); |
| 104 | |
| 105 | if (m_fboGuard != nullptr) |
| 106 | m_fboGuard->free(); |
| 107 | |
| 108 | delete m_blitProgram; |
| 109 | delete m_areaAllocator; |
| 110 | } |
| 111 | |
| 112 | void QSGOpenGLDistanceFieldGlyphCache::requestGlyphs(const QSet<glyph_t> &glyphs) |
| 113 | { |
| 114 | QList<GlyphPosition> glyphPositions; |
| 115 | QVector<glyph_t> glyphsToRender; |
| 116 | |
| 117 | const int padding = QSG_OPENGL_DISTANCEFIELD_GLYPH_CACHE_PADDING; |
| 118 | const qreal scaleFactor = qreal(1) / QT_DISTANCEFIELD_SCALE(narrowOutlineFont: m_doubleGlyphResolution); |
| 119 | |
| 120 | if (m_maxTextureHeight == 0) { |
| 121 | m_funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &m_maxTextureWidth); |
| 122 | |
| 123 | // We need to add a buffer to avoid glyphs that overlap the border between two |
| 124 | // textures causing the height of the textures to extend beyond the limit. |
| 125 | m_maxTextureHeight = m_maxTextureWidth - (qCeil(v: m_referenceFont.pixelSize() * scaleFactor + distanceFieldRadius() * 2) + padding * 2); |
| 126 | } |
| 127 | |
| 128 | if (m_areaAllocator == nullptr) |
| 129 | m_areaAllocator = new QSGAreaAllocator(QSize(m_maxTextureWidth, m_maxTextureCount * m_maxTextureHeight)); |
| 130 | |
| 131 | for (QSet<glyph_t>::const_iterator it = glyphs.constBegin(); it != glyphs.constEnd() ; ++it) { |
| 132 | glyph_t glyphIndex = *it; |
| 133 | |
| 134 | QRectF boundingRect = glyphData(glyph: glyphIndex).boundingRect; |
| 135 | int glyphWidth = qCeil(v: boundingRect.width() + distanceFieldRadius()) * 2; |
| 136 | int glyphHeight = qCeil(v: boundingRect.height() + distanceFieldRadius()) * 2; |
| 137 | QSize glyphSize(glyphWidth + padding * 2, glyphHeight + padding * 2); |
| 138 | QRect alloc = m_areaAllocator->allocate(size: glyphSize); |
| 139 | |
| 140 | if (alloc.isNull()) { |
| 141 | // Unallocate unused glyphs until we can allocated the new glyph |
| 142 | while (alloc.isNull() && !m_unusedGlyphs.isEmpty()) { |
| 143 | glyph_t unusedGlyph = *m_unusedGlyphs.constBegin(); |
| 144 | |
| 145 | TexCoord unusedCoord = glyphTexCoord(glyph: unusedGlyph); |
| 146 | QRectF unusedGlyphBoundingRect = glyphData(glyph: unusedGlyph).boundingRect; |
| 147 | int unusedGlyphWidth = qCeil(v: unusedGlyphBoundingRect.width() + distanceFieldRadius()) * 2; |
| 148 | int unusedGlyphHeight = qCeil(v: unusedGlyphBoundingRect.height() + distanceFieldRadius()) * 2; |
| 149 | m_areaAllocator->deallocate(rect: QRect(unusedCoord.x - padding, |
| 150 | unusedCoord.y - padding, |
| 151 | padding * 2 + unusedGlyphWidth, |
| 152 | padding * 2 + unusedGlyphHeight)); |
| 153 | |
| 154 | m_unusedGlyphs.remove(value: unusedGlyph); |
| 155 | m_glyphsTexture.remove(akey: unusedGlyph); |
| 156 | removeGlyph(glyph: unusedGlyph); |
| 157 | |
| 158 | alloc = m_areaAllocator->allocate(size: glyphSize); |
| 159 | } |
| 160 | |
| 161 | // Not enough space left for this glyph... skip to the next one |
| 162 | if (alloc.isNull()) |
| 163 | continue; |
| 164 | } |
| 165 | |
| 166 | TextureInfo *tex = textureInfo(index: alloc.y() / m_maxTextureHeight); |
| 167 | alloc = QRect(alloc.x(), alloc.y() % m_maxTextureHeight, alloc.width(), alloc.height()); |
| 168 | |
| 169 | tex->allocatedArea |= alloc; |
| 170 | Q_ASSERT(tex->padding == padding || tex->padding < 0); |
| 171 | tex->padding = padding; |
| 172 | |
| 173 | GlyphPosition p; |
| 174 | p.glyph = glyphIndex; |
| 175 | p.position = alloc.topLeft() + QPoint(padding, padding); |
| 176 | |
| 177 | glyphPositions.append(t: p); |
| 178 | glyphsToRender.append(t: glyphIndex); |
| 179 | m_glyphsTexture.insert(akey: glyphIndex, avalue: tex); |
| 180 | } |
| 181 | |
| 182 | setGlyphsPosition(glyphPositions); |
| 183 | markGlyphsToRender(glyphs: glyphsToRender); |
| 184 | } |
| 185 | |
| 186 | void QSGOpenGLDistanceFieldGlyphCache::storeGlyphs(const QList<QDistanceField> &glyphs) |
| 187 | { |
| 188 | typedef QHash<TextureInfo *, QVector<glyph_t> > GlyphTextureHash; |
| 189 | typedef GlyphTextureHash::const_iterator GlyphTextureHashConstIt; |
| 190 | |
| 191 | GlyphTextureHash glyphTextures; |
| 192 | |
| 193 | GLint alignment = 4; // default value |
| 194 | m_funcs->glGetIntegerv(GL_UNPACK_ALIGNMENT, params: &alignment); |
| 195 | |
| 196 | // Distance field data is always tightly packed |
| 197 | m_funcs->glPixelStorei(GL_UNPACK_ALIGNMENT, param: 1); |
| 198 | |
| 199 | for (int i = 0; i < glyphs.size(); ++i) { |
| 200 | QDistanceField glyph = glyphs.at(i); |
| 201 | glyph_t glyphIndex = glyph.glyph(); |
| 202 | TexCoord c = glyphTexCoord(glyph: glyphIndex); |
| 203 | TextureInfo *texInfo = m_glyphsTexture.value(akey: glyphIndex); |
| 204 | |
| 205 | resizeTexture(texInfo, width: texInfo->allocatedArea.width(), height: texInfo->allocatedArea.height()); |
| 206 | m_funcs->glBindTexture(GL_TEXTURE_2D, texture: texInfo->texture); |
| 207 | |
| 208 | glyphTextures[texInfo].append(t: glyphIndex); |
| 209 | |
| 210 | int padding = texInfo->padding; |
| 211 | int expectedWidth = qCeil(v: c.width + c.xMargin * 2); |
| 212 | glyph = glyph.copy(x: -padding, y: -padding, |
| 213 | w: expectedWidth + padding * 2, h: glyph.height() + padding * 2); |
| 214 | |
| 215 | if (useTextureResizeWorkaround()) { |
| 216 | uchar *inBits = glyph.scanLine(0); |
| 217 | uchar *outBits = texInfo->image.scanLine(int(c.y) - padding) + int(c.x) - padding; |
| 218 | for (int y = 0; y < glyph.height(); ++y) { |
| 219 | memcpy(dest: outBits, src: inBits, n: glyph.width()); |
| 220 | inBits += glyph.width(); |
| 221 | outBits += texInfo->image.width(); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | #if !defined(QT_OPENGL_ES_2) |
| 226 | const GLenum format = isCoreProfile() ? GL_RED : GL_ALPHA; |
| 227 | #else |
| 228 | const GLenum format = GL_ALPHA; |
| 229 | #endif |
| 230 | if (useTextureUploadWorkaround()) { |
| 231 | for (int i = 0; i < glyph.height(); ++i) { |
| 232 | m_funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, |
| 233 | xoffset: c.x - padding, yoffset: c.y + i - padding, width: glyph.width(),height: 1, |
| 234 | format, GL_UNSIGNED_BYTE, |
| 235 | pixels: glyph.scanLine(i)); |
| 236 | } |
| 237 | } else { |
| 238 | m_funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, |
| 239 | xoffset: c.x - padding, yoffset: c.y - padding, width: glyph.width(), height: glyph.height(), |
| 240 | format, GL_UNSIGNED_BYTE, |
| 241 | pixels: glyph.constBits()); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | // restore to previous alignment |
| 246 | m_funcs->glPixelStorei(GL_UNPACK_ALIGNMENT, param: alignment); |
| 247 | |
| 248 | for (GlyphTextureHashConstIt i = glyphTextures.constBegin(), cend = glyphTextures.constEnd(); i != cend; ++i) { |
| 249 | Texture t; |
| 250 | t.textureId = i.key()->texture; |
| 251 | t.size = i.key()->size; |
| 252 | t.rhiBased = false; |
| 253 | setGlyphsTexture(glyphs: i.value(), tex: t); |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | void QSGOpenGLDistanceFieldGlyphCache::referenceGlyphs(const QSet<glyph_t> &glyphs) |
| 258 | { |
| 259 | m_unusedGlyphs -= glyphs; |
| 260 | } |
| 261 | |
| 262 | void QSGOpenGLDistanceFieldGlyphCache::releaseGlyphs(const QSet<glyph_t> &glyphs) |
| 263 | { |
| 264 | m_unusedGlyphs += glyphs; |
| 265 | } |
| 266 | |
| 267 | void QSGOpenGLDistanceFieldGlyphCache::createTexture(TextureInfo *texInfo, |
| 268 | int width, |
| 269 | int height) |
| 270 | { |
| 271 | QByteArray zeroBuf(width * height, 0); |
| 272 | createTexture(texInfo, width, height, pixels: zeroBuf.constData()); |
| 273 | } |
| 274 | |
| 275 | void QSGOpenGLDistanceFieldGlyphCache::createTexture(TextureInfo *texInfo, |
| 276 | int width, |
| 277 | int height, |
| 278 | const void *pixels) |
| 279 | { |
| 280 | if (useTextureResizeWorkaround() && texInfo->image.isNull()) { |
| 281 | texInfo->image = QDistanceField(width, height); |
| 282 | memcpy(dest: texInfo->image.bits(), src: pixels, n: width * height); |
| 283 | } |
| 284 | |
| 285 | while (m_funcs->glGetError() != GL_NO_ERROR) { } |
| 286 | |
| 287 | m_funcs->glGenTextures(n: 1, textures: &texInfo->texture); |
| 288 | m_funcs->glBindTexture(GL_TEXTURE_2D, texture: texInfo->texture); |
| 289 | |
| 290 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 291 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 292 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 293 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 294 | #if !defined(QT_OPENGL_ES_2) |
| 295 | if (!QOpenGLContext::currentContext()->isOpenGLES()) |
| 296 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, param: 0); |
| 297 | const GLint internalFormat = isCoreProfile() ? GL_R8 : GL_ALPHA; |
| 298 | const GLenum format = isCoreProfile() ? GL_RED : GL_ALPHA; |
| 299 | #else |
| 300 | const GLint internalFormat = GL_ALPHA; |
| 301 | const GLenum format = GL_ALPHA; |
| 302 | #endif |
| 303 | |
| 304 | m_funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, internalformat: internalFormat, width, height, border: 0, format, GL_UNSIGNED_BYTE, pixels); |
| 305 | |
| 306 | texInfo->size = QSize(width, height); |
| 307 | |
| 308 | GLuint error = m_funcs->glGetError(); |
| 309 | if (error != GL_NO_ERROR) { |
| 310 | m_funcs->glBindTexture(GL_TEXTURE_2D, texture: 0); |
| 311 | m_funcs->glDeleteTextures(n: 1, textures: &texInfo->texture); |
| 312 | texInfo->texture = 0; |
| 313 | } |
| 314 | |
| 315 | } |
| 316 | |
| 317 | static void freeFramebufferFunc(QOpenGLFunctions *funcs, GLuint id) |
| 318 | { |
| 319 | funcs->glDeleteFramebuffers(n: 1, framebuffers: &id); |
| 320 | } |
| 321 | |
| 322 | void QSGOpenGLDistanceFieldGlyphCache::resizeTexture(TextureInfo *texInfo, int width, int height) |
| 323 | { |
| 324 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
| 325 | Q_ASSERT(ctx); |
| 326 | |
| 327 | int oldWidth = texInfo->size.width(); |
| 328 | int oldHeight = texInfo->size.height(); |
| 329 | if (width == oldWidth && height == oldHeight) |
| 330 | return; |
| 331 | |
| 332 | GLuint oldTexture = texInfo->texture; |
| 333 | createTexture(texInfo, width, height); |
| 334 | |
| 335 | if (!oldTexture) |
| 336 | return; |
| 337 | |
| 338 | updateTexture(oldTex: oldTexture, newTex: texInfo->texture, newTexSize: texInfo->size); |
| 339 | |
| 340 | #if !defined(QT_OPENGL_ES_2) |
| 341 | if (isCoreProfile() && !useTextureResizeWorkaround()) { |
| 342 | // For an OpenGL Core Profile we can use http://www.opengl.org/wiki/Framebuffer#Blitting |
| 343 | // to efficiently copy the contents of the old texture to the new texture |
| 344 | // TODO: Use ARB_copy_image if available of if we have >=4.3 context |
| 345 | if (!m_coreFuncs) { |
| 346 | m_coreFuncs = ctx->versionFunctions<QOpenGLFunctions_3_2_Core>(); |
| 347 | Q_ASSERT(m_coreFuncs); |
| 348 | m_coreFuncs->initializeOpenGLFunctions(); |
| 349 | } |
| 350 | |
| 351 | // Create a framebuffer object to which we can attach our old and new textures (to |
| 352 | // the first two color buffer attachment points) |
| 353 | if (!m_fboGuard) { |
| 354 | GLuint fbo; |
| 355 | m_coreFuncs->glGenFramebuffers(n: 1, framebuffers: &fbo); |
| 356 | m_fboGuard = new QOpenGLSharedResourceGuard(ctx, fbo, freeFramebufferFunc); |
| 357 | } |
| 358 | |
| 359 | // Bind the FBO to both the GL_READ_FRAMEBUFFER? and GL_DRAW_FRAMEBUFFER targets |
| 360 | m_coreFuncs->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: m_fboGuard->id()); |
| 361 | |
| 362 | // Bind the old texture to GL_COLOR_ATTACHMENT0 |
| 363 | m_coreFuncs->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| 364 | GL_TEXTURE_2D, texture: oldTexture, level: 0); |
| 365 | |
| 366 | // Bind the new texture to GL_COLOR_ATTACHMENT1 |
| 367 | m_coreFuncs->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, |
| 368 | GL_TEXTURE_2D, texture: texInfo->texture, level: 0); |
| 369 | |
| 370 | // Set the source and destination buffers |
| 371 | m_coreFuncs->glReadBuffer(GL_COLOR_ATTACHMENT0); |
| 372 | m_coreFuncs->glDrawBuffer(GL_COLOR_ATTACHMENT1); |
| 373 | |
| 374 | // Do the blit |
| 375 | m_coreFuncs->glBlitFramebuffer(srcX0: 0, srcY0: 0, srcX1: oldWidth, srcY1: oldHeight, |
| 376 | dstX0: 0, dstY0: 0, dstX1: oldWidth, dstY1: oldHeight, |
| 377 | GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| 378 | |
| 379 | // Reset the default framebuffer |
| 380 | QOpenGLFramebufferObject::bindDefault(); |
| 381 | |
| 382 | return; |
| 383 | } else if (useTextureResizeWorkaround()) { |
| 384 | #else |
| 385 | if (useTextureResizeWorkaround()) { |
| 386 | #endif |
| 387 | GLint alignment = 4; // default value |
| 388 | m_funcs->glGetIntegerv(GL_UNPACK_ALIGNMENT, params: &alignment); |
| 389 | m_funcs->glPixelStorei(GL_UNPACK_ALIGNMENT, param: 1); |
| 390 | |
| 391 | #if !defined(QT_OPENGL_ES_2) |
| 392 | const GLenum format = isCoreProfile() ? GL_RED : GL_ALPHA; |
| 393 | #else |
| 394 | const GLenum format = GL_ALPHA; |
| 395 | #endif |
| 396 | |
| 397 | if (useTextureUploadWorkaround()) { |
| 398 | for (int i = 0; i < texInfo->image.height(); ++i) { |
| 399 | m_funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, |
| 400 | xoffset: 0, yoffset: i, width: oldWidth, height: 1, |
| 401 | format, GL_UNSIGNED_BYTE, |
| 402 | pixels: texInfo->image.scanLine(i)); |
| 403 | } |
| 404 | } else { |
| 405 | m_funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, |
| 406 | xoffset: 0, yoffset: 0, width: oldWidth, height: oldHeight, |
| 407 | format, GL_UNSIGNED_BYTE, |
| 408 | pixels: texInfo->image.constBits()); |
| 409 | } |
| 410 | |
| 411 | m_funcs->glPixelStorei(GL_UNPACK_ALIGNMENT, param: alignment); // restore to previous value |
| 412 | |
| 413 | texInfo->image = texInfo->image.copy(x: 0, y: 0, w: width, h: height); |
| 414 | m_funcs->glDeleteTextures(n: 1, textures: &oldTexture); |
| 415 | return; |
| 416 | } |
| 417 | |
| 418 | if (!m_blitProgram) |
| 419 | createBlitProgram(); |
| 420 | |
| 421 | Q_ASSERT(m_blitProgram); |
| 422 | |
| 423 | if (!m_fboGuard) { |
| 424 | GLuint fbo; |
| 425 | m_funcs->glGenFramebuffers(n: 1, framebuffers: &fbo); |
| 426 | m_fboGuard = new QOpenGLSharedResourceGuard(ctx, fbo, freeFramebufferFunc); |
| 427 | } |
| 428 | m_funcs->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: m_fboGuard->id()); |
| 429 | |
| 430 | GLuint tmp_texture; |
| 431 | m_funcs->glGenTextures(n: 1, textures: &tmp_texture); |
| 432 | m_funcs->glBindTexture(GL_TEXTURE_2D, texture: tmp_texture); |
| 433 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 434 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 435 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 436 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 437 | #if !defined(QT_OPENGL_ES_2) |
| 438 | if (!ctx->isOpenGLES()) |
| 439 | m_funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, param: 0); |
| 440 | #endif |
| 441 | m_funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: oldWidth, height: oldHeight, border: 0, |
| 442 | GL_RGBA, GL_UNSIGNED_BYTE, pixels: nullptr); |
| 443 | m_funcs->glBindTexture(GL_TEXTURE_2D, texture: 0); |
| 444 | m_funcs->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| 445 | GL_TEXTURE_2D, texture: tmp_texture, level: 0); |
| 446 | |
| 447 | m_funcs->glActiveTexture(GL_TEXTURE0); |
| 448 | m_funcs->glBindTexture(GL_TEXTURE_2D, texture: oldTexture); |
| 449 | |
| 450 | // save current render states |
| 451 | GLboolean stencilTestEnabled; |
| 452 | GLboolean depthTestEnabled; |
| 453 | GLboolean scissorTestEnabled; |
| 454 | GLboolean blendEnabled; |
| 455 | GLint viewport[4]; |
| 456 | GLint oldProgram; |
| 457 | m_funcs->glGetBooleanv(GL_STENCIL_TEST, params: &stencilTestEnabled); |
| 458 | m_funcs->glGetBooleanv(GL_DEPTH_TEST, params: &depthTestEnabled); |
| 459 | m_funcs->glGetBooleanv(GL_SCISSOR_TEST, params: &scissorTestEnabled); |
| 460 | m_funcs->glGetBooleanv(GL_BLEND, params: &blendEnabled); |
| 461 | m_funcs->glGetIntegerv(GL_VIEWPORT, params: &viewport[0]); |
| 462 | m_funcs->glGetIntegerv(GL_CURRENT_PROGRAM, params: &oldProgram); |
| 463 | |
| 464 | m_funcs->glDisable(GL_STENCIL_TEST); |
| 465 | m_funcs->glDisable(GL_DEPTH_TEST); |
| 466 | m_funcs->glDisable(GL_SCISSOR_TEST); |
| 467 | m_funcs->glDisable(GL_BLEND); |
| 468 | |
| 469 | m_funcs->glViewport(x: 0, y: 0, width: oldWidth, height: oldHeight); |
| 470 | |
| 471 | const bool vaoInit = m_vao.isCreated(); |
| 472 | if (isCoreProfile()) { |
| 473 | if ( !vaoInit ) |
| 474 | m_vao.create(); |
| 475 | m_vao.bind(); |
| 476 | } |
| 477 | m_blitProgram->bind(); |
| 478 | if (!vaoInit || !isCoreProfile()) { |
| 479 | m_blitBuffer.bind(); |
| 480 | |
| 481 | m_blitProgram->enableAttributeArray(location: int(QT_VERTEX_COORDS_ATTR)); |
| 482 | m_blitProgram->enableAttributeArray(location: int(QT_TEXTURE_COORDS_ATTR)); |
| 483 | m_blitProgram->setAttributeBuffer(location: int(QT_VERTEX_COORDS_ATTR), GL_FLOAT, offset: 0, tupleSize: 2); |
| 484 | m_blitProgram->setAttributeBuffer(location: int(QT_TEXTURE_COORDS_ATTR), GL_FLOAT, offset: 32, tupleSize: 2); |
| 485 | } |
| 486 | m_blitProgram->disableAttributeArray(location: int(QT_OPACITY_ATTR)); |
| 487 | m_blitProgram->setUniformValue(name: "imageTexture" , value: GLuint(0)); |
| 488 | |
| 489 | m_funcs->glDrawArrays(GL_TRIANGLE_FAN, first: 0, count: 4); |
| 490 | |
| 491 | m_funcs->glBindTexture(GL_TEXTURE_2D, texture: texInfo->texture); |
| 492 | |
| 493 | if (useTextureUploadWorkaround()) { |
| 494 | for (int i = 0; i < oldHeight; ++i) |
| 495 | m_funcs->glCopyTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: 0, yoffset: i, x: 0, y: i, width: oldWidth, height: 1); |
| 496 | } else { |
| 497 | m_funcs->glCopyTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: 0, yoffset: 0, x: 0, y: 0, width: oldWidth, height: oldHeight); |
| 498 | } |
| 499 | |
| 500 | m_funcs->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| 501 | GL_RENDERBUFFER, renderbuffer: 0); |
| 502 | m_funcs->glDeleteTextures(n: 1, textures: &tmp_texture); |
| 503 | m_funcs->glDeleteTextures(n: 1, textures: &oldTexture); |
| 504 | |
| 505 | QOpenGLFramebufferObject::bindDefault(); |
| 506 | |
| 507 | // restore render states |
| 508 | if (stencilTestEnabled) |
| 509 | m_funcs->glEnable(GL_STENCIL_TEST); |
| 510 | if (depthTestEnabled) |
| 511 | m_funcs->glEnable(GL_DEPTH_TEST); |
| 512 | if (scissorTestEnabled) |
| 513 | m_funcs->glEnable(GL_SCISSOR_TEST); |
| 514 | if (blendEnabled) |
| 515 | m_funcs->glEnable(GL_BLEND); |
| 516 | m_funcs->glViewport(x: viewport[0], y: viewport[1], width: viewport[2], height: viewport[3]); |
| 517 | m_funcs->glUseProgram(program: oldProgram); |
| 518 | |
| 519 | m_blitProgram->disableAttributeArray(location: int(QT_VERTEX_COORDS_ATTR)); |
| 520 | m_blitProgram->disableAttributeArray(location: int(QT_TEXTURE_COORDS_ATTR)); |
| 521 | if (isCoreProfile()) |
| 522 | m_vao.release(); |
| 523 | } |
| 524 | |
| 525 | bool QSGOpenGLDistanceFieldGlyphCache::useTextureResizeWorkaround() const |
| 526 | { |
| 527 | static bool set = false; |
| 528 | static bool useWorkaround = false; |
| 529 | if (!set) { |
| 530 | QOpenGLContextPrivate *ctx_p = static_cast<QOpenGLContextPrivate *>(QOpenGLContextPrivate::get(context: QOpenGLContext::currentContext())); |
| 531 | useWorkaround = ctx_p->workaround_brokenFBOReadBack |
| 532 | || qmlUseGlyphCacheWorkaround(); // on some hardware the workaround is faster (see QTBUG-29264) |
| 533 | set = true; |
| 534 | } |
| 535 | return useWorkaround; |
| 536 | } |
| 537 | |
| 538 | bool QSGOpenGLDistanceFieldGlyphCache::useTextureUploadWorkaround() const |
| 539 | { |
| 540 | static bool set = false; |
| 541 | static bool useWorkaround = false; |
| 542 | if (!set) { |
| 543 | useWorkaround = qstrcmp(str1: reinterpret_cast<const char*>(m_funcs->glGetString(GL_RENDERER)), |
| 544 | str2: "Mali-400 MP" ) == 0; |
| 545 | set = true; |
| 546 | } |
| 547 | return useWorkaround; |
| 548 | } |
| 549 | |
| 550 | bool QSGOpenGLDistanceFieldGlyphCache::createFullSizeTextures() const |
| 551 | { |
| 552 | return qsgPreferFullSizeGlyphCacheTextures() && glyphCount() > QT_DISTANCEFIELD_HIGHGLYPHCOUNT(); |
| 553 | } |
| 554 | |
| 555 | namespace { |
| 556 | struct Qtdf { |
| 557 | // We need these structs to be tightly packed, but some compilers we use do not |
| 558 | // support #pragma pack(1), so we need to hardcode the offsets/sizes in the |
| 559 | // file format |
| 560 | enum TableSize { |
| 561 | = 14, |
| 562 | GlyphRecordSize = 46, |
| 563 | TextureRecordSize = 17 |
| 564 | }; |
| 565 | |
| 566 | enum Offset { |
| 567 | // Header |
| 568 | majorVersion = 0, |
| 569 | minorVersion = 1, |
| 570 | pixelSize = 2, |
| 571 | textureSize = 4, |
| 572 | flags = 8, |
| 573 | = 9, |
| 574 | numGlyphs = 10, |
| 575 | |
| 576 | // Glyph record |
| 577 | glyphIndex = 0, |
| 578 | textureOffsetX = 4, |
| 579 | textureOffsetY = 8, |
| 580 | textureWidth = 12, |
| 581 | textureHeight = 16, |
| 582 | xMargin = 20, |
| 583 | yMargin = 24, |
| 584 | boundingRectX = 28, |
| 585 | boundingRectY = 32, |
| 586 | boundingRectWidth = 36, |
| 587 | boundingRectHeight = 40, |
| 588 | textureIndex = 44, |
| 589 | |
| 590 | // Texture record |
| 591 | allocatedX = 0, |
| 592 | allocatedY = 4, |
| 593 | allocatedWidth = 8, |
| 594 | allocatedHeight = 12, |
| 595 | texturePadding = 16 |
| 596 | |
| 597 | }; |
| 598 | |
| 599 | template <typename T> |
| 600 | static inline T fetch(const char *data, Offset offset) |
| 601 | { |
| 602 | return qFromBigEndian<T>(data + int(offset)); |
| 603 | } |
| 604 | }; |
| 605 | } |
| 606 | |
| 607 | bool QSGOpenGLDistanceFieldGlyphCache::loadPregeneratedCache(const QRawFont &font) |
| 608 | { |
| 609 | // The pregenerated data must be loaded first, otherwise the area allocator |
| 610 | // will be wrong |
| 611 | if (m_areaAllocator != nullptr) { |
| 612 | qWarning(msg: "Font cache must be loaded before cache is used" ); |
| 613 | return false; |
| 614 | } |
| 615 | |
| 616 | static QElapsedTimer timer; |
| 617 | |
| 618 | bool profile = QSG_LOG_TIME_GLYPH().isDebugEnabled(); |
| 619 | if (profile) |
| 620 | timer.start(); |
| 621 | |
| 622 | QByteArray qtdfTable = font.fontTable(tagName: "qtdf" ); |
| 623 | if (qtdfTable.isEmpty()) |
| 624 | return false; |
| 625 | |
| 626 | typedef QHash<TextureInfo *, QVector<glyph_t> > GlyphTextureHash; |
| 627 | |
| 628 | GlyphTextureHash glyphTextures; |
| 629 | |
| 630 | if (uint(qtdfTable.size()) < Qtdf::HeaderSize) { |
| 631 | qWarning(msg: "Invalid qtdf table in font '%s'" , |
| 632 | qPrintable(font.familyName())); |
| 633 | return false; |
| 634 | } |
| 635 | |
| 636 | const char *qtdfTableStart = qtdfTable.constData(); |
| 637 | const char *qtdfTableEnd = qtdfTableStart + qtdfTable.size(); |
| 638 | |
| 639 | int padding = 0; |
| 640 | int textureCount = 0; |
| 641 | { |
| 642 | quint8 majorVersion = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::majorVersion); |
| 643 | quint8 minorVersion = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::minorVersion); |
| 644 | if (majorVersion != 5 || minorVersion != 12) { |
| 645 | qWarning(msg: "Invalid version of qtdf table %d.%d in font '%s'" , |
| 646 | majorVersion, |
| 647 | minorVersion, |
| 648 | qPrintable(font.familyName())); |
| 649 | return false; |
| 650 | } |
| 651 | |
| 652 | qreal pixelSize = qreal(Qtdf::fetch<quint16>(data: qtdfTableStart, offset: Qtdf::pixelSize)); |
| 653 | m_maxTextureWidth = m_maxTextureHeight = Qtdf::fetch<quint32>(data: qtdfTableStart, offset: Qtdf::textureSize); |
| 654 | m_doubleGlyphResolution = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::flags) == 1; |
| 655 | padding = Qtdf::fetch<quint8>(data: qtdfTableStart, offset: Qtdf::headerPadding); |
| 656 | |
| 657 | if (pixelSize <= 0.0) { |
| 658 | qWarning(msg: "Invalid pixel size in '%s'" , qPrintable(font.familyName())); |
| 659 | return false; |
| 660 | } |
| 661 | |
| 662 | if (m_maxTextureWidth <= 0) { |
| 663 | qWarning(msg: "Invalid texture size in '%s'" , qPrintable(font.familyName())); |
| 664 | return false; |
| 665 | } |
| 666 | |
| 667 | int systemMaxTextureSize; |
| 668 | m_funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &systemMaxTextureSize); |
| 669 | |
| 670 | if (m_maxTextureWidth > systemMaxTextureSize) { |
| 671 | qWarning(msg: "System maximum texture size is %d. This is lower than the value in '%s', which is %d" , |
| 672 | systemMaxTextureSize, |
| 673 | qPrintable(font.familyName()), |
| 674 | m_maxTextureWidth); |
| 675 | } |
| 676 | |
| 677 | if (padding != QSG_OPENGL_DISTANCEFIELD_GLYPH_CACHE_PADDING) { |
| 678 | qWarning(msg: "Padding mismatch in '%s'. Font requires %d, but Qt is compiled with %d." , |
| 679 | qPrintable(font.familyName()), |
| 680 | padding, |
| 681 | QSG_OPENGL_DISTANCEFIELD_GLYPH_CACHE_PADDING); |
| 682 | } |
| 683 | |
| 684 | m_referenceFont.setPixelSize(pixelSize); |
| 685 | |
| 686 | quint32 glyphCount = Qtdf::fetch<quint32>(data: qtdfTableStart, offset: Qtdf::numGlyphs); |
| 687 | m_unusedGlyphs.reserve(asize: glyphCount); |
| 688 | |
| 689 | const char *allocatorData = qtdfTableStart + Qtdf::HeaderSize; |
| 690 | { |
| 691 | m_areaAllocator = new QSGAreaAllocator(QSize(0, 0)); |
| 692 | allocatorData = m_areaAllocator->deserialize(data: allocatorData, size: qtdfTableEnd - allocatorData); |
| 693 | if (allocatorData == nullptr) |
| 694 | return false; |
| 695 | } |
| 696 | |
| 697 | if (m_areaAllocator->size().height() % m_maxTextureHeight != 0) { |
| 698 | qWarning(msg: "Area allocator size mismatch in '%s'" , qPrintable(font.familyName())); |
| 699 | return false; |
| 700 | } |
| 701 | |
| 702 | textureCount = m_areaAllocator->size().height() / m_maxTextureHeight; |
| 703 | m_maxTextureCount = qMax(a: m_maxTextureCount, b: textureCount); |
| 704 | |
| 705 | const char *textureRecord = allocatorData; |
| 706 | for (int i = 0; i < textureCount; ++i, textureRecord += Qtdf::TextureRecordSize) { |
| 707 | if (textureRecord + Qtdf::TextureRecordSize > qtdfTableEnd) { |
| 708 | qWarning(msg: "qtdf table too small in font '%s'." , |
| 709 | qPrintable(font.familyName())); |
| 710 | return false; |
| 711 | } |
| 712 | |
| 713 | TextureInfo *tex = textureInfo(index: i); |
| 714 | tex->allocatedArea.setX(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedX)); |
| 715 | tex->allocatedArea.setY(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedY)); |
| 716 | tex->allocatedArea.setWidth(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedWidth)); |
| 717 | tex->allocatedArea.setHeight(Qtdf::fetch<quint32>(data: textureRecord, offset: Qtdf::allocatedHeight)); |
| 718 | tex->padding = Qtdf::fetch<quint8>(data: textureRecord, offset: Qtdf::texturePadding); |
| 719 | } |
| 720 | |
| 721 | const char *glyphRecord = textureRecord; |
| 722 | for (quint32 i = 0; i < glyphCount; ++i, glyphRecord += Qtdf::GlyphRecordSize) { |
| 723 | if (glyphRecord + Qtdf::GlyphRecordSize > qtdfTableEnd) { |
| 724 | qWarning(msg: "qtdf table too small in font '%s'." , |
| 725 | qPrintable(font.familyName())); |
| 726 | return false; |
| 727 | } |
| 728 | |
| 729 | glyph_t glyph = Qtdf::fetch<quint32>(data: glyphRecord, offset: Qtdf::glyphIndex); |
| 730 | m_unusedGlyphs.insert(value: glyph); |
| 731 | |
| 732 | GlyphData &glyphData = emptyData(glyph); |
| 733 | |
| 734 | #define FROM_FIXED_POINT(value) \ |
| 735 | (((qreal)value)/(qreal)65536) |
| 736 | |
| 737 | glyphData.texCoord.x = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetX)); |
| 738 | glyphData.texCoord.y = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureOffsetY)); |
| 739 | glyphData.texCoord.width = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureWidth)); |
| 740 | glyphData.texCoord.height = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::textureHeight)); |
| 741 | glyphData.texCoord.xMargin = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::xMargin)); |
| 742 | glyphData.texCoord.yMargin = FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::yMargin)); |
| 743 | glyphData.boundingRect.setX(FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectX))); |
| 744 | glyphData.boundingRect.setY(FROM_FIXED_POINT(Qtdf::fetch<qint32>(glyphRecord, Qtdf::boundingRectY))); |
| 745 | glyphData.boundingRect.setWidth(FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::boundingRectWidth))); |
| 746 | glyphData.boundingRect.setHeight(FROM_FIXED_POINT(Qtdf::fetch<quint32>(glyphRecord, Qtdf::boundingRectHeight))); |
| 747 | |
| 748 | #undef FROM_FIXED_POINT |
| 749 | |
| 750 | int textureIndex = Qtdf::fetch<quint16>(data: glyphRecord, offset: Qtdf::textureIndex); |
| 751 | if (textureIndex < 0 || textureIndex >= textureCount) { |
| 752 | qWarning(msg: "Invalid texture index %d (texture count == %d) in '%s'" , |
| 753 | textureIndex, |
| 754 | textureCount, |
| 755 | qPrintable(font.familyName())); |
| 756 | return false; |
| 757 | } |
| 758 | |
| 759 | |
| 760 | TextureInfo *texInfo = textureInfo(index: textureIndex); |
| 761 | m_glyphsTexture.insert(akey: glyph, avalue: texInfo); |
| 762 | |
| 763 | glyphTextures[texInfo].append(t: glyph); |
| 764 | } |
| 765 | |
| 766 | GLint alignment = 4; // default value |
| 767 | m_funcs->glGetIntegerv(GL_UNPACK_ALIGNMENT, params: &alignment); |
| 768 | |
| 769 | m_funcs->glPixelStorei(GL_UNPACK_ALIGNMENT, param: 1); |
| 770 | |
| 771 | const uchar *textureData = reinterpret_cast<const uchar *>(glyphRecord); |
| 772 | for (int i = 0; i < textureCount; ++i) { |
| 773 | |
| 774 | TextureInfo *texInfo = textureInfo(index: i); |
| 775 | |
| 776 | int width = texInfo->allocatedArea.width(); |
| 777 | int height = texInfo->allocatedArea.height(); |
| 778 | qint64 size = width * height; |
| 779 | if (reinterpret_cast<const char *>(textureData + size) > qtdfTableEnd) { |
| 780 | qWarning(msg: "qtdf table too small in font '%s'." , |
| 781 | qPrintable(font.familyName())); |
| 782 | return false; |
| 783 | } |
| 784 | |
| 785 | createTexture(texInfo, width, height, pixels: textureData); |
| 786 | |
| 787 | QVector<glyph_t> glyphs = glyphTextures.value(akey: texInfo); |
| 788 | |
| 789 | Texture t; |
| 790 | t.textureId = texInfo->texture; |
| 791 | t.size = texInfo->size; |
| 792 | t.rhiBased = false; |
| 793 | |
| 794 | setGlyphsTexture(glyphs, tex: t); |
| 795 | |
| 796 | textureData += size; |
| 797 | } |
| 798 | |
| 799 | m_funcs->glPixelStorei(GL_UNPACK_ALIGNMENT, param: alignment); |
| 800 | } |
| 801 | |
| 802 | if (profile) { |
| 803 | quint64 now = timer.elapsed(); |
| 804 | qCDebug(QSG_LOG_TIME_GLYPH, |
| 805 | "distancefield: %d pre-generated glyphs loaded in %dms" , |
| 806 | m_unusedGlyphs.size(), |
| 807 | (int) now); |
| 808 | } |
| 809 | |
| 810 | return true; |
| 811 | } |
| 812 | |
| 813 | QT_END_NAMESPACE |
| 814 | |