Properly swizzle texture data before upload in QQuickPaintedItem.
[profile/ivi/qtdeclarative.git] / src / quick / scenegraph / util / qsgtexture.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #define GL_GLEXT_PROTOTYPES
43
44 #include "qsgtexture_p.h"
45 #include <qopenglfunctions.h>
46 #include <QtQuick/private/qsgcontext_p.h>
47 #include <qthread.h>
48 #include <private/qqmlprofilerservice_p.h>
49 #include <private/qqmlglobal_p.h>
50
51 #if !defined(QT_NO_DEBUG) && (defined(Q_OS_LINUX) || defined(Q_OS_MAC))
52 #include <execinfo.h>
53 #include <QHash>
54 #endif
55
56 QT_BEGIN_NAMESPACE
57
58 inline static bool isPowerOfTwo(int x)
59 {
60     // Assumption: x >= 1
61     return x == (x & -x);
62 }
63
64 QSGTexturePrivate::QSGTexturePrivate()
65     : wrapChanged(false)
66     , filteringChanged(false)
67     , horizontalWrap(QSGTexture::ClampToEdge)
68     , verticalWrap(QSGTexture::ClampToEdge)
69     , mipmapMode(QSGTexture::None)
70     , filterMode(QSGTexture::Nearest)
71 {
72 }
73
74 #ifndef QT_NO_DEBUG
75
76 static int qt_debug_texture_count = 0;
77
78 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
79 DEFINE_BOOL_CONFIG_OPTION(qmlDebugLeakBacktrace, QML_DEBUG_LEAK_BACKTRACE)
80
81 #define BACKTRACE_SIZE 20
82 class SGTextureTraceItem
83 {
84 public:
85     void *backTrace[BACKTRACE_SIZE];
86     size_t backTraceSize;
87 };
88
89 static QHash<QSGTexture*, SGTextureTraceItem*> qt_debug_allocated_textures;
90 #endif
91
92 inline static void qt_debug_print_texture_count()
93 {
94     qDebug("Number of leaked textures: %i", qt_debug_texture_count);
95     qt_debug_texture_count = -1;
96
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();
103
104             qt_debug_allocated_textures.erase(it);
105
106             qDebug() << "------";
107             qDebug() << "Leaked" << texture << "backtrace:";
108
109             char** symbols = backtrace_symbols(item->backTrace, item->backTraceSize);
110
111             if (symbols) {
112                 for (int i=0; i<(int) item->backTraceSize; i++)
113                     qDebug("Backtrace <%02d>: %s", i, symbols[i]);
114                 free(symbols);
115             }
116
117             qDebug() << "------";
118
119             delete item;
120         }
121     }
122 #endif
123 }
124
125 inline static void qt_debug_add_texture(QSGTexture* texture)
126 {
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);
132     }
133 #else
134     Q_UNUSED(texture);
135 #endif // Q_OS_LINUX
136
137     ++qt_debug_texture_count;
138
139     static bool atexit_registered = false;
140     if (!atexit_registered) {
141         atexit(qt_debug_print_texture_count);
142         atexit_registered = true;
143     }
144 }
145
146 static void qt_debug_remove_texture(QSGTexture* texture)
147 {
148 #if defined(Q_OS_LINUX) || defined (Q_OS_MAC)
149     if (qmlDebugLeakBacktrace()) {
150         SGTextureTraceItem* item = qt_debug_allocated_textures.value(texture, 0);
151         if (item) {
152             qt_debug_allocated_textures.remove(texture);
153             delete item;
154         }
155     }
156 #else
157     Q_UNUSED(texture)
158 #endif
159
160     --qt_debug_texture_count;
161
162     if (qt_debug_texture_count < 0)
163         qDebug("Material destroyed after qt_debug_print_texture_count() was called.");
164 }
165
166 #endif // QT_NO_DEBUG
167
168
169 QSGTexture::QSGTexture()
170     : QObject(*(new QSGTexturePrivate))
171 {
172 #ifndef QT_NO_DEBUG
173     qt_debug_add_texture(this);
174 #endif
175 }
176
177 QSGTexture::~QSGTexture()
178 {
179 #ifndef QT_NO_DEBUG
180     qt_debug_remove_texture(this);
181 #endif
182 }
183
184
185 /*!
186     \fn void QSGTexture::bind()
187
188     Call this function to bind this texture to the current texture
189     target.
190
191     Binding a texture may also include uploading the texture data from
192     a previously set QImage.
193
194     \warning This function can only be called from the rendering thread.
195  */
196
197 /*!
198     This function returns a copy of the current texture which is removed
199     from its atlas.
200
201     The current texture remains unchanged, so texture coordinates do not
202     need to be updated.
203
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.
207
208     If the texture is not part of a texture atlas, this function returns 0.
209
210     Implementations of this function are recommended to return the same instance
211     for multiple calls to limit memory usage.
212
213     \warning This function can only be called from the rendering thread.
214  */
215
216 QSGTexture *QSGTexture::removedFromAtlas() const
217 {
218     Q_ASSERT_X(!isAtlasTexture(), "QSGTexture::removedFromAtlas()", "Called on a non-atlas texture");
219     return 0;
220 }
221
222 /*!
223     Returns weither this texture is part of an atlas or not.
224
225     The default implementation returns false.
226  */
227 bool QSGTexture::isAtlasTexture() const
228 {
229     return false;
230 }
231
232 /*!
233     \fn int QSGTexture::textureId() const
234
235     Returns the OpenGL texture id for this texture.
236
237     The default value is 0, indicating that it is an invalid texture id.
238
239     The function should at all times return the correct texture id.
240
241     \warning This function can only be called from the rendering thread.
242  */
243
244
245
246 /*!
247     Returns the rectangle inside textureSize() that this texture
248     represents in normalized coordinates.
249
250     The default implementation returns a rect at position (0, 0) with
251     width and height of 1.
252  */
253 QRectF QSGTexture::normalizedTextureSubRect() const
254 {
255     return QRectF(0, 0, 1, 1);
256 }
257
258 /*!
259     \fn bool QSGTexture::hasMipmaps() const
260
261     Returns true if the texture data contains mipmap levels.
262  */
263
264
265 /*!
266     Sets the mipmap sampling mode to be used for the upcoming bind() call to \a filter.
267
268     Setting the mipmap filtering has no effect it the texture does not have mipmaps.
269
270     \sa hasMipmaps()
271  */
272 void QSGTexture::setMipmapFiltering(Filtering filter)
273 {
274     Q_D(QSGTexture);
275     if (d->mipmapMode != (uint) filter) {
276         d->mipmapMode = filter;
277         d->filteringChanged = true;
278     }
279 }
280
281 /*!
282     Returns whether mipmapping should be used when sampling from this texture.
283  */
284 QSGTexture::Filtering QSGTexture::mipmapFiltering() const
285 {
286     return (QSGTexture::Filtering) d_func()->mipmapMode;
287 }
288
289
290 /*!
291     Sets the sampling mode to be used for the upcoming bind() call to \a filter.
292  */
293 void QSGTexture::setFiltering(QSGTexture::Filtering filter)
294 {
295     Q_D(QSGTexture);
296     if (d->filterMode != (uint) filter) {
297         d->filterMode = filter;
298         d->filteringChanged = true;
299     }
300 }
301
302 QSGTexture::Filtering QSGTexture::filtering() const
303 {
304     return (QSGTexture::Filtering) d_func()->filterMode;
305 }
306
307
308
309 /*!
310     Sets the horizontal wrap mode to be used for the upcoming bind() call to \a hwrap
311  */
312
313 void QSGTexture::setHorizontalWrapMode(WrapMode hwrap)
314 {
315     Q_D(QSGTexture);
316     if ((uint) hwrap != d->horizontalWrap) {
317         d->horizontalWrap = hwrap;
318         d->wrapChanged = true;
319     }
320 }
321
322 QSGTexture::WrapMode QSGTexture::horizontalWrapMode() const
323 {
324     return (QSGTexture::WrapMode) d_func()->horizontalWrap;
325 }
326
327
328
329 void QSGTexture::setVerticalWrapMode(WrapMode vwrap)
330 {
331     Q_D(QSGTexture);
332     if ((uint) vwrap != d->verticalWrap) {
333         d->verticalWrap = vwrap;
334         d->wrapChanged = true;
335     }
336 }
337
338 QSGTexture::WrapMode QSGTexture::verticalWrapMode() const
339 {
340     return (QSGTexture::WrapMode) d_func()->verticalWrap;
341 }
342
343
344 /*!
345     Update the texture state to match the filtering, mipmap and wrap options
346     currently set.
347
348     If \a force is true, all properties will be updated regardless of weither
349     they have changed or not.
350  */
351 void QSGTexture::updateBindOptions(bool force)
352 {
353     Q_D(QSGTexture);
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;
358
359         if (hasMipmaps()) {
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;
364         }
365         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
366         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
367         d->filteringChanged = false;
368     }
369
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.");
378         }
379 #endif
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;
383     }
384 }
385
386 QSGPlainTexture::QSGPlainTexture()
387     : QSGTexture()
388     , m_texture_id(0)
389     , m_has_alpha(false)
390     , m_has_mipmaps(false)
391     , m_dirty_bind_options(false)
392     , m_owns_texture(true)
393     , m_mipmaps_generated(false)
394 {
395 }
396
397
398 QSGPlainTexture::~QSGPlainTexture()
399 {
400     if (m_texture_id && m_owns_texture)
401         glDeleteTextures(1, &m_texture_id);
402 }
403
404 #ifdef QT_OPENGL_ES
405 void qsg_swizzleBGRAToRGBA(QImage *image)
406 {
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);
413     }
414 }
415 #endif
416
417 void QSGPlainTexture::setImage(const QImage &image)
418 {
419     m_image = image;
420     m_texture_size = image.size();
421     m_has_alpha = image.hasAlphaChannel();
422     m_dirty_texture = true;
423     m_dirty_bind_options = true;
424  }
425
426 int QSGPlainTexture::textureId() const
427 {
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.
432             return 0;
433         } else {
434             // Generate a texture id for use later and return it.
435             glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id);
436             return m_texture_id;
437         }
438     }
439     return m_texture_id;
440 }
441
442 void QSGPlainTexture::setTextureId(int id)
443 {
444     if (m_texture_id && m_owns_texture)
445         glDeleteTextures(1, &m_texture_id);
446
447     m_texture_id = id;
448     m_dirty_texture = false;
449     m_dirty_bind_options = true;
450     m_image = QImage();
451     m_mipmaps_generated = false;
452 }
453
454 void QSGPlainTexture::setHasMipmaps(bool mm)
455 {
456     m_has_mipmaps = mm;
457     m_mipmaps_generated = false;
458 }
459
460
461 void QSGPlainTexture::bind()
462 {
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;
469         }
470         updateBindOptions(m_dirty_bind_options);
471         m_dirty_bind_options = false;
472         return;
473     }
474
475     m_dirty_texture = false;
476
477
478     if (m_image.isNull()) {
479         if (m_texture_id && m_owns_texture)
480             glDeleteTextures(1, &m_texture_id);
481         m_texture_id = 0;
482         m_texture_size = QSize();
483         m_has_mipmaps = false;
484         m_has_alpha = false;
485         return;
486     }
487
488     if (m_texture_id == 0)
489         glGenTextures(1, &m_texture_id);
490     glBindTexture(GL_TEXTURE_2D, m_texture_id);
491
492     // ### TODO: check for out-of-memory situations...
493     int w = m_image.width();
494     int h = m_image.height();
495
496     QImage tmp = (m_image.format() == QImage::Format_RGB32 || m_image.format() == QImage::Format_ARGB32_Premultiplied)
497                  ? m_image
498                  : m_image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
499
500     updateBindOptions(m_dirty_bind_options);
501
502 #ifdef QT_OPENGL_ES
503         qsg_swizzleBGRAToRGBA(&tmp);
504         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp.constBits());
505 #else
506         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, tmp.constBits());
507 #endif
508
509     if (m_has_mipmaps) {
510         QOpenGLContext *ctx = QOpenGLContext::currentContext();
511         ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
512         m_mipmaps_generated = true;
513     }
514
515     m_texture_size = QSize(w, h);
516     m_texture_rect = QRectF(0, 0, 1, 1);
517
518     m_dirty_bind_options = false;
519 }
520
521
522 /*!
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.
526
527     To update the content of the texture, call updateTexture() explicitly. Simply calling bind()
528     will not update the texture.
529  */
530
531
532 /*!
533     \fn bool QSGDynamicTexture::updateTexture()
534
535     Call this function to explicitely update the dynamic texture. Calling bind() will bind
536     the content that was previously updated.
537
538     The function returns true if the texture was changed as a resul of the update; otherwise
539     returns false.
540  */
541
542
543
544 QT_END_NAMESPACE