1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "texturehelper_p.h" |
5 | #include "utils_p.h" |
6 | |
7 | #include <QtGui/QImage> |
8 | #include <QtGui/QPainter> |
9 | #include <QtCore/QTime> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | // Defined in shaderhelper.cpp |
14 | extern void discardDebugMsgs(QtMsgType type, const QMessageLogContext &context, const QString &msg); |
15 | |
16 | TextureHelper::TextureHelper() |
17 | { |
18 | initializeOpenGLFunctions(); |
19 | #if !QT_CONFIG(opengles2) |
20 | if (!Utils::isOpenGLES()) { |
21 | // Discard warnings about deprecated functions |
22 | QtMessageHandler handler = qInstallMessageHandler(discardDebugMsgs); |
23 | |
24 | m_openGlFunctions_2_1 = new QOpenGLFunctions_2_1; |
25 | if (m_openGlFunctions_2_1) |
26 | m_openGlFunctions_2_1->initializeOpenGLFunctions(); |
27 | |
28 | // Restore original message handler |
29 | qInstallMessageHandler(handler); |
30 | |
31 | if (!m_openGlFunctions_2_1) |
32 | qFatal(msg: "OpenGL version is too low, at least 2.1 is required" ); |
33 | } |
34 | #endif |
35 | } |
36 | |
37 | TextureHelper::~TextureHelper() |
38 | { |
39 | #if !QT_CONFIG(opengles2) |
40 | delete m_openGlFunctions_2_1; |
41 | #endif |
42 | } |
43 | |
44 | GLuint TextureHelper::create2DTexture(const QImage &image, bool useTrilinearFiltering, |
45 | bool convert, bool smoothScale, bool clampY) |
46 | { |
47 | if (image.isNull()) |
48 | return 0; |
49 | |
50 | QImage texImage = image; |
51 | |
52 | if (Utils::isOpenGLES()) { |
53 | GLuint imageWidth = Utils::getNearestPowerOfTwo(value: image.width()); |
54 | GLuint imageHeight = Utils::getNearestPowerOfTwo(value: image.height()); |
55 | if (smoothScale) { |
56 | texImage = image.scaled(w: imageWidth, h: imageHeight, aspectMode: Qt::IgnoreAspectRatio, |
57 | mode: Qt::SmoothTransformation); |
58 | } else { |
59 | texImage = image.scaled(w: imageWidth, h: imageHeight, aspectMode: Qt::IgnoreAspectRatio); |
60 | } |
61 | } |
62 | |
63 | GLuint textureId; |
64 | glGenTextures(n: 1, textures: &textureId); |
65 | glBindTexture(GL_TEXTURE_2D, texture: textureId); |
66 | if (convert) |
67 | texImage = convertToGLFormat(srcImage: texImage); |
68 | glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: texImage.width(), height: texImage.height(), |
69 | border: 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels: texImage.bits()); |
70 | if (smoothScale) |
71 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
72 | else |
73 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
74 | if (useTrilinearFiltering) { |
75 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
76 | glGenerateMipmap(GL_TEXTURE_2D); |
77 | } else { |
78 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
79 | } |
80 | if (clampY) |
81 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
82 | glBindTexture(GL_TEXTURE_2D, texture: 0); |
83 | |
84 | return textureId; |
85 | } |
86 | |
87 | GLuint TextureHelper::create3DTexture(const QList<uchar> *data, int width, int height, int depth, |
88 | QImage::Format dataFormat) |
89 | { |
90 | if (Utils::isOpenGLES() || !width || !height || !depth) |
91 | return 0; |
92 | |
93 | GLuint textureId = 0; |
94 | #if QT_CONFIG(opengles2) |
95 | Q_UNUSED(dataFormat); |
96 | Q_UNUSED(data); |
97 | #else |
98 | glEnable(GL_TEXTURE_3D); |
99 | |
100 | glGenTextures(n: 1, textures: &textureId); |
101 | glBindTexture(GL_TEXTURE_3D, texture: textureId); |
102 | glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
103 | glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
104 | glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); |
105 | glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
106 | glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
107 | |
108 | GLenum status = glGetError(); |
109 | // glGetError docs advise to call glGetError in loop to clear all error flags |
110 | while (status) |
111 | status = glGetError(); |
112 | |
113 | GLint internalFormat = 4; |
114 | GLint format = GL_BGRA; |
115 | if (dataFormat == QImage::Format_Indexed8) { |
116 | internalFormat = 1; |
117 | format = GL_RED; |
118 | // Align width to 32bits |
119 | width = width + width % 4; |
120 | } |
121 | m_openGlFunctions_2_1->glTexImage3D(GL_TEXTURE_3D, level: 0, internalformat: internalFormat, width, height, depth, border: 0, |
122 | format, GL_UNSIGNED_BYTE, pixels: data->constData()); |
123 | status = glGetError(); |
124 | if (status) |
125 | qWarning() << __FUNCTION__ << "3D texture creation failed:" << status; |
126 | |
127 | glBindTexture(GL_TEXTURE_3D, texture: 0); |
128 | glDisable(GL_TEXTURE_3D); |
129 | #endif |
130 | return textureId; |
131 | } |
132 | |
133 | GLuint TextureHelper::createCubeMapTexture(const QImage &image, bool useTrilinearFiltering) |
134 | { |
135 | if (image.isNull()) |
136 | return 0; |
137 | |
138 | GLuint textureId; |
139 | glGenTextures(n: 1, textures: &textureId); |
140 | glBindTexture(GL_TEXTURE_CUBE_MAP, texture: textureId); |
141 | QImage glTexture = convertToGLFormat(srcImage: image); |
142 | glTexImage2D(GL_TEXTURE_CUBE_MAP, level: 0, GL_RGBA, width: glTexture.width(), height: glTexture.height(), |
143 | border: 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels: glTexture.bits()); |
144 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
145 | if (useTrilinearFiltering) { |
146 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
147 | glGenerateMipmap(GL_TEXTURE_CUBE_MAP); |
148 | } else { |
149 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
150 | } |
151 | glBindTexture(GL_TEXTURE_2D, texture: 0); |
152 | return textureId; |
153 | } |
154 | |
155 | GLuint TextureHelper::createSelectionTexture(const QSize &size, GLuint &frameBuffer, |
156 | GLuint &depthBuffer) |
157 | { |
158 | GLuint textureid; |
159 | |
160 | // Create texture for the selection buffer |
161 | glGenTextures(n: 1, textures: &textureid); |
162 | glBindTexture(GL_TEXTURE_2D, texture: textureid); |
163 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
164 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
165 | glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: size.width(), height: size.height(), border: 0, GL_RGBA, |
166 | GL_UNSIGNED_BYTE, NULL); |
167 | glBindTexture(GL_TEXTURE_2D, texture: 0); |
168 | |
169 | // Create render buffer |
170 | if (depthBuffer) |
171 | glDeleteRenderbuffers(n: 1, renderbuffers: &depthBuffer); |
172 | |
173 | glGenRenderbuffers(n: 1, renderbuffers: &depthBuffer); |
174 | glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: depthBuffer); |
175 | GLenum status = glGetError(); |
176 | // glGetError docs advise to call glGetError in loop to clear all error flags |
177 | while (status) |
178 | status = glGetError(); |
179 | if (Utils::isOpenGLES()) |
180 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width: size.width(), height: size.height()); |
181 | else |
182 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width: size.width(), height: size.height()); |
183 | |
184 | status = glGetError(); |
185 | if (status) { |
186 | qCritical() << "Selection texture render buffer creation failed:" << status; |
187 | glDeleteTextures(n: 1, textures: &textureid); |
188 | glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: 0); |
189 | return 0; |
190 | } |
191 | glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: 0); |
192 | |
193 | // Create frame buffer |
194 | if (!frameBuffer) |
195 | glGenFramebuffers(n: 1, framebuffers: &frameBuffer); |
196 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: frameBuffer); |
197 | |
198 | // Attach texture to color attachment |
199 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture: textureid, level: 0); |
200 | // Attach renderbuffer to depth attachment |
201 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer: depthBuffer); |
202 | |
203 | // Verify that the frame buffer is complete |
204 | status = glCheckFramebufferStatus(GL_FRAMEBUFFER); |
205 | if (status != GL_FRAMEBUFFER_COMPLETE) { |
206 | qCritical() << "Selection texture frame buffer creation failed:" << status; |
207 | glDeleteTextures(n: 1, textures: &textureid); |
208 | textureid = 0; |
209 | } |
210 | |
211 | // Restore the default framebuffer |
212 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: 0); |
213 | |
214 | return textureid; |
215 | } |
216 | |
217 | GLuint TextureHelper::createCursorPositionTexture(const QSize &size, GLuint &frameBuffer) |
218 | { |
219 | GLuint textureid; |
220 | glGenTextures(n: 1, textures: &textureid); |
221 | glBindTexture(GL_TEXTURE_2D, texture: textureid); |
222 | glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: size.width(), height: size.height(), border: 0, GL_RGBA, |
223 | GL_UNSIGNED_BYTE, NULL); |
224 | glBindTexture(GL_TEXTURE_2D, texture: 0); |
225 | |
226 | glGenFramebuffers(n: 1, framebuffers: &frameBuffer); |
227 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: frameBuffer); |
228 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
229 | texture: textureid, level: 0); |
230 | |
231 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); |
232 | if (status != GL_FRAMEBUFFER_COMPLETE) { |
233 | qCritical() << "Cursor position mapper frame buffer creation failed:" << status; |
234 | glDeleteTextures(n: 1, textures: &textureid); |
235 | textureid = 0; |
236 | } |
237 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: 0); |
238 | |
239 | return textureid; |
240 | } |
241 | |
242 | GLuint TextureHelper::createUniformTexture(const QColor &color) |
243 | { |
244 | QImage image(QSize(int(uniformTextureWidth), int(uniformTextureHeight)), |
245 | QImage::Format_RGB32); |
246 | QPainter pmp(&image); |
247 | pmp.setBrush(QBrush(color)); |
248 | pmp.setPen(Qt::NoPen); |
249 | pmp.drawRect(x: 0, y: 0, w: int(uniformTextureWidth), h: int(uniformTextureHeight)); |
250 | |
251 | return create2DTexture(image, useTrilinearFiltering: false, convert: true, smoothScale: false, clampY: true); |
252 | } |
253 | |
254 | GLuint TextureHelper::createGradientTexture(const QLinearGradient &gradient) |
255 | { |
256 | QImage image(QSize(int(gradientTextureWidth), int(gradientTextureHeight)), |
257 | QImage::Format_RGB32); |
258 | QPainter pmp(&image); |
259 | pmp.setBrush(QBrush(gradient)); |
260 | pmp.setPen(Qt::NoPen); |
261 | pmp.drawRect(x: 0, y: 0, w: int(gradientTextureWidth), h: int(gradientTextureHeight)); |
262 | |
263 | return create2DTexture(image, useTrilinearFiltering: false, convert: true, smoothScale: false, clampY: true); |
264 | } |
265 | |
266 | GLuint TextureHelper::createDepthTexture(const QSize &size, GLuint textureSize) |
267 | { |
268 | GLuint depthtextureid = 0; |
269 | #if QT_CONFIG(opengles2) |
270 | Q_UNUSED(size); |
271 | Q_UNUSED(textureSize); |
272 | #else |
273 | if (!Utils::isOpenGLES()) { |
274 | // Create depth texture for the shadow mapping |
275 | glGenTextures(n: 1, textures: &depthtextureid); |
276 | glBindTexture(GL_TEXTURE_2D, texture: depthtextureid); |
277 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
278 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
279 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
280 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
281 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); |
282 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); |
283 | glTexImage2D(GL_TEXTURE_2D, level: 0, GL_DEPTH_COMPONENT, width: size.width() * textureSize, |
284 | height: size.height() * textureSize, border: 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); |
285 | glBindTexture(GL_TEXTURE_2D, texture: 0); |
286 | } |
287 | #endif |
288 | return depthtextureid; |
289 | } |
290 | |
291 | GLuint TextureHelper::createDepthTextureFrameBuffer(const QSize &size, GLuint &frameBuffer, |
292 | GLuint textureSize) |
293 | { |
294 | GLuint depthtextureid = createDepthTexture(size, textureSize); |
295 | #if QT_CONFIG(opengles2) |
296 | Q_UNUSED(frameBuffer); |
297 | #else |
298 | if (!Utils::isOpenGLES()) { |
299 | // Create frame buffer |
300 | if (!frameBuffer) |
301 | glGenFramebuffers(n: 1, framebuffers: &frameBuffer); |
302 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: frameBuffer); |
303 | |
304 | // Attach texture to depth attachment |
305 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture: depthtextureid, level: 0); |
306 | |
307 | m_openGlFunctions_2_1->glDrawBuffers(n: 0, GL_NONE); |
308 | m_openGlFunctions_2_1->glReadBuffer(GL_NONE); |
309 | |
310 | // Verify that the frame buffer is complete |
311 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); |
312 | if (status != GL_FRAMEBUFFER_COMPLETE) { |
313 | qCritical() << "Depth texture frame buffer creation failed" << status; |
314 | glDeleteTextures(n: 1, textures: &depthtextureid); |
315 | depthtextureid = 0; |
316 | } |
317 | |
318 | // Restore the default framebuffer |
319 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: 0); |
320 | } |
321 | #endif |
322 | return depthtextureid; |
323 | } |
324 | |
325 | void TextureHelper::deleteTexture(GLuint *texture) |
326 | { |
327 | if (texture && *texture) { |
328 | if (QOpenGLContext::currentContext()) |
329 | glDeleteTextures(n: 1, textures: texture); |
330 | *texture = 0; |
331 | } |
332 | } |
333 | |
334 | QImage TextureHelper::convertToGLFormat(const QImage &srcImage) |
335 | { |
336 | QImage res(srcImage.size(), QImage::Format_ARGB32); |
337 | convertToGLFormatHelper(dstImage&: res, srcImage: srcImage.convertToFormat(f: QImage::Format_ARGB32), GL_RGBA); |
338 | return res; |
339 | } |
340 | |
341 | void TextureHelper::convertToGLFormatHelper(QImage &dstImage, const QImage &srcImage, |
342 | GLenum texture_format) |
343 | { |
344 | Q_ASSERT(dstImage.depth() == 32); |
345 | Q_ASSERT(srcImage.depth() == 32); |
346 | |
347 | if (dstImage.size() != srcImage.size()) { |
348 | int target_width = dstImage.width(); |
349 | int target_height = dstImage.height(); |
350 | float sx = target_width / float(srcImage.width()); |
351 | float sy = target_height / float(srcImage.height()); |
352 | |
353 | quint32 *dest = (quint32 *) dstImage.scanLine(0); // NB! avoid detach here |
354 | uchar *srcPixels = (uchar *) srcImage.scanLine(srcImage.height() - 1); |
355 | int sbpl = srcImage.bytesPerLine(); |
356 | int dbpl = dstImage.bytesPerLine(); |
357 | |
358 | int ix = int(0x00010000 / sx); |
359 | int iy = int(0x00010000 / sy); |
360 | |
361 | quint32 basex = int(0.5 * ix); |
362 | quint32 srcy = int(0.5 * iy); |
363 | |
364 | // scale, swizzle and mirror in one loop |
365 | while (target_height--) { |
366 | const uint *src = (const quint32 *) (srcPixels - (srcy >> 16) * sbpl); |
367 | int srcx = basex; |
368 | for (int x=0; x<target_width; ++x) { |
369 | dest[x] = qt_gl_convertToGLFormatHelper(src_pixel: src[srcx >> 16], texture_format); |
370 | srcx += ix; |
371 | } |
372 | dest = (quint32 *)(((uchar *) dest) + dbpl); |
373 | srcy += iy; |
374 | } |
375 | } else { |
376 | const int width = srcImage.width(); |
377 | const int height = srcImage.height(); |
378 | const uint *p = (const uint*) srcImage.scanLine(srcImage.height() - 1); |
379 | uint *q = (uint*) dstImage.scanLine(0); |
380 | |
381 | #if !QT_CONFIG(opengles2) |
382 | if (texture_format == GL_BGRA) { |
383 | #else |
384 | if (texture_format == GL_BGRA8_EXT) { |
385 | #endif |
386 | if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { |
387 | // mirror + swizzle |
388 | for (int i=0; i < height; ++i) { |
389 | const uint *end = p + width; |
390 | while (p < end) { |
391 | *q = ((*p << 24) & 0xff000000) |
392 | | ((*p >> 24) & 0x000000ff) |
393 | | ((*p << 8) & 0x00ff0000) |
394 | | ((*p >> 8) & 0x0000ff00); |
395 | p++; |
396 | q++; |
397 | } |
398 | p -= 2 * width; |
399 | } |
400 | } else { |
401 | const uint bytesPerLine = srcImage.bytesPerLine(); |
402 | for (int i=0; i < height; ++i) { |
403 | memcpy(dest: q, src: p, n: bytesPerLine); |
404 | q += width; |
405 | p -= width; |
406 | } |
407 | } |
408 | } else { |
409 | if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { |
410 | for (int i=0; i < height; ++i) { |
411 | const uint *end = p + width; |
412 | while (p < end) { |
413 | *q = (*p << 8) | ((*p >> 24) & 0xff); |
414 | p++; |
415 | q++; |
416 | } |
417 | p -= 2 * width; |
418 | } |
419 | } else { |
420 | for (int i=0; i < height; ++i) { |
421 | const uint *end = p + width; |
422 | while (p < end) { |
423 | *q = ((*p << 16) & 0xff0000) | ((*p >> 16) & 0xff) | (*p & 0xff00ff00); |
424 | p++; |
425 | q++; |
426 | } |
427 | p -= 2 * width; |
428 | } |
429 | } |
430 | } |
431 | } |
432 | } |
433 | |
434 | QRgb TextureHelper::qt_gl_convertToGLFormatHelper(QRgb src_pixel, GLenum texture_format) |
435 | { |
436 | #if !QT_CONFIG(opengles2) |
437 | if (texture_format == GL_BGRA) { |
438 | #else |
439 | if (texture_format == GL_BGRA8_EXT) { |
440 | #endif |
441 | if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { |
442 | return ((src_pixel << 24) & 0xff000000) |
443 | | ((src_pixel >> 24) & 0x000000ff) |
444 | | ((src_pixel << 8) & 0x00ff0000) |
445 | | ((src_pixel >> 8) & 0x0000ff00); |
446 | } else { |
447 | return src_pixel; |
448 | } |
449 | } else { // GL_RGBA |
450 | if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { |
451 | return (src_pixel << 8) | ((src_pixel >> 24) & 0xff); |
452 | } else { |
453 | return ((src_pixel << 16) & 0xff0000) |
454 | | ((src_pixel >> 16) & 0xff) |
455 | | (src_pixel & 0xff00ff00); |
456 | } |
457 | } |
458 | } |
459 | |
460 | QT_END_NAMESPACE |
461 | |