Avoid direct GL calls in Quick
[qt:qtdeclarative.git] / src / quick / scenegraph / util / qsgatlastexture.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
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.
16 **
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.
24 **
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.
28 **
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.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qsgatlastexture_p.h"
43
44 #include <QtCore/QVarLengthArray>
45 #include <QtCore/QElapsedTimer>
46
47 #include <QtGui/QOpenGLContext>
48 #include <QtGui/QGuiApplication>
49 #include <QtGui/QScreen>
50 #include <QtGui/QSurface>
51 #include <QtGui/QWindow>
52 #include <QtGui/qpa/qplatformnativeinterface.h>
53
54 #include <private/qsgtexture_p.h>
55
56 #include <private/qquickprofiler_p.h>
57
58 QT_BEGIN_NAMESPACE
59
60 #ifndef GL_BGRA
61 #define GL_BGRA 0x80E1
62 #endif
63
64
65 #ifndef QSG_NO_RENDER_TIMING
66 static bool qsg_render_timing = !qgetenv("QSG_RENDER_TIMING").isEmpty();
67 static QElapsedTimer qsg_renderer_timer;
68 #endif
69
70 namespace QSGAtlasTexture
71 {
72
73 static inline int qsg_powerOfTwo(int v)
74 {
75     v--;
76     v |= v >> 1;
77     v |= v >> 2;
78     v |= v >> 4;
79     v |= v >> 8;
80     v |= v >> 16;
81     ++v;
82     return v;
83 }
84
85 static int qsg_envInt(const char *name, int defaultValue)
86 {
87     QByteArray content = qgetenv(name);
88
89     bool ok = false;
90     int value = content.toInt(&ok);
91     return ok ? value : defaultValue;
92 }
93
94 Manager::Manager()
95     : m_atlas(0)
96 {
97     QOpenGLContext *gl = QOpenGLContext::currentContext();
98     Q_ASSERT(gl);
99     QSurface *surface = gl->surface();
100     QSize surfaceSize = surface->size();
101     int max;
102     gl->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
103
104     int w = qMin(max, qsg_envInt("QSG_ATLAS_WIDTH", qMax(512, qsg_powerOfTwo(surfaceSize.width()))));
105     int h = qMin(max, qsg_envInt("QSG_ATLAS_HEIGHT", qMax(512, qsg_powerOfTwo(surfaceSize.height()))));
106
107     if (surface->surfaceClass() == QSurface::Window) {
108         QWindow *window = static_cast<QWindow *>(surface);
109         // Coverwindows, optimize for memory rather than speed
110         if ((window->type() & Qt::CoverWindow) == Qt::CoverWindow) {
111             w /= 2;
112             h /= 2;
113         }
114     }
115
116     m_atlas_size_limit = qsg_envInt("QSG_ATLAS_SIZE_LIMIT", qMax(w, h) / 2);
117     m_atlas_size = QSize(w, h);
118
119     if (qEnvironmentVariableIsSet("QSG_INFO"))
120         qDebug() << "QSG: texture atlas dimensions:" << w << "x" << h;
121 }
122
123
124 Manager::~Manager()
125 {
126     Q_ASSERT(m_atlas == 0);
127 }
128
129 void Manager::invalidate()
130 {
131     if (m_atlas) {
132         m_atlas->invalidate();
133         m_atlas->deleteLater();
134         m_atlas = 0;
135     }
136 }
137
138 QSGTexture *Manager::create(const QImage &image)
139 {
140     QSGTexture *t = 0;
141     if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) {
142         if (!m_atlas)
143             m_atlas = new Atlas(m_atlas_size);
144         t = m_atlas->create(image);
145     }
146     return t;
147 }
148
149 Atlas::Atlas(const QSize &size)
150     : m_allocator(size)
151     , m_texture_id(0)
152     , m_size(size)
153     , m_allocated(false)
154 {
155
156     m_internalFormat = GL_RGBA;
157     m_externalFormat = GL_BGRA;
158
159 #ifndef QT_OPENGL_ES
160     if (QOpenGLContext::currentContext()->isOpenGLES()) {
161 #endif
162
163 #if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_NO_SDK)
164     QString *deviceName =
165             static_cast<QString *>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("AndroidDeviceName"));
166     static bool wrongfullyReportsBgra8888Support = deviceName != 0
167                                                     && (deviceName->compare(QStringLiteral("samsung SM-T211"), Qt::CaseInsensitive) == 0
168                                                         || deviceName->compare(QStringLiteral("samsung SM-T210"), Qt::CaseInsensitive) == 0
169                                                         || deviceName->compare(QStringLiteral("samsung SM-T215"), Qt::CaseInsensitive) == 0);
170 #else
171     static bool wrongfullyReportsBgra8888Support = false;
172 #endif // ANDROID
173
174     const char *ext = (const char *) QOpenGLContext::currentContext()->functions()->glGetString(GL_EXTENSIONS);
175     if (!wrongfullyReportsBgra8888Support
176             && (strstr(ext, "GL_EXT_bgra")
177                 || strstr(ext, "GL_EXT_texture_format_BGRA8888")
178                 || strstr(ext, "GL_IMG_texture_format_BGRA8888"))) {
179         m_internalFormat = m_externalFormat = GL_BGRA;
180 #ifdef Q_OS_IOS
181     } else if (strstr(ext, "GL_APPLE_texture_format_BGRA8888")) {
182         m_internalFormat = GL_RGBA;
183         m_externalFormat = GL_BGRA;
184 #endif // IOS
185     } else {
186         m_internalFormat = m_externalFormat = GL_RGBA;
187     }
188
189 #ifndef QT_OPENGL_ES
190     }
191 #endif
192
193     m_use_bgra_fallback = qEnvironmentVariableIsSet("QSG_ATLAS_USE_BGRA_FALLBACK");
194     m_debug_overlay = qEnvironmentVariableIsSet("QSG_ATLAS_OVERLAY");
195 }
196
197 Atlas::~Atlas()
198 {
199     Q_ASSERT(!m_texture_id);
200 }
201
202 void Atlas::invalidate()
203 {
204     if (m_texture_id && QOpenGLContext::currentContext())
205         QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id);
206     m_texture_id = 0;
207 }
208
209 Texture *Atlas::create(const QImage &image)
210 {
211     // No need to lock, as manager already locked it.
212     QRect rect = m_allocator.allocate(QSize(image.width() + 2, image.height() + 2));
213     if (rect.width() > 0 && rect.height() > 0) {
214         Texture *t = new Texture(this, rect, image);
215         m_pending_uploads << t;
216         return t;
217     }
218     return 0;
219 }
220
221
222 int Atlas::textureId() const
223 {
224     if (!m_texture_id) {
225         Q_ASSERT(QOpenGLContext::currentContext());
226         QOpenGLContext::currentContext()->functions()->glGenTextures(1, &const_cast<Atlas *>(this)->m_texture_id);
227     }
228
229     return m_texture_id;
230 }
231
232 static void swizzleBGRAToRGBA(QImage *image)
233 {
234     const int width = image->width();
235     const int height = image->height();
236     uint *p = (uint *) image->bits();
237     int stride = image->bytesPerLine() / 4;
238     for (int i = 0; i < height; ++i) {
239         for (int x = 0; x < width; ++x)
240             p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
241         p += stride;
242     }
243 }
244
245 void Atlas::upload(Texture *texture)
246 {
247     const QImage &image = texture->image();
248     const QRect &r = texture->atlasSubRect();
249
250     QImage tmp(r.width(), r.height(), QImage::Format_ARGB32_Premultiplied);
251     {
252         QPainter p(&tmp);
253         p.setCompositionMode(QPainter::CompositionMode_Source);
254
255         int w = r.width();
256         int h = r.height();
257         int iw = image.width();
258         int ih = image.height();
259
260         p.drawImage(1, 1, image);
261         p.drawImage(1, 0, image, 0, 0, iw, 1);
262         p.drawImage(1, h - 1, image, 0, ih - 1, iw, 1);
263         p.drawImage(0, 1, image, 0, 0, 1, ih);
264         p.drawImage(w - 1, 1, image, iw - 1, 0, 1, ih);
265         p.drawImage(0, 0, image, 0, 0, 1, 1);
266         p.drawImage(0, h - 1, image, 0, ih - 1, 1, 1);
267         p.drawImage(w - 1, 0, image, iw - 1, 0, 1, 1);
268         p.drawImage(w - 1, h - 1, image, iw - 1, ih - 1, 1, 1);
269         if (m_debug_overlay) {
270             p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
271             p.fillRect(0, 0, iw, ih, QBrush(QColor::fromRgbF(1, 0, 1, 0.5)));
272         }
273     }
274
275     if (m_externalFormat == GL_RGBA)
276         swizzleBGRAToRGBA(&tmp);
277     QOpenGLContext::currentContext()->functions()->glTexSubImage2D(GL_TEXTURE_2D, 0,
278                                                                    r.x(), r.y(), r.width(), r.height(),
279                                                                    m_externalFormat, GL_UNSIGNED_BYTE, tmp.constBits());
280 }
281
282 void Atlas::uploadBgra(Texture *texture)
283 {
284     QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
285     const QRect &r = texture->atlasSubRect();
286     QImage image = texture->image();
287
288     if (image.format() != QImage::Format_ARGB32_Premultiplied
289             && image.format() != QImage::Format_RGB32) {
290         image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
291     }
292
293     if (m_debug_overlay) {
294         QPainter p(&image);
295         p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
296         p.fillRect(0, 0, image.width(), image.height(), QBrush(QColor::fromRgbF(0, 1, 1, 0.5)));
297     }
298
299     QVarLengthArray<quint32, 512> tmpBits(qMax(image.width() + 2, image.height() + 2));
300     int iw = image.width();
301     int ih = image.height();
302     int bpl = image.bytesPerLine() / 4;
303     const quint32 *src = (const quint32 *) image.constBits();
304     quint32 *dst = tmpBits.data();
305
306     // top row, padding corners
307     dst[0] = src[0];
308     memcpy(dst + 1, src, iw * sizeof(quint32));
309     dst[1 + iw] = src[iw-1];
310     funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y(), iw + 2, 1, m_externalFormat, GL_UNSIGNED_BYTE, dst);
311
312     // bottom row, padded corners
313     const quint32 *lastRow = src + bpl * (ih - 1);
314     dst[0] = lastRow[0];
315     memcpy(dst + 1, lastRow, iw * sizeof(quint32));
316     dst[1 + iw] = lastRow[iw-1];
317     funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y() + ih + 1, iw + 2, 1, m_externalFormat, GL_UNSIGNED_BYTE, dst);
318
319     // left column
320     for (int i=0; i<ih; ++i)
321         dst[i] = src[i * bpl];
322     funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y() + 1, 1, ih, m_externalFormat, GL_UNSIGNED_BYTE, dst);
323
324     // right column
325     for (int i=0; i<ih; ++i)
326         dst[i] = src[i * bpl + iw - 1];
327     funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + iw + 1, r.y() + 1, 1, ih, m_externalFormat, GL_UNSIGNED_BYTE, dst);
328
329     // Inner part of the image....
330     funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, m_externalFormat, GL_UNSIGNED_BYTE, src);
331 }
332
333 void Atlas::bind(QSGTexture::Filtering filtering)
334 {
335     QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
336     if (!m_allocated) {
337         m_allocated = true;
338
339         while (funcs->glGetError() != GL_NO_ERROR) ;
340
341         funcs->glGenTextures(1, &m_texture_id);
342         funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id);
343         funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
344         funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
345         funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
346         funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
347 #if !defined(QT_OPENGL_ES_2)
348         if (!QOpenGLContext::currentContext()->isOpenGLES())
349             funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
350 #endif
351         funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, 0);
352
353 #if 0
354         QImage pink(m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied);
355         pink.fill(0);
356         QPainter p(&pink);
357         QLinearGradient redGrad(0, 0, m_size.width(), 0);
358         redGrad.setColorAt(0, Qt::black);
359         redGrad.setColorAt(1, Qt::red);
360         p.fillRect(0, 0, m_size.width(), m_size.height(), redGrad);
361         p.setCompositionMode(QPainter::CompositionMode_Plus);
362         QLinearGradient blueGrad(0, 0, 0, m_size.height());
363         blueGrad.setColorAt(0, Qt::black);
364         blueGrad.setColorAt(1, Qt::blue);
365         p.fillRect(0, 0, m_size.width(), m_size.height(), blueGrad);
366         p.end();
367
368         funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, pink.constBits());
369 #endif
370
371         GLenum errorCode = funcs->glGetError();
372         if (errorCode == GL_OUT_OF_MEMORY) {
373             qDebug("QSGTextureAtlas: texture atlas allocation failed, out of memory");
374             funcs->glDeleteTextures(1, &m_texture_id);
375             m_texture_id = 0;
376         } else if (errorCode != GL_NO_ERROR) {
377             qDebug("QSGTextureAtlas: texture atlas allocation failed, code=%x", errorCode);
378             funcs->glDeleteTextures(1, &m_texture_id);
379             m_texture_id = 0;
380         }
381     } else {
382         funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id);
383     }
384
385     if (m_texture_id == 0)
386         return;
387
388     // Upload all pending images..
389     for (int i=0; i<m_pending_uploads.size(); ++i) {
390
391 #ifndef QSG_NO_RENDER_TIMING
392         bool profileFrames = qsg_render_timing || QQuickProfiler::enabled;
393         if (profileFrames)
394             qsg_renderer_timer.start();
395 #endif
396
397         if (m_externalFormat == GL_BGRA &&
398                 !m_use_bgra_fallback) {
399             uploadBgra(m_pending_uploads.at(i));
400         } else {
401             upload(m_pending_uploads.at(i));
402         }
403
404 #ifndef QSG_NO_RENDER_TIMING
405         if (qsg_render_timing) {
406             qDebug("   - AtlasTexture(%dx%d), uploaded in %d ms",
407                    m_pending_uploads.at(i)->image().width(),
408                    m_pending_uploads.at(i)->image().height(),
409                    (int) (qsg_renderer_timer.elapsed()));
410         }
411
412         Q_QUICK_SG_PROFILE1(QQuickProfiler::SceneGraphTexturePrepare, (
413                 0,  // bind (not relevant)
414                 0,  // convert (not relevant)
415                 0,  // swizzle (not relevant)
416                 qsg_renderer_timer.nsecsElapsed(), // (upload all of the above)
417                 0)); // mipmap (not used ever...)
418 #endif
419     }
420
421     GLenum f = filtering == QSGTexture::Nearest ? GL_NEAREST : GL_LINEAR;
422     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, f);
423     funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, f);
424
425     m_pending_uploads.clear();
426 }
427
428 void Atlas::remove(Texture *t)
429 {
430     QRect atlasRect = t->atlasSubRect();
431     m_allocator.deallocate(atlasRect);
432     m_pending_uploads.removeOne(t);
433 }
434
435
436
437 Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image)
438     : QSGTexture()
439     , m_allocated_rect(textureRect)
440     , m_image(image)
441     , m_atlas(atlas)
442     , m_nonatlas_texture(0)
443 {
444     m_allocated_rect_without_padding = m_allocated_rect.adjusted(1, 1, -1, -1);
445     float w = atlas->size().width();
446     float h = atlas->size().height();
447
448     m_texture_coords_rect = QRectF(m_allocated_rect_without_padding.x() / w,
449                                    m_allocated_rect_without_padding.y() / h,
450                                    m_allocated_rect_without_padding.width() / w,
451                                    m_allocated_rect_without_padding.height() / h);
452 }
453
454 Texture::~Texture()
455 {
456     m_atlas->remove(this);
457     if (m_nonatlas_texture)
458         delete m_nonatlas_texture;
459 }
460
461 void Texture::bind()
462 {
463     m_atlas->bind(filtering());
464 }
465
466 QSGTexture *Texture::removedFromAtlas() const
467 {
468     if (!m_nonatlas_texture) {
469         m_nonatlas_texture = new QSGPlainTexture();
470         m_nonatlas_texture->setImage(m_image);
471         m_nonatlas_texture->setFiltering(filtering());
472         m_nonatlas_texture->setMipmapFiltering(mipmapFiltering());
473     }
474     return m_nonatlas_texture;
475 }
476
477 }
478
479 QT_END_NAMESPACE