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(Q_OS_LINUX) && !defined(Q_OS_LINUX_ANDROID)
52 #define CAN_BACKTRACE_EXECINFO
56 #define CAN_BACKTRACE_EXECINFO
59 #if defined(QT_NO_DEBUG)
60 #undef CAN_BACKTRACE_EXECINFO
63 #if defined(CAN_BACKTRACE_EXECINFO)
70 inline static bool isPowerOfTwo(int x)
76 QSGTexturePrivate::QSGTexturePrivate()
78 , filteringChanged(false)
79 , horizontalWrap(QSGTexture::ClampToEdge)
80 , verticalWrap(QSGTexture::ClampToEdge)
81 , mipmapMode(QSGTexture::None)
82 , filterMode(QSGTexture::Nearest)
88 static int qt_debug_texture_count = 0;
90 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
91 DEFINE_BOOL_CONFIG_OPTION(qmlDebugLeakBacktrace, QML_DEBUG_LEAK_BACKTRACE)
93 #define BACKTRACE_SIZE 20
94 class SGTextureTraceItem
97 void *backTrace[BACKTRACE_SIZE];
101 static QHash<QSGTexture*, SGTextureTraceItem*> qt_debug_allocated_textures;
104 inline static void qt_debug_print_texture_count()
106 qDebug("Number of leaked textures: %i", qt_debug_texture_count);
107 qt_debug_texture_count = -1;
109 #if defined(CAN_BACKTRACE_EXECINFO)
110 if (qmlDebugLeakBacktrace()) {
111 while (!qt_debug_allocated_textures.isEmpty()) {
112 QHash<QSGTexture*, SGTextureTraceItem*>::Iterator it = qt_debug_allocated_textures.begin();
113 QSGTexture* texture = it.key();
114 SGTextureTraceItem* item = it.value();
116 qt_debug_allocated_textures.erase(it);
118 qDebug() << "------";
119 qDebug() << "Leaked" << texture << "backtrace:";
121 char** symbols = backtrace_symbols(item->backTrace, item->backTraceSize);
124 for (int i=0; i<(int) item->backTraceSize; i++)
125 qDebug("Backtrace <%02d>: %s", i, symbols[i]);
129 qDebug() << "------";
137 inline static void qt_debug_add_texture(QSGTexture* texture)
139 #if defined(CAN_BACKTRACE_EXECINFO)
140 if (qmlDebugLeakBacktrace()) {
141 SGTextureTraceItem* item = new SGTextureTraceItem;
142 item->backTraceSize = backtrace(item->backTrace, BACKTRACE_SIZE);
143 qt_debug_allocated_textures.insert(texture, item);
149 ++qt_debug_texture_count;
151 static bool atexit_registered = false;
152 if (!atexit_registered) {
153 atexit(qt_debug_print_texture_count);
154 atexit_registered = true;
158 static void qt_debug_remove_texture(QSGTexture* texture)
160 #if defined(CAN_BACKTRACE_EXECINFO)
161 if (qmlDebugLeakBacktrace()) {
162 SGTextureTraceItem* item = qt_debug_allocated_textures.value(texture, 0);
164 qt_debug_allocated_textures.remove(texture);
172 --qt_debug_texture_count;
174 if (qt_debug_texture_count < 0)
175 qDebug("Material destroyed after qt_debug_print_texture_count() was called.");
178 #endif // QT_NO_DEBUG
185 \brief The QSGTexture class is a baseclass for textures used in
189 Users can freely implement their own texture classes to support
190 arbitrary input textures, such as YUV video frames or 8 bit alpha
191 masks. The scene graph backend provides a default implementation
192 of normal color textures. As the implementation of these may be
193 hardware specific, they are are constructed via the factory
194 function QQuickWindow::createTextureFromImage().
196 The texture is a wrapper around an OpenGL texture, which texture
197 id is given by textureId() and which size in pixels is given by
198 textureSize(). hasAlphaChannel() reports if the texture contains
199 opacity values and hasMipmaps() reports if the texture contains
202 To use a texture, call the bind() function. The texture parameters
203 specifying how the texture is bound, can be specified with
204 setMipmapFiltering(), setFiltering(), setHorizontalWrapMode() and
205 setVerticalWrapMode(). The texture will internally try to store
206 these values to minimize the OpenGL state changes when the texture
209 \section1 Texture Atlasses
211 Some scene graph backens use texture atlasses, grouping multiple
212 small textures into one large texture. If this is the case, the
213 function isAtlasTexture() will return true. Atlasses are used to
214 aid the rendering algorithm to do better sorting which increases
215 performance. The location of the texture inside the atlas is
216 given with the normalizedTextureSubRect() function.
218 If the texture is used in such a way that atlas is not preferable,
219 the function removedFromAtlas() can be used to extract a
224 \enum QSGTexture::WrapMode
226 Specifies how the texture should treat texture coordinates.
228 \value Repeat Only the factional part of the texture coordiante is
229 used, causing values above 1 and below 0 to repeat.
231 \value ClampToEdge Values above 1 are clamped to 1 and values
232 below 0 are clamped to 0.
236 \enum QSGTexture::Filtering
238 Specifies how sampling of texels should filter when texture
239 coordinates are not pixel aligned.
241 \value None No filtering should occur. This value is only used
242 together with setMipmapFiltering().
244 \value Nearest Sampling returns the nearest texel.
246 \value Linear Sampling returns a linear interpolation of the
250 QSGTexture::QSGTexture()
251 : QObject(*(new QSGTexturePrivate))
254 qt_debug_add_texture(this);
258 QSGTexture::~QSGTexture()
261 qt_debug_remove_texture(this);
267 \fn void QSGTexture::bind()
269 Call this function to bind this texture to the current texture
272 Binding a texture may also include uploading the texture data from
273 a previously set QImage.
275 \warning This function can only be called from the rendering thread.
279 This function returns a copy of the current texture which is removed
282 The current texture remains unchanged, so texture coordinates do not
285 Removing a texture from an atlas is primarily useful when passing
286 it to a shader that operates on the texture coordinates 0-1 instead
287 of the texture subrect inside the atlas.
289 If the texture is not part of a texture atlas, this function returns 0.
291 Implementations of this function are recommended to return the same instance
292 for multiple calls to limit memory usage.
294 \warning This function can only be called from the rendering thread.
297 QSGTexture *QSGTexture::removedFromAtlas() const
299 Q_ASSERT_X(!isAtlasTexture(), "QSGTexture::removedFromAtlas()", "Called on a non-atlas texture");
304 Returns weither this texture is part of an atlas or not.
306 The default implementation returns false.
308 bool QSGTexture::isAtlasTexture() const
314 \fn int QSGTexture::textureId() const
316 Returns the OpenGL texture id for this texture.
318 The default value is 0, indicating that it is an invalid texture id.
320 The function should at all times return the correct texture id.
322 \warning This function can only be called from the rendering thread.
328 Returns the rectangle inside textureSize() that this texture
329 represents in normalized coordinates.
331 The default implementation returns a rect at position (0, 0) with
332 width and height of 1.
334 QRectF QSGTexture::normalizedTextureSubRect() const
336 return QRectF(0, 0, 1, 1);
340 \fn bool QSGTexture::hasMipmaps() const
342 Returns true if the texture data contains mipmap levels.
347 Sets the mipmap sampling mode to be used for the upcoming bind() call to \a filter.
349 Setting the mipmap filtering has no effect it the texture does not have mipmaps.
353 void QSGTexture::setMipmapFiltering(Filtering filter)
356 if (d->mipmapMode != (uint) filter) {
357 d->mipmapMode = filter;
358 d->filteringChanged = true;
363 Returns whether mipmapping should be used when sampling from this texture.
365 QSGTexture::Filtering QSGTexture::mipmapFiltering() const
367 return (QSGTexture::Filtering) d_func()->mipmapMode;
372 Sets the sampling mode to be used for the upcoming bind() call to \a filter.
374 void QSGTexture::setFiltering(QSGTexture::Filtering filter)
377 if (d->filterMode != (uint) filter) {
378 d->filterMode = filter;
379 d->filteringChanged = true;
383 QSGTexture::Filtering QSGTexture::filtering() const
385 return (QSGTexture::Filtering) d_func()->filterMode;
391 Sets the horizontal wrap mode to be used for the upcoming bind() call to \a hwrap
394 void QSGTexture::setHorizontalWrapMode(WrapMode hwrap)
397 if ((uint) hwrap != d->horizontalWrap) {
398 d->horizontalWrap = hwrap;
399 d->wrapChanged = true;
403 QSGTexture::WrapMode QSGTexture::horizontalWrapMode() const
405 return (QSGTexture::WrapMode) d_func()->horizontalWrap;
410 void QSGTexture::setVerticalWrapMode(WrapMode vwrap)
413 if ((uint) vwrap != d->verticalWrap) {
414 d->verticalWrap = vwrap;
415 d->wrapChanged = true;
419 QSGTexture::WrapMode QSGTexture::verticalWrapMode() const
421 return (QSGTexture::WrapMode) d_func()->verticalWrap;
426 Update the texture state to match the filtering, mipmap and wrap options
429 If \a force is true, all properties will be updated regardless of weither
430 they have changed or not.
432 void QSGTexture::updateBindOptions(bool force)
435 if (force || d->filteringChanged) {
436 bool linear = d->filterMode == Linear;
437 GLint minFilter = linear ? GL_LINEAR : GL_NEAREST;
438 GLint magFilter = linear ? GL_LINEAR : GL_NEAREST;
441 if (d->mipmapMode == Nearest)
442 minFilter = linear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST;
443 else if (d->mipmapMode == Linear)
444 minFilter = linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR;
446 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
447 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
448 d->filteringChanged = false;
451 if (force || d->wrapChanged) {
452 #if !defined(QT_NO_DEBUG) && defined(QT_OPENGL_ES_2)
453 if (d->horizontalWrap == Repeat || d->verticalWrap == Repeat) {
454 bool npotSupported = QOpenGLFunctions(QOpenGLContext::currentContext()).hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
455 QSize size = textureSize();
456 bool isNpot = !isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height());
457 if (!npotSupported && isNpot)
458 qWarning("Scene Graph: This system does not support the REPEAT wrap mode for non-power-of-two textures.");
461 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, d->horizontalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
462 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, d->verticalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
463 d->wrapChanged = false;
467 QSGPlainTexture::QSGPlainTexture()
471 , m_has_mipmaps(false)
472 , m_dirty_bind_options(false)
473 , m_owns_texture(true)
474 , m_mipmaps_generated(false)
479 QSGPlainTexture::~QSGPlainTexture()
481 if (m_texture_id && m_owns_texture)
482 glDeleteTextures(1, &m_texture_id);
486 void qsg_swizzleBGRAToRGBA(QImage *image)
488 const int width = image->width();
489 const int height = image->height();
490 for (int i = 0; i < height; ++i) {
491 uint *p = (uint *) image->scanLine(i);
492 for (int x = 0; x < width; ++x)
493 p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
498 void QSGPlainTexture::setImage(const QImage &image)
501 m_texture_size = image.size();
502 m_has_alpha = image.hasAlphaChannel();
503 m_dirty_texture = true;
504 m_dirty_bind_options = true;
507 int QSGPlainTexture::textureId() const
509 if (m_dirty_texture) {
510 if (m_image.isNull()) {
511 // The actual texture and id will be updated/deleted in a later bind()
512 // or ~QSGPlainTexture so just keep it minimal here.
514 } else if (m_texture_id == 0){
515 // Generate a texture id for use later and return it.
516 glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id);
523 void QSGPlainTexture::setTextureId(int id)
525 if (m_texture_id && m_owns_texture)
526 glDeleteTextures(1, &m_texture_id);
529 m_dirty_texture = false;
530 m_dirty_bind_options = true;
532 m_mipmaps_generated = false;
535 void QSGPlainTexture::setHasMipmaps(bool mm)
538 m_mipmaps_generated = false;
542 void QSGPlainTexture::bind()
544 if (!m_dirty_texture) {
545 glBindTexture(GL_TEXTURE_2D, m_texture_id);
546 if (m_has_mipmaps && !m_mipmaps_generated) {
547 QOpenGLContext *ctx = QOpenGLContext::currentContext();
548 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
549 m_mipmaps_generated = true;
551 updateBindOptions(m_dirty_bind_options);
552 m_dirty_bind_options = false;
556 m_dirty_texture = false;
559 if (m_image.isNull()) {
560 if (m_texture_id && m_owns_texture)
561 glDeleteTextures(1, &m_texture_id);
563 m_texture_size = QSize();
564 m_has_mipmaps = false;
569 if (m_texture_id == 0)
570 glGenTextures(1, &m_texture_id);
571 glBindTexture(GL_TEXTURE_2D, m_texture_id);
573 // ### TODO: check for out-of-memory situations...
574 int w = m_image.width();
575 int h = m_image.height();
577 QImage tmp = (m_image.format() == QImage::Format_RGB32 || m_image.format() == QImage::Format_ARGB32_Premultiplied)
579 : m_image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
581 updateBindOptions(m_dirty_bind_options);
584 qsg_swizzleBGRAToRGBA(&tmp);
585 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp.constBits());
587 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, tmp.constBits());
591 QOpenGLContext *ctx = QOpenGLContext::currentContext();
592 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
593 m_mipmaps_generated = true;
596 m_texture_size = QSize(w, h);
597 m_texture_rect = QRectF(0, 0, 1, 1);
599 m_dirty_bind_options = false;
604 \class QSGDynamicTexture
605 \brief The QSGDynamicTexture class serves as a baseclass for dynamically changing textures,
606 such as content that is rendered to FBO's.
609 To update the content of the texture, call updateTexture() explicitly. Simply calling bind()
610 will not update the texture.
615 \fn bool QSGDynamicTexture::updateTexture()
617 Call this function to explicitely update the dynamic texture. Calling bind() will bind
618 the content that was previously updated.
620 The function returns true if the texture was changed as a resul of the update; otherwise