diff options
author | Gunnar Sletta <gunnar.sletta@jollamobile.com> | 2014-02-25 13:44:29 +0000 |
---|---|---|
committer | Gunnar Sletta <gunnar.sletta@jollamobile.com> | 2014-02-25 18:15:32 +0100 |
commit | c4dc0ac41b9efa020be99ae604d6a12431d7d8c1 (patch) | |
tree | bfa8d3e1779750240d28be3e4314a54a23fab362 | |
parent | e63c3bef0855e1dc4cf151241772da995ce064aa (diff) |
EGLImage/gralloc based textures
All the work relating to the texture upload is done on the
pixmap loader thread when the texture factory is created.
QSGTexture::bind() on the render thread then becomes a
zero-cost operation, which is very beneficial for large images
which tend to block the render thread for 10s of milliseconds.
Change-Id: I2d9eb15e64c435d58e454641dda7d588d6b236f4
Reviewed-by: Michael Brasser <michael.brasser@live.com>
-rw-r--r-- | customcontext/context.cpp | 19 | ||||
-rw-r--r-- | customcontext/context.h | 5 | ||||
-rw-r--r-- | customcontext/customcontext.pro | 13 | ||||
-rw-r--r-- | customcontext/texture/eglgralloctexture.cpp | 353 | ||||
-rw-r--r-- | customcontext/texture/eglgralloctexture.h | 120 |
5 files changed, 508 insertions, 2 deletions
diff --git a/customcontext/context.cpp b/customcontext/context.cpp index d94d2f9..8b4ab6d 100644 --- a/customcontext/context.cpp +++ b/customcontext/context.cpp @@ -76,6 +76,10 @@ #include "nonpreservedtexture.h" #endif +#ifdef CUSTOMCONTEXT_EGLGRALLOCTEXTURE +#include "eglgralloctexture.h" +#endif + #ifdef CUSTOMCONTEXT_MSAA #include <private/qsgdefaultimagenode_p.h> #include <private/qsgdefaultrectanglenode_p.h> @@ -180,6 +184,10 @@ Context::Context(QObject *parent) m_macTexture = qgetenv("CUSTOMCONTEXT_NO_MACTEXTURE").isEmpty(); #endif +#ifdef CUSTOMCONTEXT_EGLGRALLOCTEXTURE + m_eglGrallocTexture = qEnvironmentVariableIsEmpty("CUSTOMCONTEXT_NO_EGLGRALLOCTEXTURE"); +#endif + #ifdef CUSTOMCONTEXT_THREADUPLOADTEXTURE m_threadUploadTexture = qgetenv("CUSTOMCONTEXT_NO_THREADUPLOADTEXTURE").isEmpty(); connect(this, SIGNAL(invalidated()), &m_threadUploadManager, SLOT(invalidated()), Qt::DirectConnection); @@ -231,6 +239,9 @@ Context::Context(QObject *parent) #ifdef CUSTOMCONTEXT_THREADUPLOADTEXTURE qDebug(" - threaded texture upload: %s", m_threadUploadTexture ? "yes" : "no"); #endif +#ifdef CUSTOMCONTEXT_EGLGRALLOCTEXTURE + qDebug(" - EGLImage/Gralloc based texture: %s", m_eglGrallocTexture ? "yes" : "no"); +#endif #ifdef CUSTOMCONTEXT_MACTEXTURE qDebug(" - mac textures: %s", m_macTexture ? "yes" : "no"); #endif @@ -450,6 +461,14 @@ QQuickTextureFactory *Context::createTextureFactory(const QImage &image) { Q_UNUSED(image); +#ifdef CUSTOMCONTEXT_EGLGRALLOCTEXTURE + if (m_eglGrallocTexture) { + EglGrallocTextureFactory *tf = EglGrallocTextureFactory::create(image); + if (tf) + return tf; + } +#endif + #ifdef CUSTOMCONTEXT_THREADUPLOADTEXTURE if (m_threadUploadTexture) return m_threadUploadManager.create(image); diff --git a/customcontext/context.h b/customcontext/context.h index 2a2a3da..830d854 100644 --- a/customcontext/context.h +++ b/customcontext/context.h @@ -191,8 +191,9 @@ private: bool m_defaultImageNodes; #endif - - +#ifdef CUSTOMCONTEXT_EGLGRALLOCTEXTURE + bool m_eglGrallocTexture; +#endif }; diff --git a/customcontext/customcontext.pro b/customcontext/customcontext.pro index 2660280..3301541 100644 --- a/customcontext/customcontext.pro +++ b/customcontext/customcontext.pro @@ -77,6 +77,19 @@ dither:{ } } +eglgralloctexture:{ + message("eglgralloctexture ........: yes (remember: ANDROID_SDK_INCLUDE)") + DEFINES += CUSTOMCONTEXT_EGLGRALLOCTEXTURE + SOURCES += texture/eglgralloctexture.cpp + HEADERS += texture/eglgralloctexture.h + + INCLUDEPATH += texture $(ANDROID_SDK_INCLUDE) $(ANDROID_SDK_INCLUDE)/android + LIBS += -lhardware + +} else { + message("eglgralloctexure .........: no") +} + nonpreservedtexture:{ message("nonpreservedtexture ......: yes") DEFINES += CUSTOMCONTEXT_NONPRESERVEDTEXTURE diff --git a/customcontext/texture/eglgralloctexture.cpp b/customcontext/texture/eglgralloctexture.cpp new file mode 100644 index 0000000..943f57d --- /dev/null +++ b/customcontext/texture/eglgralloctexture.cpp @@ -0,0 +1,353 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Scenegraph Playground module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "eglgralloctexture.h" + +#include <QtCore/QElapsedTimer> +#include <QtCore/QThread> +#include <QtCore/QCoreApplication> +#include <QtCore/qdebug.h> + +#include <QtQuick/QQuickWindow> + +#include <android/hardware/gralloc.h> +#include <android/system/window.h> + + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +// Taken from libhybris +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#ifndef QSG_NO_RENDER_TIMING +static bool qsg_render_timing = !qgetenv("QSG_RENDER_TIMING").isEmpty(); +static QElapsedTimer qsg_renderer_timer; +#endif + +namespace CustomContext { + +gralloc_module_t *gralloc = 0; +alloc_device_t *alloc = 0; + +extern "C" { + typedef void (* _glEGLImageTargetTexture2DOES)(GLenum target, EGLImageKHR image); + typedef EGLImageKHR (* _eglCreateImageKHR)(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attribs); + typedef EGLBoolean (* _eglDestroyImageKHR)(EGLDisplay dpy, EGLImageKHR image); +} +_glEGLImageTargetTexture2DOES glEGLImageTargetTexture2DOES = 0; +_eglCreateImageKHR eglCreateImageKHR = 0; +_eglDestroyImageKHR eglDestroyImageKHR = 0; + +void initialize() +{ + hw_get_module(GRALLOC_HARDWARE_MODULE_ID, (const hw_module_t **) &gralloc); + gralloc_open((const hw_module_t *) gralloc, &alloc); + +#ifdef CUSTOMCONTEXT_DEBUG + qDebug("EglGrallocTexture: initializing gralloc=%p, alloc=%p", gralloc, alloc); +#endif + + glEGLImageTargetTexture2DOES = (_glEGLImageTargetTexture2DOES) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + eglCreateImageKHR = (_eglCreateImageKHR) eglGetProcAddress("eglCreateImageKHR"); + eglDestroyImageKHR = (_eglDestroyImageKHR) eglGetProcAddress("eglDestroyImageKHR"); +} + +NativeBuffer::NativeBuffer(const QImage &image) +{ + m_hasAlpha = image.hasAlphaChannel(); + + format = HAL_PIXEL_FORMAT_BGRA_8888; + usage = GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_TEXTURE; + width = image.width(); + height = image.height(); + stride = 0; + handle = 0; + common.incRef = _incRef; + common.decRef = _decRef; + +#ifdef CUSTOMCONTEXT_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + + char *data = 0; + + if (alloc->alloc(alloc, width, height, format, usage, &handle, &stride)) { + qDebug() << "alloc failed..."; + return; + } + +#ifdef CUSTOMCONTEXT_DEBUG + quint64 allocTime = timer.elapsed(); +#endif + + if (gralloc->lock(gralloc, handle, GRALLOC_USAGE_SW_WRITE_RARELY, 0, 0, width, height, (void **) &data) || data == 0) { + qDebug() << "gralloc lock failed..."; + release(); + return; + } + +#ifdef CUSTOMCONTEXT_DEBUG + quint64 lockTime = timer.elapsed(); +#endif + + int dbpl = stride * 4; + if (dbpl == image.bytesPerLine()) { + memcpy(data, image.constBits(), image.byteCount()); + } else { + int h = image.height(); + int bpl = qMin(dbpl, image.bytesPerLine()); + for (int y=0; y<h; ++y) + memcpy(data + y * dbpl, image.scanLine(y), bpl); + } + +#ifdef CUSTOMCONTEXT_DEBUG + quint64 copyTime = timer.elapsed(); +#endif + + if (gralloc->unlock(gralloc, handle)) { + qDebug() << "gralloc unlock failed..."; + release(); + return; + } + +#ifdef CUSTOMCONTEXT_DEBUG + quint64 unlockTime = timer.elapsed(); +#endif + + m_eglImage = eglCreateImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), + EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, + (ANativeWindowBuffer *) this, + 0); + + if (!m_eglImage) + qFatal("no egl image..."); + +#ifdef CUSTOMCONTEXT_DEBUG + quint64 imageTime = timer.elapsed(); +#endif + +#ifdef CUSTOMCONTEXT_DEBUG + qDebug("EglGrallocTexture: created gralloc buffer: %d x %d, handle=%p, stride=%d/%d (%d), time: alloc=%d, lock=%d, copy=%d, unlock=%d, eglImage=%d", + width, height, + handle, + stride, stride * 4, image.bytesPerLine(), + (int) allocTime, + (int) (lockTime - allocTime), + (int) (copyTime - lockTime), + (int) (unlockTime - copyTime), + (int) (imageTime - unlockTime)); +#endif +} + +NativeBuffer::~NativeBuffer() +{ +#ifdef CUSTOMCONTEXT_DEBUG + qDebug() << "EglGrallocTexture: native buffer released.." << (void *) this; +#endif + release(); +} + +void NativeBuffer::releaseEglImage() +{ + eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), m_eglImage); + m_eglImage = 0; +} + +void NativeBuffer::_incRef(android_native_base_t* base) +{ + NativeBuffer *b = container_of(base, NativeBuffer, common); + b->m_ref.ref(); +} + +void NativeBuffer::_decRef(android_native_base_t* base) +{ + NativeBuffer *b = container_of(base, NativeBuffer, common); + if (!b->m_ref.deref()) + delete b; +} + +void NativeBuffer::release() +{ + alloc->free(alloc, handle); + handle = 0; +} + +EglGrallocTexture::EglGrallocTexture(NativeBuffer *buffer) + : m_id(0) + , m_buffer(buffer) + , m_bound(false) +{ + Q_ASSERT(buffer); +#ifdef CUSTOMCONTEXT_DEBUG + qDebug() << "EglGrallocTexture: created" << QSize(buffer->width, buffer->height) << buffer << (void *) (buffer ? buffer->eglImage() : 0) << (void *) (buffer ? buffer->handle : 0); +#endif +} + +EglGrallocTexture::~EglGrallocTexture() +{ + if (m_id) + glDeleteTextures(1, &m_id); +} + +void EglGrallocTexture::bind() +{ + glBindTexture(GL_TEXTURE_2D, textureId()); + updateBindOptions(!m_bound); + + if (!m_bound) { + m_bound = true; + +#ifdef CUSTOMCONTEXT_DEBUG + qsg_render_timing = true; +#endif + if (Q_UNLIKELY(qsg_render_timing)) + qsg_renderer_timer.start(); + + while (glGetError() != GL_NO_ERROR); // Clear pending errors + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_buffer->eglImage()); + + int error; + while ((error = glGetError()) != GL_NO_ERROR) + qDebug() << "Error after glEGLImageTargetTexture2DOES" << hex << error; + + if (Q_UNLIKELY(qsg_render_timing)) + qDebug(" - eglgralloctexture(%dx%d) bind=%d ms", m_buffer->width, m_buffer->height, (int) qsg_renderer_timer.elapsed()); + } +} + +int EglGrallocTexture::textureId() const +{ + if (m_id == 0) + glGenTextures(1, &m_id); + return m_id; +} + +QSize EglGrallocTexture::textureSize() const +{ + return QSize(m_buffer->width, m_buffer->height); +} + +bool EglGrallocTexture::hasAlphaChannel() const +{ + return m_buffer->hasAlpha(); +} + +bool EglGrallocTexture::hasMipmaps() const +{ + return false; +} + +EglGrallocTextureFactory::EglGrallocTextureFactory(NativeBuffer *buffer) + : m_buffer(buffer) +{ +#ifdef CUSTOMCONTEXT_DEBUG + qDebug("EglGrallocTexture: creating texture factory size=%dx%d, buffer=%p", buffer->width, buffer->height, buffer); +#endif +} + +EglGrallocTextureFactory::~EglGrallocTextureFactory() +{ + m_buffer->releaseEglImage(); +} + +QSGTexture *EglGrallocTextureFactory::createTexture(QQuickWindow *window) const +{ + return new EglGrallocTexture(m_buffer); +} + +QSize EglGrallocTextureFactory::textureSize() const +{ + return QSize(m_buffer->width, m_buffer->height); +} + +int EglGrallocTextureFactory::textureByteCount() const +{ + return m_buffer->stride * m_buffer->height; +} + +QImage EglGrallocTextureFactory::image() const +{ + void *data = 0; + if (gralloc->lock(gralloc, m_buffer->handle, GRALLOC_USAGE_SW_READ_RARELY, 0, 0, m_buffer->width, m_buffer->height, (void **) &data) || data == 0) { + qDebug("EglGrallocTextureFactory::image(): lock failed, cannot get image data"); + return QImage(); + } + + QImage content = QImage((const uchar *) data, m_buffer->width, m_buffer->height, m_buffer->stride * 4, + m_buffer->hasAlpha() ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_RGB32).copy(); + + if (gralloc->unlock(gralloc, m_buffer->handle)) { + qDebug("EglGrallocTextureFactory::image(): unlock failed"); + return QImage(); + } + + return content; +} + +EglGrallocTextureFactory *EglGrallocTextureFactory::create(const QImage &image) +{ + if (image.width() * image.height() < 500 * 500) + return 0; + + if (gralloc == 0) { + initialize(); + if (!gralloc) + return 0; + } + + NativeBuffer *buffer = new NativeBuffer(image); + if (buffer && !buffer->handle) { +#ifdef CUSTOMCONTEXT_DEBUG + qDebug("EglGrallocTextureFactory: failed to allocate native buffer for image: %d x %d", image.width(), image.height()); +#endif + delete buffer; + return 0; + } + return new EglGrallocTextureFactory(buffer); +} + +} diff --git a/customcontext/texture/eglgralloctexture.h b/customcontext/texture/eglgralloctexture.h new file mode 100644 index 0000000..2d05f7e --- /dev/null +++ b/customcontext/texture/eglgralloctexture.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Scenegraph Playground module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef EGLGRALLOCTEXTURE_H +#define EGLGRALLOCTEXTURE_H + +#include <QtQuick/QSGTexture> +#include <QtQuick/QQuickTextureFactory> + +#include <QtGui/qopengl.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <android/system/window.h> + +class QQuickWindow; + +namespace CustomContext { + +class NativeBuffer : public ANativeWindowBuffer +{ +public: + NativeBuffer(const QImage &image); + ~NativeBuffer(); + + static void _incRef(android_native_base_t *base); + static void _decRef(android_native_base_t *base); + + void release(); + + EGLImageKHR eglImage() const { return m_eglImage; } + void releaseEglImage(); + + bool hasAlpha() const { return m_hasAlpha; } + +private: + QAtomicInt m_ref; + EGLImageKHR m_eglImage; + bool m_hasAlpha; +}; + +class EglGrallocTexture : public QSGTexture +{ + Q_OBJECT +public: + EglGrallocTexture(NativeBuffer *buffer); + ~EglGrallocTexture(); + + virtual int textureId() const; + virtual QSize textureSize() const; + virtual bool hasAlphaChannel() const; + virtual bool hasMipmaps() const; + virtual void bind(); + +private: + mutable GLuint m_id; + NativeBuffer *m_buffer; + bool m_bound; +}; + +class EglGrallocTextureFactory : public QQuickTextureFactory +{ + Q_OBJECT +public: + EglGrallocTextureFactory(NativeBuffer *buffer); + ~EglGrallocTextureFactory(); + + virtual QSGTexture *createTexture(QQuickWindow *window) const; + virtual QSize textureSize() const; + virtual int textureByteCount() const; + virtual QImage image() const; + + static EglGrallocTextureFactory *create(const QImage &image); + +private: + NativeBuffer *m_buffer; +}; + +} + +#endif // EGLGRALLOCTEXTURE_H |