1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #define GL_GLEXT_PROTOTYPES
44 #include "qsgtexture_p.h"
45 #include <qopenglfunctions.h>
46 #include <QtQuick/private/qsgcontext_p.h>
48 #include <private/qqmlprofilerservice_p.h>
49 #include <private/qqmlglobal_p.h>
51 #if !defined(QT_NO_DEBUG) && (defined(Q_OS_LINUX) || defined(Q_OS_MAC))
58 inline static bool isPowerOfTwo(int x)
64 QSGTexturePrivate::QSGTexturePrivate()
66 , filteringChanged(false)
67 , horizontalWrap(QSGTexture::ClampToEdge)
68 , verticalWrap(QSGTexture::ClampToEdge)
69 , mipmapMode(QSGTexture::None)
70 , filterMode(QSGTexture::Nearest)
76 static int qt_debug_texture_count = 0;
78 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
79 DEFINE_BOOL_CONFIG_OPTION(qmlDebugLeakBacktrace, QML_DEBUG_LEAK_BACKTRACE)
81 #define BACKTRACE_SIZE 20
82 class SGTextureTraceItem
85 void *backTrace[BACKTRACE_SIZE];
89 static QHash<QSGTexture*, SGTextureTraceItem*> qt_debug_allocated_textures;
92 inline static void qt_debug_print_texture_count()
94 qDebug("Number of leaked textures: %i", qt_debug_texture_count);
95 qt_debug_texture_count = -1;
97 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
98 if (qmlDebugLeakBacktrace()) {
99 while (!qt_debug_allocated_textures.isEmpty()) {
100 QHash<QSGTexture*, SGTextureTraceItem*>::Iterator it = qt_debug_allocated_textures.begin();
101 QSGTexture* texture = it.key();
102 SGTextureTraceItem* item = it.value();
104 qt_debug_allocated_textures.erase(it);
106 qDebug() << "------";
107 qDebug() << "Leaked" << texture << "backtrace:";
109 char** symbols = backtrace_symbols(item->backTrace, item->backTraceSize);
112 for (int i=0; i<(int) item->backTraceSize; i++)
113 qDebug("Backtrace <%02d>: %s", i, symbols[i]);
117 qDebug() << "------";
125 inline static void qt_debug_add_texture(QSGTexture* texture)
127 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
128 if (qmlDebugLeakBacktrace()) {
129 SGTextureTraceItem* item = new SGTextureTraceItem;
130 item->backTraceSize = backtrace(item->backTrace, BACKTRACE_SIZE);
131 qt_debug_allocated_textures.insert(texture, item);
137 ++qt_debug_texture_count;
139 static bool atexit_registered = false;
140 if (!atexit_registered) {
141 atexit(qt_debug_print_texture_count);
142 atexit_registered = true;
146 static void qt_debug_remove_texture(QSGTexture* texture)
148 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
149 if (qmlDebugLeakBacktrace()) {
150 SGTextureTraceItem* item = qt_debug_allocated_textures.value(texture, 0);
152 qt_debug_allocated_textures.remove(texture);
160 --qt_debug_texture_count;
162 if (qt_debug_texture_count < 0)
163 qDebug("Material destroyed after qt_debug_print_texture_count() was called.");
166 #endif // QT_NO_DEBUG
169 QSGTexture::QSGTexture()
170 : QObject(*(new QSGTexturePrivate))
173 qt_debug_add_texture(this);
177 QSGTexture::~QSGTexture()
180 qt_debug_remove_texture(this);
186 \fn void QSGTexture::bind()
188 Call this function to bind this texture to the current texture
191 Binding a texture may also include uploading the texture data from
192 a previously set QImage.
194 \warning This function can only be called from the rendering thread.
198 This function returns a copy of the current texture which is removed
201 The current texture remains unchanged, so texture coordinates do not
204 Removing a texture from an atlas is primarily useful when passing
205 it to a shader that operates on the texture coordinates 0-1 instead
206 of the texture subrect inside the atlas.
208 If the texture is not part of a texture atlas, this function returns 0.
210 Implementations of this function are recommended to return the same instance
211 for multiple calls to limit memory usage.
213 \warning This function can only be called from the rendering thread.
216 QSGTexture *QSGTexture::removedFromAtlas() const
218 Q_ASSERT_X(!isAtlasTexture(), "QSGTexture::removedFromAtlas()", "Called on a non-atlas texture");
223 Returns weither this texture is part of an atlas or not.
225 The default implementation returns false.
227 bool QSGTexture::isAtlasTexture() const
233 \fn int QSGTexture::textureId() const
235 Returns the OpenGL texture id for this texture.
237 The default value is 0, indicating that it is an invalid texture id.
239 The function should at all times return the correct texture id.
241 \warning This function can only be called from the rendering thread.
247 Returns the rectangle inside textureSize() that this texture
248 represents in normalized coordinates.
250 The default implementation returns a rect at position (0, 0) with
251 width and height of 1.
253 QRectF QSGTexture::normalizedTextureSubRect() const
255 return QRectF(0, 0, 1, 1);
259 \fn bool QSGTexture::hasMipmaps() const
261 Returns true if the texture data contains mipmap levels.
266 Sets the mipmap sampling mode to be used for the upcoming bind() call to \a filter.
268 Setting the mipmap filtering has no effect it the texture does not have mipmaps.
272 void QSGTexture::setMipmapFiltering(Filtering filter)
275 if (d->mipmapMode != (uint) filter) {
276 d->mipmapMode = filter;
277 d->filteringChanged = true;
282 Returns whether mipmapping should be used when sampling from this texture.
284 QSGTexture::Filtering QSGTexture::mipmapFiltering() const
286 return (QSGTexture::Filtering) d_func()->mipmapMode;
291 Sets the sampling mode to be used for the upcoming bind() call to \a filter.
293 void QSGTexture::setFiltering(QSGTexture::Filtering filter)
296 if (d->filterMode != (uint) filter) {
297 d->filterMode = filter;
298 d->filteringChanged = true;
302 QSGTexture::Filtering QSGTexture::filtering() const
304 return (QSGTexture::Filtering) d_func()->filterMode;
310 Sets the horizontal wrap mode to be used for the upcoming bind() call to \a hwrap
313 void QSGTexture::setHorizontalWrapMode(WrapMode hwrap)
316 if ((uint) hwrap != d->horizontalWrap) {
317 d->horizontalWrap = hwrap;
318 d->wrapChanged = true;
322 QSGTexture::WrapMode QSGTexture::horizontalWrapMode() const
324 return (QSGTexture::WrapMode) d_func()->horizontalWrap;
329 void QSGTexture::setVerticalWrapMode(WrapMode vwrap)
332 if ((uint) vwrap != d->verticalWrap) {
333 d->verticalWrap = vwrap;
334 d->wrapChanged = true;
338 QSGTexture::WrapMode QSGTexture::verticalWrapMode() const
340 return (QSGTexture::WrapMode) d_func()->verticalWrap;
345 Update the texture state to match the filtering, mipmap and wrap options
348 If \a force is true, all properties will be updated regardless of weither
349 they have changed or not.
351 void QSGTexture::updateBindOptions(bool force)
354 if (force || d->filteringChanged) {
355 bool linear = d->filterMode == Linear;
356 GLint minFilter = linear ? GL_LINEAR : GL_NEAREST;
357 GLint magFilter = linear ? GL_LINEAR : GL_NEAREST;
360 if (d->mipmapMode == Nearest)
361 minFilter = linear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST;
362 else if (d->mipmapMode == Linear)
363 minFilter = linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR;
365 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
366 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
367 d->filteringChanged = false;
370 if (force || d->wrapChanged) {
371 #if !defined(QT_NO_DEBUG) && defined(QT_OPENGL_ES_2)
372 if (d->horizontalWrap == Repeat || d->verticalWrap == Repeat) {
373 bool npotSupported = QOpenGLFunctions(QOpenGLContext::currentContext()).hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
374 QSize size = textureSize();
375 bool isNpot = !isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height());
376 if (!npotSupported && isNpot)
377 qWarning("Scene Graph: This system does not support the REPEAT wrap mode for non-power-of-two textures.");
380 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, d->horizontalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
381 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, d->verticalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
382 d->wrapChanged = false;
386 QSGPlainTexture::QSGPlainTexture()
390 , m_has_mipmaps(false)
391 , m_dirty_bind_options(false)
392 , m_owns_texture(true)
393 , m_mipmaps_generated(false)
398 QSGPlainTexture::~QSGPlainTexture()
400 if (m_texture_id && m_owns_texture)
401 glDeleteTextures(1, &m_texture_id);
405 void qsg_swizzleBGRAToRGBA(QImage *image)
407 const int width = image->width();
408 const int height = image->height();
409 for (int i = 0; i < height; ++i) {
410 uint *p = (uint *) image->scanLine(i);
411 for (int x = 0; x < width; ++x)
412 p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
417 void QSGPlainTexture::setImage(const QImage &image)
420 m_texture_size = image.size();
421 m_has_alpha = image.hasAlphaChannel();
422 m_dirty_texture = true;
423 m_dirty_bind_options = true;
426 int QSGPlainTexture::textureId() const
428 if (m_dirty_texture) {
429 if (m_image.isNull()) {
430 // The actual texture and id will be updated/deleted in a later bind()
431 // or ~QSGPlainTexture so just keep it minimal here.
434 // Generate a texture id for use later and return it.
435 glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id);
442 void QSGPlainTexture::setTextureId(int id)
444 if (m_texture_id && m_owns_texture)
445 glDeleteTextures(1, &m_texture_id);
448 m_dirty_texture = false;
449 m_dirty_bind_options = true;
451 m_mipmaps_generated = false;
454 void QSGPlainTexture::setHasMipmaps(bool mm)
457 m_mipmaps_generated = false;
461 void QSGPlainTexture::bind()
463 if (!m_dirty_texture) {
464 glBindTexture(GL_TEXTURE_2D, m_texture_id);
465 if (m_has_mipmaps && !m_mipmaps_generated) {
466 QOpenGLContext *ctx = QOpenGLContext::currentContext();
467 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
468 m_mipmaps_generated = true;
470 updateBindOptions(m_dirty_bind_options);
471 m_dirty_bind_options = false;
475 m_dirty_texture = false;
478 if (m_image.isNull()) {
479 if (m_texture_id && m_owns_texture)
480 glDeleteTextures(1, &m_texture_id);
482 m_texture_size = QSize();
483 m_has_mipmaps = false;
488 if (m_texture_id == 0)
489 glGenTextures(1, &m_texture_id);
490 glBindTexture(GL_TEXTURE_2D, m_texture_id);
492 // ### TODO: check for out-of-memory situations...
493 int w = m_image.width();
494 int h = m_image.height();
496 QImage tmp = (m_image.format() == QImage::Format_RGB32 || m_image.format() == QImage::Format_ARGB32_Premultiplied)
498 : m_image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
500 updateBindOptions(m_dirty_bind_options);
503 qsg_swizzleBGRAToRGBA(&tmp);
504 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp.constBits());
506 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, tmp.constBits());
510 QOpenGLContext *ctx = QOpenGLContext::currentContext();
511 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
512 m_mipmaps_generated = true;
515 m_texture_size = QSize(w, h);
516 m_texture_rect = QRectF(0, 0, 1, 1);
518 m_dirty_bind_options = false;
523 \class QSGDynamicTexture
524 \brief The QSGDynamicTexture class serves as a baseclass for dynamically changing textures,
525 such as content that is rendered to FBO's.
527 To update the content of the texture, call updateTexture() explicitly. Simply calling bind()
528 will not update the texture.
533 \fn bool QSGDynamicTexture::updateTexture()
535 Call this function to explicitely update the dynamic texture. Calling bind() will bind
536 the content that was previously updated.
538 The function returns true if the texture was changed as a resul of the update; otherwise