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 "qopengltextureglyphcache_p.h" |
41 | #include <private/qopenglpaintengine_p.h> |
42 | #include "private/qopenglengineshadersource_p.h" |
43 | #include <private/qopenglextensions_p.h> |
44 | #include <qrgb.h> |
45 | #include <private/qdrawhelper_p.h> |
46 | |
47 | QT_BEGIN_NAMESPACE |
48 | |
49 | |
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 | |
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 | |
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 !QT_CONFIG(opengles2) |
96 | static inline bool isCoreProfile() |
97 | { |
98 | return QOpenGLContext::currentContext()->format().profile() == QSurfaceFormat::CoreProfile; |
99 | } |
100 | #endif |
101 | |
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 !QT_CONFIG(opengles2) |
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 | |
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 | |
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 | #if QT_CONFIG(opengles2) |
237 | GLenum fmt = GL_RGBA; |
238 | #else |
239 | GLenum fmt = ctx->isOpenGLES() ? GL_RGBA : GL_BGRA; |
240 | #endif // QT_CONFIG(opengles2) |
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 !QT_CONFIG(opengles2) |
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 | |
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 | |
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 | |
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 | |
442 | int QOpenGLTextureGlyphCache::glyphPadding() const |
443 | { |
444 | return 1; |
445 | } |
446 | |
447 | 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 | |
456 | 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 | |
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 | |