1 /****************************************************************************
3 ** Copyright (C) 2010 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)
418 m_image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
420 swizzleBGRAToRGBA(&m_image);
423 m_texture_size = image.size();
424 m_has_alpha = image.hasAlphaChannel();
425 m_dirty_texture = true;
426 m_dirty_bind_options = true;
429 int QSGPlainTexture::textureId() const
431 if (m_dirty_texture) {
432 if (m_image.isNull()) {
433 // The actual texture and id will be updated/deleted in a later bind()
434 // or ~QSGPlainTexture so just keep it minimal here.
437 // Generate a texture id for use later and return it.
438 glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id);
445 void QSGPlainTexture::setTextureId(int id)
447 if (m_texture_id && m_owns_texture)
448 glDeleteTextures(1, &m_texture_id);
451 m_dirty_texture = false;
452 m_dirty_bind_options = true;
454 m_mipmaps_generated = false;
457 void QSGPlainTexture::setHasMipmaps(bool mm)
460 m_mipmaps_generated = false;
464 void QSGPlainTexture::bind()
466 if (!m_dirty_texture) {
467 glBindTexture(GL_TEXTURE_2D, m_texture_id);
468 if (m_has_mipmaps && !m_mipmaps_generated) {
469 QOpenGLContext *ctx = QOpenGLContext::currentContext();
470 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
471 m_mipmaps_generated = true;
473 updateBindOptions(m_dirty_bind_options);
474 m_dirty_bind_options = false;
478 m_dirty_texture = false;
481 if (m_image.isNull()) {
482 if (m_texture_id && m_owns_texture)
483 glDeleteTextures(1, &m_texture_id);
485 m_texture_size = QSize();
486 m_has_mipmaps = false;
491 if (m_texture_id == 0)
492 glGenTextures(1, &m_texture_id);
493 glBindTexture(GL_TEXTURE_2D, m_texture_id);
495 // ### TODO: check for out-of-memory situations...
496 int w = m_image.width();
497 int h = m_image.height();
500 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_image.constBits());
502 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, m_image.constBits());
506 QOpenGLContext *ctx = QOpenGLContext::currentContext();
507 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
508 m_mipmaps_generated = true;
511 m_texture_size = QSize(w, h);
512 m_texture_rect = QRectF(0, 0, 1, 1);
514 updateBindOptions(m_dirty_bind_options);
515 m_dirty_bind_options = false;
520 \class QSGDynamicTexture
521 \brief The QSGDynamicTexture class serves as a baseclass for dynamically changing textures,
522 such as content that is rendered to FBO's.
524 To update the content of the texture, call updateTexture() explicitly. Simply calling bind()
525 will not update the texture.
530 \fn bool QSGDynamicTexture::updateTexture()
532 Call this function to explicitely update the dynamic texture. Calling bind() will bind
533 the content that was previously updated.
535 The function returns true if the texture was changed as a resul of the update; otherwise