| 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 QtOpenGL 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 "qtextureglyphcache_gl_p.h" | 
| 41 | #include "qpaintengineex_opengl2_p.h" | 
| 42 | #include "qglfunctions.h" | 
| 43 | #include "private/qglengineshadersource_p.h" | 
| 44 |  | 
| 45 | QT_BEGIN_NAMESPACE | 
| 46 |  | 
| 47 |  | 
| 48 | static int next_qgltextureglyphcache_serial_number() | 
| 49 | { | 
| 50 |     static QBasicAtomicInt serial = Q_BASIC_ATOMIC_INITIALIZER(0); | 
| 51 |     return 1 + serial.fetchAndAddRelaxed(valueToAdd: 1); | 
| 52 | } | 
| 53 |  | 
| 54 | QGLTextureGlyphCache::QGLTextureGlyphCache(QFontEngine::GlyphFormat format, const QTransform &matrix) | 
| 55 |     : QImageTextureGlyphCache(format, matrix) | 
| 56 |     , m_textureResource(0) | 
| 57 |     , pex(0) | 
| 58 |     , m_blitProgram(0) | 
| 59 |     , m_filterMode(Nearest) | 
| 60 |     , m_serialNumber(next_qgltextureglyphcache_serial_number()) | 
| 61 | { | 
| 62 | #ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG | 
| 63 |     qDebug(" -> QGLTextureGlyphCache() %p for context %p." , this, QOpenGLContext::currentContext()); | 
| 64 | #endif | 
| 65 |     m_vertexCoordinateArray[0] = -1.0f; | 
| 66 |     m_vertexCoordinateArray[1] = -1.0f; | 
| 67 |     m_vertexCoordinateArray[2] =  1.0f; | 
| 68 |     m_vertexCoordinateArray[3] = -1.0f; | 
| 69 |     m_vertexCoordinateArray[4] =  1.0f; | 
| 70 |     m_vertexCoordinateArray[5] =  1.0f; | 
| 71 |     m_vertexCoordinateArray[6] = -1.0f; | 
| 72 |     m_vertexCoordinateArray[7] =  1.0f; | 
| 73 |  | 
| 74 |     m_textureCoordinateArray[0] = 0.0f; | 
| 75 |     m_textureCoordinateArray[1] = 0.0f; | 
| 76 |     m_textureCoordinateArray[2] = 1.0f; | 
| 77 |     m_textureCoordinateArray[3] = 0.0f; | 
| 78 |     m_textureCoordinateArray[4] = 1.0f; | 
| 79 |     m_textureCoordinateArray[5] = 1.0f; | 
| 80 |     m_textureCoordinateArray[6] = 0.0f; | 
| 81 |     m_textureCoordinateArray[7] = 1.0f; | 
| 82 | } | 
| 83 |  | 
| 84 | QGLTextureGlyphCache::~QGLTextureGlyphCache() | 
| 85 | { | 
| 86 | #ifdef QT_GL_TEXTURE_GLYPH_CACHE_DEBUG | 
| 87 |     qDebug(" -> ~QGLTextureGlyphCache() %p." , this); | 
| 88 | #endif | 
| 89 |     delete m_blitProgram; | 
| 90 |     if (m_textureResource) | 
| 91 |         m_textureResource->free(); | 
| 92 | } | 
| 93 |  | 
| 94 | void QGLTextureGlyphCache::createTextureData(int width, int height) | 
| 95 | { | 
| 96 |     QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); | 
| 97 |     if (ctx == 0) { | 
| 98 |         qWarning(msg: "QGLTextureGlyphCache::createTextureData: Called with no context" ); | 
| 99 |         return; | 
| 100 |     } | 
| 101 |     QOpenGLFunctions *funcs = ctx->contextHandle()->functions(); | 
| 102 |  | 
| 103 |     // create in QImageTextureGlyphCache baseclass is meant to be called | 
| 104 |     // only to create the initial image and does not preserve the content, | 
| 105 |     // so we don't call when this function is called from resize. | 
| 106 |     if ((!QGLFramebufferObject::hasOpenGLFramebufferObjects() || ctx->d_ptr->workaround_brokenFBOReadBack) && image().isNull()) | 
| 107 |         QImageTextureGlyphCache::createTextureData(width, height); | 
| 108 |  | 
| 109 |     // Make the lower glyph texture size 16 x 16. | 
| 110 |     if (width < 16) | 
| 111 |         width = 16; | 
| 112 |     if (height < 16) | 
| 113 |         height = 16; | 
| 114 |  | 
| 115 |     if (m_textureResource && !m_textureResource->m_texture) { | 
| 116 |         delete m_textureResource; | 
| 117 |         m_textureResource = 0; | 
| 118 |     } | 
| 119 |  | 
| 120 |     if (!m_textureResource) | 
| 121 |         m_textureResource = new QGLGlyphTexture(ctx); | 
| 122 |  | 
| 123 |     funcs->glGenTextures(n: 1, textures: &m_textureResource->m_texture); | 
| 124 |     funcs->glBindTexture(GL_TEXTURE_2D, texture: m_textureResource->m_texture); | 
| 125 |  | 
| 126 |     m_textureResource->m_width = width; | 
| 127 |     m_textureResource->m_height = height; | 
| 128 |  | 
| 129 |     if (m_format == QFontEngine::Format_A32) { | 
| 130 |         QVarLengthArray<uchar> data(width * height * 4); | 
| 131 |         for (int i = 0; i < data.size(); ++i) | 
| 132 |             data[i] = 0; | 
| 133 |         funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width, height, border: 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels: &data[0]); | 
| 134 |     } else { | 
| 135 |         QVarLengthArray<uchar> data(width * height); | 
| 136 |         for (int i = 0; i < data.size(); ++i) | 
| 137 |             data[i] = 0; | 
| 138 |         funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_ALPHA, width, height, border: 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels: &data[0]); | 
| 139 |     } | 
| 140 |  | 
| 141 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | 
| 142 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 
| 143 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | 
| 144 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | 
| 145 |     m_filterMode = Nearest; | 
| 146 | } | 
| 147 |  | 
| 148 | void QGLTextureGlyphCache::resizeTextureData(int width, int height) | 
| 149 | { | 
| 150 |     QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); | 
| 151 |     if (ctx == 0) { | 
| 152 |         qWarning(msg: "QGLTextureGlyphCache::resizeTextureData: Called with no context" ); | 
| 153 |         return; | 
| 154 |     } | 
| 155 |     QOpenGLFunctions *funcs = ctx->contextHandle()->functions(); | 
| 156 |  | 
| 157 |     int oldWidth = m_textureResource->m_width; | 
| 158 |     int oldHeight = m_textureResource->m_height; | 
| 159 |  | 
| 160 |     // Make the lower glyph texture size 16 x 16. | 
| 161 |     if (width < 16) | 
| 162 |         width = 16; | 
| 163 |     if (height < 16) | 
| 164 |         height = 16; | 
| 165 |  | 
| 166 |     GLuint oldTexture = m_textureResource->m_texture; | 
| 167 |     createTextureData(width, height); | 
| 168 |  | 
| 169 |     if (!QGLFramebufferObject::hasOpenGLFramebufferObjects() || ctx->d_ptr->workaround_brokenFBOReadBack) { | 
| 170 |         QImageTextureGlyphCache::resizeTextureData(width, height); | 
| 171 |         Q_ASSERT(image().depth() == 8); | 
| 172 |         funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: 0, yoffset: 0, width, height: oldHeight, GL_ALPHA, GL_UNSIGNED_BYTE, pixels: image().constBits()); | 
| 173 |         funcs->glDeleteTextures(n: 1, textures: &oldTexture); | 
| 174 |         return; | 
| 175 |     } | 
| 176 |  | 
| 177 |     // ### the QTextureGlyphCache API needs to be reworked to allow | 
| 178 |     // ### resizeTextureData to fail | 
| 179 |  | 
| 180 |     ctx->d_ptr->refreshCurrentFbo(); | 
| 181 |  | 
| 182 |     funcs->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: m_textureResource->m_fbo); | 
| 183 |  | 
| 184 |     GLuint tmp_texture; | 
| 185 |     funcs->glGenTextures(n: 1, textures: &tmp_texture); | 
| 186 |     funcs->glBindTexture(GL_TEXTURE_2D, texture: tmp_texture); | 
| 187 |     funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: oldWidth, height: oldHeight, border: 0, | 
| 188 |                         GL_RGBA, GL_UNSIGNED_BYTE, NULL); | 
| 189 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 
| 190 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | 
| 191 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | 
| 192 |     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | 
| 193 |     m_filterMode = Nearest; | 
| 194 |     funcs->glBindTexture(GL_TEXTURE_2D, texture: 0); | 
| 195 |     funcs->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | 
| 196 |                                   GL_TEXTURE_2D, texture: tmp_texture, level: 0); | 
| 197 |  | 
| 198 |     funcs->glActiveTexture(GL_TEXTURE0 + QT_IMAGE_TEXTURE_UNIT); | 
| 199 |     funcs->glBindTexture(GL_TEXTURE_2D, texture: oldTexture); | 
| 200 |  | 
| 201 |     if (pex != 0) | 
| 202 |         pex->transferMode(newMode: BrushDrawingMode); | 
| 203 |  | 
| 204 |     funcs->glDisable(GL_STENCIL_TEST); | 
| 205 |     funcs->glDisable(GL_DEPTH_TEST); | 
| 206 |     funcs->glDisable(GL_SCISSOR_TEST); | 
| 207 |     funcs->glDisable(GL_BLEND); | 
| 208 |  | 
| 209 |     funcs->glViewport(x: 0, y: 0, width: oldWidth, height: oldHeight); | 
| 210 |  | 
| 211 |     QGLShaderProgram *blitProgram = 0; | 
| 212 |     if (pex == 0) { | 
| 213 |         if (m_blitProgram == 0) { | 
| 214 |             m_blitProgram = new QGLShaderProgram(ctx); | 
| 215 |  | 
| 216 |             { | 
| 217 |                 QString source; | 
| 218 |                 source.append(s: QLatin1String(qglslMainWithTexCoordsVertexShader)); | 
| 219 |                 source.append(s: QLatin1String(qglslUntransformedPositionVertexShader)); | 
| 220 |  | 
| 221 |                 QGLShader *vertexShader = new QGLShader(QGLShader::Vertex, m_blitProgram); | 
| 222 |                 vertexShader->compileSourceCode(source); | 
| 223 |  | 
| 224 |                 m_blitProgram->addShader(shader: vertexShader); | 
| 225 |             } | 
| 226 |  | 
| 227 |             { | 
| 228 |                 QString source; | 
| 229 |                 source.append(s: QLatin1String(qglslMainFragmentShader)); | 
| 230 |                 source.append(s: QLatin1String(qglslImageSrcFragmentShader)); | 
| 231 |  | 
| 232 |                 QGLShader *fragmentShader = new QGLShader(QGLShader::Fragment, m_blitProgram); | 
| 233 |                 fragmentShader->compileSourceCode(source); | 
| 234 |  | 
| 235 |                 m_blitProgram->addShader(shader: fragmentShader); | 
| 236 |             } | 
| 237 |  | 
| 238 |             m_blitProgram->bindAttributeLocation(name: "vertexCoordsArray" , location: QT_VERTEX_COORDS_ATTR); | 
| 239 |             m_blitProgram->bindAttributeLocation(name: "textureCoordArray" , location: QT_TEXTURE_COORDS_ATTR); | 
| 240 |  | 
| 241 |             m_blitProgram->link(); | 
| 242 |         } | 
| 243 |  | 
| 244 |         funcs->glVertexAttribPointer(indx: QT_VERTEX_COORDS_ATTR, size: 2, GL_FLOAT, GL_FALSE, stride: 0, ptr: m_vertexCoordinateArray); | 
| 245 |         funcs->glVertexAttribPointer(indx: QT_TEXTURE_COORDS_ATTR, size: 2, GL_FLOAT, GL_FALSE, stride: 0, ptr: m_textureCoordinateArray); | 
| 246 |  | 
| 247 |         m_blitProgram->bind(); | 
| 248 |         m_blitProgram->enableAttributeArray(location: int(QT_VERTEX_COORDS_ATTR)); | 
| 249 |         m_blitProgram->enableAttributeArray(location: int(QT_TEXTURE_COORDS_ATTR)); | 
| 250 |         m_blitProgram->disableAttributeArray(location: int(QT_OPACITY_ATTR)); | 
| 251 |  | 
| 252 |         blitProgram = m_blitProgram; | 
| 253 |  | 
| 254 |     } else { | 
| 255 |         pex->setVertexAttributePointer(arrayIndex: QT_VERTEX_COORDS_ATTR, pointer: m_vertexCoordinateArray); | 
| 256 |         pex->setVertexAttributePointer(arrayIndex: QT_TEXTURE_COORDS_ATTR, pointer: m_textureCoordinateArray); | 
| 257 |  | 
| 258 |         pex->shaderManager->useBlitProgram(); | 
| 259 |         blitProgram = pex->shaderManager->blitProgram(); | 
| 260 |     } | 
| 261 |  | 
| 262 |     blitProgram->setUniformValue(name: "imageTexture" , QT_IMAGE_TEXTURE_UNIT); | 
| 263 |  | 
| 264 |     funcs->glDrawArrays(GL_TRIANGLE_FAN, first: 0, count: 4); | 
| 265 |  | 
| 266 |     funcs->glBindTexture(GL_TEXTURE_2D, texture: m_textureResource->m_texture); | 
| 267 |  | 
| 268 |     funcs->glCopyTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: 0, yoffset: 0, x: 0, y: 0, width: oldWidth, height: oldHeight); | 
| 269 |  | 
| 270 |     funcs->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | 
| 271 |                                      GL_RENDERBUFFER, renderbuffer: 0); | 
| 272 |     funcs->glDeleteTextures(n: 1, textures: &tmp_texture); | 
| 273 |     funcs->glDeleteTextures(n: 1, textures: &oldTexture); | 
| 274 |  | 
| 275 |     funcs->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->d_ptr->current_fbo); | 
| 276 |  | 
| 277 |     if (pex != 0) { | 
| 278 |         funcs->glViewport(x: 0, y: 0, width: pex->width, height: pex->height); | 
| 279 |         pex->updateClipScissorTest(); | 
| 280 |     } | 
| 281 | } | 
| 282 |  | 
| 283 | void QGLTextureGlyphCache::fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition) | 
| 284 | { | 
| 285 |     QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); | 
| 286 |     if (ctx == 0) { | 
| 287 |         qWarning(msg: "QGLTextureGlyphCache::fillTexture: Called with no context" ); | 
| 288 |         return; | 
| 289 |     } | 
| 290 |     QOpenGLFunctions *funcs = ctx->contextHandle()->functions(); | 
| 291 |  | 
| 292 |     if (!QGLFramebufferObject::hasOpenGLFramebufferObjects() || ctx->d_ptr->workaround_brokenFBOReadBack) { | 
| 293 |         QImageTextureGlyphCache::fillTexture(c, glyph, subPixelPosition); | 
| 294 |  | 
| 295 |         funcs->glBindTexture(GL_TEXTURE_2D, texture: m_textureResource->m_texture); | 
| 296 |         const QImage &texture = image(); | 
| 297 |         const uchar *bits = texture.constBits(); | 
| 298 |         bits += c.y * texture.bytesPerLine() + c.x; | 
| 299 |         for (int i=0; i<c.h; ++i) { | 
| 300 |             funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: c.x, yoffset: c.y + i, width: c.w, height: 1, GL_ALPHA, GL_UNSIGNED_BYTE, pixels: bits); | 
| 301 |             bits += texture.bytesPerLine(); | 
| 302 |         } | 
| 303 |         return; | 
| 304 |     } | 
| 305 |  | 
| 306 |     QImage mask = textureMapForGlyph(g: glyph, subPixelPosition); | 
| 307 |     const int maskWidth = mask.width(); | 
| 308 |     const int maskHeight = mask.height(); | 
| 309 |  | 
| 310 |     if (mask.format() == QImage::Format_Mono) { | 
| 311 |         mask = mask.convertToFormat(f: QImage::Format_Indexed8); | 
| 312 |         for (int y = 0; y < maskHeight; ++y) { | 
| 313 |             uchar *src = (uchar *) mask.scanLine(y); | 
| 314 |             for (int x = 0; x < maskWidth; ++x) | 
| 315 |                 src[x] = -src[x]; // convert 0 and 1 into 0 and 255 | 
| 316 |         } | 
| 317 |     } else if (mask.depth() == 32) { | 
| 318 |         // Make the alpha component equal to the average of the RGB values. | 
| 319 |         // This is needed when drawing sub-pixel antialiased text on translucent targets. | 
| 320 |         for (int y = 0; y < maskHeight; ++y) { | 
| 321 |             quint32 *src = (quint32 *) mask.scanLine(y); | 
| 322 |             for (int x = 0; x < maskWidth; ++x) { | 
| 323 |                 int r = qRed(rgb: src[x]); | 
| 324 |                 int g = qGreen(rgb: src[x]); | 
| 325 |                 int b = qBlue(rgb: src[x]); | 
| 326 |                 int avg; | 
| 327 |                 if (mask.format() == QImage::Format_RGB32) | 
| 328 |                     avg = (r + g + b + 1) / 3; // "+1" for rounding. | 
| 329 |                 else // Format_ARGB_Premultiplied | 
| 330 |                     avg = qAlpha(rgb: src[x]); | 
| 331 |                 if (ctx->contextHandle()->isOpenGLES()) { | 
| 332 |                     // swizzle the bits to accommodate for the GL_RGBA upload. | 
| 333 |                     src[x] = (avg << 24) | (r << 0) | (g << 8) | (b << 16); | 
| 334 |                 } else { | 
| 335 |                     src[x] = (src[x] & 0x00ffffff) | (avg << 24); | 
| 336 |                 } | 
| 337 |             } | 
| 338 |         } | 
| 339 |     } | 
| 340 |  | 
| 341 |     funcs->glBindTexture(GL_TEXTURE_2D, texture: m_textureResource->m_texture); | 
| 342 |     if (mask.depth() == 32) { | 
| 343 |         GLenum format = GL_RGBA; | 
| 344 | #if !defined(QT_OPENGL_ES_2) | 
| 345 |         if (!ctx->contextHandle()->isOpenGLES()) | 
| 346 |             format = GL_BGRA; | 
| 347 | #endif | 
| 348 |         funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: c.x, yoffset: c.y, width: maskWidth, height: maskHeight, format, GL_UNSIGNED_BYTE, pixels: mask.bits()); | 
| 349 |     } else { | 
| 350 |         // glTexSubImage2D() might cause some garbage to appear in the texture if the mask width is | 
| 351 |         // not a multiple of four bytes. The bug appeared on a computer with 32-bit Windows Vista | 
| 352 |         // and nVidia GeForce 8500GT. GL_UNPACK_ALIGNMENT is set to four bytes, 'mask' has a | 
| 353 |         // multiple of four bytes per line, and most of the glyph shows up correctly in the | 
| 354 |         // texture, which makes me think that this is a driver bug. | 
| 355 |         // One workaround is to make sure the mask width is a multiple of four bytes, for instance | 
| 356 |         // by converting it to a format with four bytes per pixel. Another is to copy one line at a | 
| 357 |         // time. | 
| 358 |  | 
| 359 |         if (!ctx->d_ptr->workaround_brokenAlphaTexSubImage_init) { | 
| 360 |             // don't know which driver versions exhibit this bug, so be conservative for now | 
| 361 |             const QByteArray vendorString(reinterpret_cast<const char*>(funcs->glGetString(GL_VENDOR))); | 
| 362 |             ctx->d_ptr->workaround_brokenAlphaTexSubImage = vendorString.indexOf(c: "NVIDIA" ) >= 0; | 
| 363 |             ctx->d_ptr->workaround_brokenAlphaTexSubImage_init = true; | 
| 364 |         } | 
| 365 |  | 
| 366 |         if (ctx->d_ptr->workaround_brokenAlphaTexSubImage) { | 
| 367 |             for (int i = 0; i < maskHeight; ++i) | 
| 368 |                 funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: c.x, yoffset: c.y + i, width: maskWidth, height: 1, GL_ALPHA, GL_UNSIGNED_BYTE, pixels: mask.scanLine(i)); | 
| 369 |         } else { | 
| 370 |             funcs->glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: c.x, yoffset: c.y, width: maskWidth, height: maskHeight, GL_ALPHA, GL_UNSIGNED_BYTE, pixels: mask.bits()); | 
| 371 |         } | 
| 372 |     } | 
| 373 | } | 
| 374 |  | 
| 375 | int QGLTextureGlyphCache::glyphPadding() const | 
| 376 | { | 
| 377 |     return 1; | 
| 378 | } | 
| 379 |  | 
| 380 | int QGLTextureGlyphCache::maxTextureWidth() const | 
| 381 | { | 
| 382 |     QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); | 
| 383 |     if (ctx == 0) | 
| 384 |         return QImageTextureGlyphCache::maxTextureWidth(); | 
| 385 |     else | 
| 386 |         return ctx->d_ptr->maxTextureSize(); | 
| 387 | } | 
| 388 |  | 
| 389 | int QGLTextureGlyphCache::maxTextureHeight() const | 
| 390 | { | 
| 391 |     QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); | 
| 392 |     if (ctx == 0) | 
| 393 |         return QImageTextureGlyphCache::maxTextureHeight(); | 
| 394 |  | 
| 395 |     if (ctx->d_ptr->workaround_brokenTexSubImage) | 
| 396 |         return qMin(a: 1024, b: ctx->d_ptr->maxTextureSize()); | 
| 397 |     else | 
| 398 |         return ctx->d_ptr->maxTextureSize(); | 
| 399 | } | 
| 400 |  | 
| 401 | void QGLTextureGlyphCache::clear() | 
| 402 | { | 
| 403 |     m_textureResource->free(); | 
| 404 |     m_textureResource = 0; | 
| 405 |  | 
| 406 |     m_w = 0; | 
| 407 |     m_h = 0; | 
| 408 |     m_cx = 0; | 
| 409 |     m_cy = 0; | 
| 410 |     m_currentRowHeight = 0; | 
| 411 |     coords.clear(); | 
| 412 | } | 
| 413 |  | 
| 414 | QT_END_NAMESPACE | 
| 415 |  |