1
/****************************************************************************
3
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of the QtQml module of the Qt Toolkit.
8
** $QT_BEGIN_LICENSE:LGPL$
9
** Commercial License Usage
10
** Licensees holding valid commercial Qt licenses may use this file in
11
** accordance with the commercial license agreement provided with the
12
** Software or, alternatively, in accordance with the terms contained in
13
** a written agreement between you and Digia. For licensing terms and
14
** conditions see http://qt.digia.com/licensing. For further information
15
** use the contact form at http://qt.digia.com/contact-us.
17
** GNU Lesser General Public License Usage
18
** Alternatively, this file may be used under the terms of the GNU Lesser
19
** General Public License version 2.1 as published by the Free Software
20
** Foundation and appearing in the file LICENSE.LGPL included in the
21
** packaging of this file. Please review the following information to
22
** ensure the GNU Lesser General Public License version 2.1 requirements
23
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25
** In addition, as a special exception, Digia gives you certain additional
26
** rights. These rights are described in the Digia Qt LGPL Exception
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29
** GNU General Public License Usage
30
** Alternatively, this file may be used under the terms of the GNU
31
** General Public License version 3.0 as published by the Free Software
32
** Foundation and appearing in the file LICENSE.GPL included in the
33
** packaging of this file. Please review the following information to
34
** ensure the GNU General Public License version 3.0 requirements will be
35
** met: http://www.gnu.org/copyleft/gpl.html.
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
251
\fn QSGTexture::QSGTexture(QSGTexturePrivate &dd)
256
Constructs the QSGTexture base class.
258
QSGTexture::QSGTexture()
259
: QObject(*(new QSGTexturePrivate))
262
qt_debug_add_texture(this);
267
Destroys the QSGTexture.
269
QSGTexture::~QSGTexture()
272
qt_debug_remove_texture(this);
278
\fn void QSGTexture::bind()
280
Call this function to bind this texture to the current texture
283
Binding a texture may also include uploading the texture data from
284
a previously set QImage.
286
\warning This function can only be called from the rendering thread.
290
\fn QRectF QSGTexture::convertToNormalizedSourceRect(const QRectF &rect) const
292
Returns \a rect converted to normalized coordinates.
294
\sa normalizedTextureSubRect()
298
This function returns a copy of the current texture which is removed
301
The current texture remains unchanged, so texture coordinates do not
304
Removing a texture from an atlas is primarily useful when passing
305
it to a shader that operates on the texture coordinates 0-1 instead
306
of the texture subrect inside the atlas.
308
If the texture is not part of a texture atlas, this function returns 0.
310
Implementations of this function are recommended to return the same instance
311
for multiple calls to limit memory usage.
313
\warning This function can only be called from the rendering thread.
316
QSGTexture *QSGTexture::removedFromAtlas() const
318
Q_ASSERT_X(!isAtlasTexture(), "QSGTexture::removedFromAtlas()", "Called on a non-atlas texture");
323
Returns weither this texture is part of an atlas or not.
325
The default implementation returns false.
327
bool QSGTexture::isAtlasTexture() const
333
\fn int QSGTexture::textureId() const
335
Returns the OpenGL texture id for this texture.
337
The default value is 0, indicating that it is an invalid texture id.
339
The function should at all times return the correct texture id.
341
\warning This function can only be called from the rendering thread.
345
\fn QSize QSGTexture::textureSize() const
347
Returns the size of the texture.
351
Returns the rectangle inside textureSize() that this texture
352
represents in normalized coordinates.
354
The default implementation returns a rect at position (0, 0) with
355
width and height of 1.
357
QRectF QSGTexture::normalizedTextureSubRect() const
359
return QRectF(0, 0, 1, 1);
363
\fn bool QSGTexture::hasAlphaChannel() const
365
Returns true if the texture data contains an alpha channel.
369
\fn bool QSGTexture::hasMipmaps() const
371
Returns true if the texture data contains mipmap levels.
376
Sets the mipmap sampling mode to be used for the upcoming bind() call to \a filter.
378
Setting the mipmap filtering has no effect it the texture does not have mipmaps.
382
void QSGTexture::setMipmapFiltering(Filtering filter)
385
if (d->mipmapMode != (uint) filter) {
386
d->mipmapMode = filter;
387
d->filteringChanged = true;
392
Returns whether mipmapping should be used when sampling from this texture.
394
QSGTexture::Filtering QSGTexture::mipmapFiltering() const
396
return (QSGTexture::Filtering) d_func()->mipmapMode;
401
Sets the sampling mode to be used for the upcoming bind() call to \a filter.
403
void QSGTexture::setFiltering(QSGTexture::Filtering filter)
406
if (d->filterMode != (uint) filter) {
407
d->filterMode = filter;
408
d->filteringChanged = true;
413
Returns the sampling mode to be used for this texture.
415
QSGTexture::Filtering QSGTexture::filtering() const
417
return (QSGTexture::Filtering) d_func()->filterMode;
423
Sets the horizontal wrap mode to be used for the upcoming bind() call to \a hwrap
426
void QSGTexture::setHorizontalWrapMode(WrapMode hwrap)
429
if ((uint) hwrap != d->horizontalWrap) {
430
d->horizontalWrap = hwrap;
431
d->wrapChanged = true;
436
Returns the horizontal wrap mode to be used for this texture.
438
QSGTexture::WrapMode QSGTexture::horizontalWrapMode() const
440
return (QSGTexture::WrapMode) d_func()->horizontalWrap;
446
Sets the vertical wrap mode to be used for the upcoming bind() call to \a vwrap
448
void QSGTexture::setVerticalWrapMode(WrapMode vwrap)
451
if ((uint) vwrap != d->verticalWrap) {
452
d->verticalWrap = vwrap;
453
d->wrapChanged = true;
458
Returns the vertical wrap mode to be used for this texture.
460
QSGTexture::WrapMode QSGTexture::verticalWrapMode() const
462
return (QSGTexture::WrapMode) d_func()->verticalWrap;
467
Update the texture state to match the filtering, mipmap and wrap options
470
If \a force is true, all properties will be updated regardless of weither
471
they have changed or not.
473
void QSGTexture::updateBindOptions(bool force)
476
if (force || d->filteringChanged) {
477
bool linear = d->filterMode == Linear;
478
GLint minFilter = linear ? GL_LINEAR : GL_NEAREST;
479
GLint magFilter = linear ? GL_LINEAR : GL_NEAREST;
482
if (d->mipmapMode == Nearest)
483
minFilter = linear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST;
484
else if (d->mipmapMode == Linear)
485
minFilter = linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR;
487
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
488
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
489
d->filteringChanged = false;
492
if (force || d->wrapChanged) {
493
#if !defined(QT_NO_DEBUG) && defined(QT_OPENGL_ES_2)
494
if (d->horizontalWrap == Repeat || d->verticalWrap == Repeat) {
495
bool npotSupported = QOpenGLFunctions(QOpenGLContext::currentContext()).hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
496
QSize size = textureSize();
497
bool isNpot = !isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height());
498
if (!npotSupported && isNpot)
499
qWarning("Scene Graph: This system does not support the REPEAT wrap mode for non-power-of-two textures.");
502
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, d->horizontalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
503
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, d->verticalWrap == Repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
504
d->wrapChanged = false;
508
QSGPlainTexture::QSGPlainTexture()
512
, m_has_mipmaps(false)
513
, m_dirty_bind_options(false)
514
, m_owns_texture(true)
515
, m_mipmaps_generated(false)
520
QSGPlainTexture::~QSGPlainTexture()
522
if (m_texture_id && m_owns_texture)
523
glDeleteTextures(1, &m_texture_id);
527
void qsg_swizzleBGRAToRGBA(QImage *image)
529
const int width = image->width();
530
const int height = image->height();
531
for (int i = 0; i < height; ++i) {
532
uint *p = (uint *) image->scanLine(i);
533
for (int x = 0; x < width; ++x)
534
p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
539
void QSGPlainTexture::setImage(const QImage &image)
542
m_texture_size = image.size();
543
m_has_alpha = image.hasAlphaChannel();
544
m_dirty_texture = true;
545
m_dirty_bind_options = true;
548
int QSGPlainTexture::textureId() const
550
if (m_dirty_texture) {
551
if (m_image.isNull()) {
552
// The actual texture and id will be updated/deleted in a later bind()
553
// or ~QSGPlainTexture so just keep it minimal here.
555
} else if (m_texture_id == 0){
556
// Generate a texture id for use later and return it.
557
glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id);
564
void QSGPlainTexture::setTextureId(int id)
566
if (m_texture_id && m_owns_texture)
567
glDeleteTextures(1, &m_texture_id);
570
m_dirty_texture = false;
571
m_dirty_bind_options = true;
573
m_mipmaps_generated = false;
576
void QSGPlainTexture::setHasMipmaps(bool mm)
579
m_mipmaps_generated = false;
583
void QSGPlainTexture::bind()
585
if (!m_dirty_texture) {
586
glBindTexture(GL_TEXTURE_2D, m_texture_id);
587
if (m_has_mipmaps && !m_mipmaps_generated) {
588
QOpenGLContext *ctx = QOpenGLContext::currentContext();
589
ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
590
m_mipmaps_generated = true;
592
updateBindOptions(m_dirty_bind_options);
593
m_dirty_bind_options = false;
597
m_dirty_texture = false;
600
if (m_image.isNull()) {
601
if (m_texture_id && m_owns_texture)
602
glDeleteTextures(1, &m_texture_id);
604
m_texture_size = QSize();
605
m_has_mipmaps = false;
610
if (m_texture_id == 0)
611
glGenTextures(1, &m_texture_id);
612
glBindTexture(GL_TEXTURE_2D, m_texture_id);
614
// ### TODO: check for out-of-memory situations...
615
int w = m_image.width();
616
int h = m_image.height();
618
QImage tmp = (m_image.format() == QImage::Format_RGB32 || m_image.format() == QImage::Format_ARGB32_Premultiplied)
620
: m_image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
622
updateBindOptions(m_dirty_bind_options);
625
qsg_swizzleBGRAToRGBA(&tmp);
626
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp.constBits());
628
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, tmp.constBits());
632
QOpenGLContext *ctx = QOpenGLContext::currentContext();
633
ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
634
m_mipmaps_generated = true;
637
m_texture_size = QSize(w, h);
638
m_texture_rect = QRectF(0, 0, 1, 1);
640
m_dirty_bind_options = false;
645
\class QSGDynamicTexture
646
\brief The QSGDynamicTexture class serves as a baseclass for dynamically changing textures,
647
such as content that is rendered to FBO's.
650
To update the content of the texture, call updateTexture() explicitly. Simply calling bind()
651
will not update the texture.
656
\fn bool QSGDynamicTexture::updateTexture()
658
Call this function to explicitly update the dynamic texture. Calling bind() will bind
659
the content that was previously updated.
661
The function returns true if the texture was changed as a resul of the update; otherwise