1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** 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/qdeclarativedebugtrace_p.h>
50 #if !defined(QT_NO_DEBUG) && (defined(Q_OS_LINUX) || defined(Q_OS_MAC))
57 inline static bool isPowerOfTwo(int x)
63 QSGTexturePrivate::QSGTexturePrivate()
65 , filteringChanged(false)
66 , horizontalWrap(QSGTexture::ClampToEdge)
67 , verticalWrap(QSGTexture::ClampToEdge)
68 , mipmapMode(QSGTexture::None)
69 , filterMode(QSGTexture::Nearest)
75 static int qt_debug_texture_count = 0;
77 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
78 DEFINE_BOOL_CONFIG_OPTION(qmlDebugLeakBacktrace, QML_DEBUG_LEAK_BACKTRACE)
80 #define BACKTRACE_SIZE 20
81 class SGTextureTraceItem
84 void *backTrace[BACKTRACE_SIZE];
88 static QHash<QSGTexture*, SGTextureTraceItem*> qt_debug_allocated_textures;
91 inline static void qt_debug_print_texture_count()
93 qDebug("Number of leaked textures: %i", qt_debug_texture_count);
94 qt_debug_texture_count = -1;
96 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
97 if (qmlDebugLeakBacktrace()) {
98 while (!qt_debug_allocated_textures.isEmpty()) {
99 QHash<QSGTexture*, SGTextureTraceItem*>::Iterator it = qt_debug_allocated_textures.begin();
100 QSGTexture* texture = it.key();
101 SGTextureTraceItem* item = it.value();
103 qt_debug_allocated_textures.erase(it);
105 qDebug() << "------";
106 qDebug() << "Leaked" << texture << "backtrace:";
108 char** symbols = backtrace_symbols(item->backTrace, item->backTraceSize);
111 for (int i=0; i<(int) item->backTraceSize; i++)
112 qDebug("Backtrace <%02d>: %s", i, symbols[i]);
116 qDebug() << "------";
124 inline static void qt_debug_add_texture(QSGTexture* texture)
126 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
127 if (qmlDebugLeakBacktrace()) {
128 SGTextureTraceItem* item = new SGTextureTraceItem;
129 item->backTraceSize = backtrace(item->backTrace, BACKTRACE_SIZE);
130 qt_debug_allocated_textures.insert(texture, item);
136 ++qt_debug_texture_count;
138 static bool atexit_registered = false;
139 if (!atexit_registered) {
140 atexit(qt_debug_print_texture_count);
141 atexit_registered = true;
145 static void qt_debug_remove_texture(QSGTexture* texture)
147 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
148 if (qmlDebugLeakBacktrace()) {
149 SGTextureTraceItem* item = qt_debug_allocated_textures.value(texture, 0);
151 qt_debug_allocated_textures.remove(texture);
159 --qt_debug_texture_count;
161 if (qt_debug_texture_count < 0)
162 qDebug("Material destroyed after qt_debug_print_texture_count() was called.");
165 #endif // QT_NO_DEBUG
168 QSGTexture::QSGTexture()
169 : QObject(*(new QSGTexturePrivate))
172 qt_debug_add_texture(this);
176 QSGTexture::~QSGTexture()
179 qt_debug_remove_texture(this);
185 \fn void QSGTexture::bind()
187 Call this function to bind this texture to the current texture
190 Binding a texture may also include uploading the texture data from
191 a previously set QImage.
193 \warning This function can only be called from the rendering thread.
197 This function returns a copy of the current texture which is removed
200 The current texture remains unchanged, so texture coordinates do not
203 Removing a texture from an atlas is primarily useful when passing
204 it to a shader that operates on the texture coordinates 0-1 instead
205 of the texture subrect inside the atlas.
207 If the texture is not part of a texture atlas, this function returns 0.
209 Implementations of this function are recommended to return the same instance
210 for multiple calls to limit memory usage.
212 \warning This function can only be called from the rendering thread.
215 QSGTexture *QSGTexture::removedFromAtlas() const
217 Q_ASSERT_X(!isAtlasTexture(), "QSGTexture::removedFromAtlas()", "Called on a non-atlas texture");
222 Returns weither this texture is part of an atlas or not.
224 The default implementation returns false.
226 bool QSGTexture::isAtlasTexture() const
232 \fn int QSGTexture::textureId() const
234 Returns the OpenGL texture id for this texture.
236 The default value is 0, indicating that it is an invalid texture id.
238 The function should at all times return the correct texture id.
240 \warning This function can only be called from the rendering thread.
246 Returns the rectangle inside textureSize() that this texture
247 represents in normalized coordinates.
249 The default implementation returns a rect at position (0, 0) with
250 width and height of 1.
252 QRectF QSGTexture::textureSubRect() const
254 return QRectF(0, 0, 1, 1);
258 \fn bool QSGTexture::hasMipmaps() const
260 Returns true if the texture data contains mipmap levels.
265 Sets the mipmap sampling mode to be used for the upcoming bind() call to \a filter.
267 Setting the mipmap filtering has no effect it the texture does not have mipmaps.
271 void QSGTexture::setMipmapFiltering(Filtering filter)
274 if (d->mipmapMode != (uint) filter) {
275 d->mipmapMode = filter;
276 d->filteringChanged = true;
281 Returns whether mipmapping should be used when sampling from this texture.
283 QSGTexture::Filtering QSGTexture::mipmapFiltering() const
285 return (QSGTexture::Filtering) d_func()->mipmapMode;
290 Sets the sampling mode to be used for the upcoming bind() call to \a filter.
292 void QSGTexture::setFiltering(QSGTexture::Filtering filter)
295 if (d->filterMode != (uint) filter) {
296 d->filterMode = filter;
297 d->filteringChanged = true;
301 QSGTexture::Filtering QSGTexture::filtering() const
303 return (QSGTexture::Filtering) d_func()->filterMode;
309 Sets the horizontal wrap mode to be used for the upcoming bind() call to \a hwrap
312 void QSGTexture::setHorizontalWrapMode(WrapMode hwrap)
315 if ((uint) hwrap != d->horizontalWrap) {
316 d->horizontalWrap = hwrap;
317 d->wrapChanged = true;
321 QSGTexture::WrapMode QSGTexture::horizontalWrapMode() const
323 return (QSGTexture::WrapMode) d_func()->horizontalWrap;
328 void QSGTexture::setVerticalWrapMode(WrapMode vwrap)
331 if ((uint) vwrap != d->verticalWrap) {
332 d->verticalWrap = vwrap;
333 d->wrapChanged = true;
337 QSGTexture::WrapMode QSGTexture::verticalWrapMode() const
339 return (QSGTexture::WrapMode) d_func()->verticalWrap;
344 Update the texture state to match the filtering, mipmap and wrap options
347 If \a force is true, all properties will be updated regardless of weither
348 they have changed or not.
350 void QSGTexture::updateBindOptions(bool force)
353 if (force || d->filteringChanged) {
354 bool linear = d->filterMode == Linear;
355 GLint minFilter = linear ? GL_LINEAR : GL_NEAREST;
356 GLint magFilter = linear ? GL_LINEAR : GL_NEAREST;
359 if (d->mipmapMode == Nearest)
360 minFilter = linear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST;
361 else if (d->mipmapMode == Linear)
362 minFilter = linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR;
364 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
365 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
366 d->filteringChanged = false;
369 if (force || d->wrapChanged) {
370 #if !defined(QT_NO_DEBUG) && defined(QT_OPENGL_ES_2)
371 if (d->horizontalWrap == Repeat || d->verticalWrap == Repeat) {
372 bool npotSupported = QOpenGLFunctions(QOpenGLContext::currentContext()).hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
373 QSize size = textureSize();
374 bool isNpot = !isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height());
375 if (!npotSupported && isNpot)
376 qWarning("Scene Graph: This system does not support the REPEAT wrap mode for non-power-of-two textures.");
379 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, d->horizontalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
380 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, d->verticalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
381 d->wrapChanged = false;
385 QSGPlainTexture::QSGPlainTexture()
389 , m_has_mipmaps(false)
390 , m_dirty_bind_options(false)
391 , m_owns_texture(true)
392 , m_mipmaps_generated(false)
397 QSGPlainTexture::~QSGPlainTexture()
399 if (m_texture_id && m_owns_texture)
400 glDeleteTextures(1, &m_texture_id);
404 static void swizzleBGRAToRGBA(QImage *image)
406 const int width = image->width();
407 const int height = image->height();
408 for (int i = 0; i < height; ++i) {
409 uint *p = (uint *) image->scanLine(i);
410 for (int x = 0; x < width; ++x)
411 p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
416 void QSGPlainTexture::setImage(const QImage &image)
419 m_texture_size = image.size();
420 m_has_alpha = image.hasAlphaChannel();
421 m_dirty_texture = true;
422 m_dirty_bind_options = true;
425 int QSGPlainTexture::textureId() const
427 if (m_dirty_texture) {
428 if (m_image.isNull()) {
429 // The actual texture and id will be updated/deleted in a later bind()
430 // or ~QSGPlainTexture so just keep it minimal here.
433 // Generate a texture id for use later and return it.
434 glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id);
441 void QSGPlainTexture::setTextureId(int id)
443 if (m_texture_id && m_owns_texture)
444 glDeleteTextures(1, &m_texture_id);
447 m_dirty_texture = false;
448 m_dirty_bind_options = true;
450 m_mipmaps_generated = false;
453 void QSGPlainTexture::setHasMipmaps(bool mm)
456 m_mipmaps_generated = false;
460 void QSGPlainTexture::bind()
462 if (!m_dirty_texture) {
463 glBindTexture(GL_TEXTURE_2D, m_texture_id);
464 if (m_has_mipmaps && !m_mipmaps_generated) {
465 QOpenGLContext *ctx = QOpenGLContext::currentContext();
466 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
467 m_mipmaps_generated = true;
469 updateBindOptions(m_dirty_bind_options);
470 m_dirty_bind_options = false;
474 m_dirty_texture = false;
477 if (m_image.isNull()) {
478 if (m_texture_id && m_owns_texture)
479 glDeleteTextures(1, &m_texture_id);
481 m_texture_size = QSize();
482 m_has_mipmaps = false;
487 if (m_texture_id == 0)
488 glGenTextures(1, &m_texture_id);
489 glBindTexture(GL_TEXTURE_2D, m_texture_id);
491 // ### TODO: check for out-of-memory situations...
492 int w = m_image.width();
493 int h = m_image.height();
495 QImage tmp = (m_image.format() == QImage::Format_RGB32 || m_image.format() == QImage::Format_ARGB32_Premultiplied)
497 : m_image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
500 swizzleBGRAToRGBA(&tmp);
501 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp.constBits());
503 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, tmp.constBits());
507 QOpenGLContext *ctx = QOpenGLContext::currentContext();
508 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
509 m_mipmaps_generated = true;
512 m_texture_size = QSize(w, h);
513 m_texture_rect = QRectF(0, 0, 1, 1);
515 updateBindOptions(m_dirty_bind_options);
516 m_dirty_bind_options = false;
521 \class QSGDynamicTexture
522 \brief The QSGDynamicTexture class serves as a baseclass for dynamically changing textures,
523 such as content that is rendered to FBO's.
525 To update the content of the texture, call updateTexture() explicitly. Simply calling bind()
526 will not update the texture.
531 \fn bool QSGDynamicTexture::updateTexture()
533 Call this function to explicitely update the dynamic texture. Calling bind() will bind
534 the content that was previously updated.
536 The function returns true if the texture was changed as a resul of the update; otherwise