gecko/widget/android/AndroidGraphicBuffer.cpp
2012-02-01 16:18:35 -05:00

501 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Corporation code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* James Willcox <jwillcox@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include <dlfcn.h>
#include <android/log.h>
#include <GLES2/gl2.h>
#include "AndroidGraphicBuffer.h"
#include "AndroidBridge.h"
#include "mozilla/Preferences.h"
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AndroidGraphicBuffer" , ## args)
#define EGL_NATIVE_BUFFER_ANDROID 0x3140
#define EGL_IMAGE_PRESERVED_KHR 0x30D2
typedef PRUint32 EGLenum;
typedef PRInt32 EGLint;
typedef PRUint32 EGLBoolean;
typedef gfxASurface::gfxImageFormat gfxImageFormat;
#define EGL_TRUE 1
#define EGL_FALSE 0
#define EGL_NONE 0x3038
#define EGL_NO_CONTEXT (EGLContext)0
#define EGL_DEFAULT_DISPLAY (void*)0
#define ANDROID_LIBUI_PATH "libui.so"
#define ANDROID_GLES_PATH "libGLESv2.so"
#define ANDROID_EGL_PATH "libEGL.so"
// Really I have no idea, but this should be big enough
#define GRAPHIC_BUFFER_SIZE 1024
enum {
/* buffer is never read in software */
GRALLOC_USAGE_SW_READ_NEVER = 0x00000000,
/* buffer is rarely read in software */
GRALLOC_USAGE_SW_READ_RARELY = 0x00000002,
/* buffer is often read in software */
GRALLOC_USAGE_SW_READ_OFTEN = 0x00000003,
/* mask for the software read values */
GRALLOC_USAGE_SW_READ_MASK = 0x0000000F,
/* buffer is never written in software */
GRALLOC_USAGE_SW_WRITE_NEVER = 0x00000000,
/* buffer is never written in software */
GRALLOC_USAGE_SW_WRITE_RARELY = 0x00000020,
/* buffer is never written in software */
GRALLOC_USAGE_SW_WRITE_OFTEN = 0x00000030,
/* mask for the software write values */
GRALLOC_USAGE_SW_WRITE_MASK = 0x000000F0,
/* buffer will be used as an OpenGL ES texture */
GRALLOC_USAGE_HW_TEXTURE = 0x00000100,
/* buffer will be used as an OpenGL ES render target */
GRALLOC_USAGE_HW_RENDER = 0x00000200,
/* buffer will be used by the 2D hardware blitter */
GRALLOC_USAGE_HW_2D = 0x00000400,
/* buffer will be used with the framebuffer device */
GRALLOC_USAGE_HW_FB = 0x00001000,
/* mask for the software usage bit-mask */
GRALLOC_USAGE_HW_MASK = 0x00001F00,
};
enum {
HAL_PIXEL_FORMAT_RGBA_8888 = 1,
HAL_PIXEL_FORMAT_RGBX_8888 = 2,
HAL_PIXEL_FORMAT_RGB_888 = 3,
HAL_PIXEL_FORMAT_RGB_565 = 4,
HAL_PIXEL_FORMAT_BGRA_8888 = 5,
HAL_PIXEL_FORMAT_RGBA_5551 = 6,
HAL_PIXEL_FORMAT_RGBA_4444 = 7,
};
typedef struct ARect {
int32_t left;
int32_t top;
int32_t right;
int32_t bottom;
} ARect;
static bool gTryRealloc = true;
static class GLFunctions
{
public:
GLFunctions() : mInitialized(false)
{
}
typedef EGLDisplay (* pfnGetDisplay)(void *display_id);
pfnGetDisplay fGetDisplay;
typedef EGLint (* pfnEGLGetError)(void);
pfnEGLGetError fEGLGetError;
typedef EGLImageKHR (* pfnCreateImageKHR)(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
pfnCreateImageKHR fCreateImageKHR;
typedef EGLBoolean (* pfnDestroyImageKHR)(EGLDisplay dpy, EGLImageKHR image);
pfnDestroyImageKHR fDestroyImageKHR;
typedef void (* pfnImageTargetTexture2DOES)(GLenum target, EGLImageKHR image);
pfnImageTargetTexture2DOES fImageTargetTexture2DOES;
typedef void (* pfnBindTexture)(GLenum target, GLuint texture);
pfnBindTexture fBindTexture;
typedef GLenum (* pfnGLGetError)();
pfnGLGetError fGLGetError;
typedef void (*pfnGraphicBufferCtor)(void*, PRUint32 w, PRUint32 h, PRUint32 format, PRUint32 usage);
pfnGraphicBufferCtor fGraphicBufferCtor;
typedef void (*pfnGraphicBufferDtor)(void*);
pfnGraphicBufferDtor fGraphicBufferDtor;
typedef int (*pfnGraphicBufferLock)(void*, PRUint32 usage, unsigned char **addr);
pfnGraphicBufferLock fGraphicBufferLock;
typedef int (*pfnGraphicBufferLockRect)(void*, PRUint32 usage, const ARect&, unsigned char **addr);
pfnGraphicBufferLockRect fGraphicBufferLockRect;
typedef int (*pfnGraphicBufferUnlock)(void*);
pfnGraphicBufferUnlock fGraphicBufferUnlock;
typedef void* (*pfnGraphicBufferGetNativeBuffer)(void*);
pfnGraphicBufferGetNativeBuffer fGraphicBufferGetNativeBuffer;
typedef int (*pfnGraphicBufferReallocate)(void*, PRUint32 w, PRUint32 h, PRUint32 format);
pfnGraphicBufferReallocate fGraphicBufferReallocate;
bool EnsureInitialized()
{
if (mInitialized) {
return true;
}
void *handle = dlopen(ANDROID_EGL_PATH, RTLD_LAZY);
if (!handle) {
LOG("Couldn't load EGL library");
return false;
}
fGetDisplay = (pfnGetDisplay)dlsym(handle, "eglGetDisplay");
fEGLGetError = (pfnEGLGetError)dlsym(handle, "eglGetError");
fCreateImageKHR = (pfnCreateImageKHR)dlsym(handle, "eglCreateImageKHR");
fDestroyImageKHR = (pfnDestroyImageKHR)dlsym(handle, "eglDestroyImageKHR");
if (!fGetDisplay || !fEGLGetError || !fCreateImageKHR || !fDestroyImageKHR) {
LOG("Failed to find some EGL functions");
return false;
}
handle = dlopen(ANDROID_GLES_PATH, RTLD_LAZY);
if (!handle) {
LOG("Couldn't load GL library");
return false;
}
fImageTargetTexture2DOES = (pfnImageTargetTexture2DOES)dlsym(handle, "glEGLImageTargetTexture2DOES");
fBindTexture = (pfnBindTexture)dlsym(handle, "glBindTexture");
fGLGetError = (pfnGLGetError)dlsym(handle, "glGetError");
if (!fImageTargetTexture2DOES || !fBindTexture || !fGLGetError) {
LOG("Failed to find some GL functions");
return false;
}
handle = dlopen(ANDROID_LIBUI_PATH, RTLD_LAZY);
if (!handle) {
LOG("Couldn't load libui.so");
return false;
}
fGraphicBufferCtor = (pfnGraphicBufferCtor)dlsym(handle, "_ZN7android13GraphicBufferC1Ejjij");
fGraphicBufferDtor = (pfnGraphicBufferDtor)dlsym(handle, "_ZN7android13GraphicBufferD1Ev");
fGraphicBufferLock = (pfnGraphicBufferLock)dlsym(handle, "_ZN7android13GraphicBuffer4lockEjPPv");
fGraphicBufferLockRect = (pfnGraphicBufferLockRect)dlsym(handle, "_ZN7android13GraphicBuffer4lockEjRKNS_4RectEPPv");
fGraphicBufferUnlock = (pfnGraphicBufferUnlock)dlsym(handle, "_ZN7android13GraphicBuffer6unlockEv");
fGraphicBufferGetNativeBuffer = (pfnGraphicBufferGetNativeBuffer)dlsym(handle, "_ZNK7android13GraphicBuffer15getNativeBufferEv");
fGraphicBufferReallocate = (pfnGraphicBufferReallocate)dlsym(handle, "_ZN7android13GraphicBuffer10reallocateEjjij");
if (!fGraphicBufferCtor || !fGraphicBufferDtor || !fGraphicBufferLock ||
!fGraphicBufferUnlock || !fGraphicBufferGetNativeBuffer) {
LOG("Failed to lookup some GraphicBuffer functions");
return false;
}
mInitialized = true;
return true;
}
private:
bool mInitialized;
} sGLFunctions;
namespace mozilla {
static void clearGLError()
{
while (glGetError() != GL_NO_ERROR);
}
static bool ensureNoGLError(const char* name)
{
bool result = true;
GLuint error;
while ((error = glGetError()) != GL_NO_ERROR) {
LOG("GL error [%s]: %40x\n", name, error);
result = false;
}
return result;
}
AndroidGraphicBuffer::AndroidGraphicBuffer(PRUint32 width, PRUint32 height, PRUint32 usage,
gfxImageFormat format) :
mWidth(width)
, mHeight(height)
, mUsage(usage)
, mFormat(format)
, mHandle(0)
, mEGLImage(0)
{
}
AndroidGraphicBuffer::~AndroidGraphicBuffer()
{
DestroyBuffer();
}
void
AndroidGraphicBuffer::DestroyBuffer()
{
/**
* XXX: eglDestroyImageKHR crashes sometimes due to refcount badness (I think)
*
* If you look at egl.cpp (https://github.com/android/platform_frameworks_base/blob/master/opengl/libagl/egl.cpp#L2002)
* you can see that eglCreateImageKHR just refs the native buffer, and eglDestroyImageKHR
* just unrefs it. Somehow the ref count gets messed up and things are already destroyed
* by the time eglDestroyImageKHR gets called. For now, at least, just not calling
* eglDestroyImageKHR should be fine since we do free the GraphicBuffer below.
*
* Bug 712716
*/
#if 0
if (mEGLImage) {
if (sGLFunctions.EnsureInitialized()) {
sGLFunctions.fDestroyImageKHR(sGLFunctions.fGetDisplay(EGL_DEFAULT_DISPLAY), mEGLImage);
mEGLImage = NULL;
}
}
#endif
mEGLImage = NULL;
if (mHandle) {
if (sGLFunctions.EnsureInitialized()) {
sGLFunctions.fGraphicBufferDtor(mHandle);
}
free(mHandle);
mHandle = NULL;
}
}
bool
AndroidGraphicBuffer::EnsureBufferCreated()
{
if (!mHandle) {
mHandle = malloc(GRAPHIC_BUFFER_SIZE);
sGLFunctions.fGraphicBufferCtor(mHandle, mWidth, mHeight, GetAndroidFormat(mFormat), GetAndroidUsage(mUsage));
}
return true;
}
bool
AndroidGraphicBuffer::EnsureInitialized()
{
if (!sGLFunctions.EnsureInitialized()) {
return false;
}
EnsureBufferCreated();
return true;
}
int
AndroidGraphicBuffer::Lock(PRUint32 aUsage, unsigned char **bits)
{
if (!EnsureInitialized())
return true;
return sGLFunctions.fGraphicBufferLock(mHandle, GetAndroidUsage(aUsage), bits);
}
int
AndroidGraphicBuffer::Lock(PRUint32 aUsage, const nsIntRect& aRect, unsigned char **bits)
{
if (!EnsureInitialized())
return false;
ARect rect;
rect.left = aRect.x;
rect.top = aRect.y;
rect.right = aRect.x + aRect.width;
rect.bottom = aRect.y + aRect.height;
return sGLFunctions.fGraphicBufferLockRect(mHandle, GetAndroidUsage(aUsage), rect, bits);
}
int
AndroidGraphicBuffer::Unlock()
{
if (!EnsureInitialized())
return false;
return sGLFunctions.fGraphicBufferUnlock(mHandle);
}
bool
AndroidGraphicBuffer::Reallocate(PRUint32 aWidth, PRUint32 aHeight, gfxImageFormat aFormat)
{
if (!EnsureInitialized())
return false;
mWidth = aWidth;
mHeight = aHeight;
mFormat = aFormat;
// Sometimes GraphicBuffer::reallocate just doesn't work. In those cases we'll just allocate a brand
// new buffer. If reallocate fails once, never try it again.
if (!gTryRealloc || sGLFunctions.fGraphicBufferReallocate(mHandle, aWidth, aHeight, GetAndroidFormat(aFormat)) != 0) {
DestroyBuffer();
EnsureBufferCreated();
gTryRealloc = false;
}
return true;
}
PRUint32
AndroidGraphicBuffer::GetAndroidUsage(PRUint32 aUsage)
{
PRUint32 flags = 0;
if (aUsage & UsageSoftwareRead) {
flags |= GRALLOC_USAGE_SW_READ_OFTEN;
}
if (aUsage & UsageSoftwareWrite) {
flags |= GRALLOC_USAGE_SW_WRITE_OFTEN;
}
if (aUsage & UsageTexture) {
flags |= GRALLOC_USAGE_HW_TEXTURE;
}
if (aUsage & UsageTarget) {
flags |= GRALLOC_USAGE_HW_RENDER;
}
if (aUsage & Usage2D) {
flags |= GRALLOC_USAGE_HW_2D;
}
return flags;
}
PRUint32
AndroidGraphicBuffer::GetAndroidFormat(gfxImageFormat aFormat)
{
switch (aFormat) {
case gfxImageFormat::ImageFormatRGB24:
return HAL_PIXEL_FORMAT_RGBX_8888;
case gfxImageFormat::ImageFormatRGB16_565:
return HAL_PIXEL_FORMAT_RGB_565;
default:
return 0;
}
}
bool
AndroidGraphicBuffer::EnsureEGLImage()
{
if (mEGLImage)
return true;
if (!EnsureInitialized())
return false;
EGLint eglImgAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE, EGL_NONE };
void* nativeBuffer = sGLFunctions.fGraphicBufferGetNativeBuffer(mHandle);
mEGLImage = sGLFunctions.fCreateImageKHR(sGLFunctions.fGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, (EGLClientBuffer)nativeBuffer, eglImgAttrs);
return mEGLImage != NULL;
}
bool
AndroidGraphicBuffer::Bind()
{
if (!EnsureInitialized())
return false;
if (!EnsureEGLImage()) {
LOG("No valid EGLImage!");
return false;
}
clearGLError();
sGLFunctions.fImageTargetTexture2DOES(GL_TEXTURE_2D, mEGLImage);
return ensureNoGLError("glEGLImageTargetTexture2DOES");
}
static const char* const sAllowedBoards[] = {
"venus2", // Motorola Droid Pro
"tuna", // Galaxy Nexus
"omap4sdp", // Amazon Kindle Fire
"droid2", // Motorola Droid 2
"targa", // Motorola Droid Bionic
"spyder", // Motorola Razr
"shadow", // Motorola Droid X
"SGH-I897", // Samsung Galaxy S
"GT-I9100", // Samsung Galaxy SII
"sgh-i997", // Samsung Infuse 4G
"herring", // Samsung Nexus S
"sgh-t839", // Samsung Sidekick 4G
NULL
};
bool
AndroidGraphicBuffer::IsBlacklisted()
{
nsAutoString board;
if (!AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "BOARD", board))
return true;
const char* boardUtf8 = NS_ConvertUTF16toUTF8(board).get();
if (Preferences::GetBool("direct-texture.force.enabled", false)) {
LOG("allowing board '%s' due to prefs override", boardUtf8);
return false;
}
if (Preferences::GetBool("direct-texture.force.disabled", false)) {
LOG("disallowing board '%s' due to prefs override", boardUtf8);
return true;
}
// FIXME: (Bug 722605) use something better than a linear search
for (int i = 0; sAllowedBoards[i]; i++) {
if (board.Find(sAllowedBoards[i]) >= 0) {
LOG("allowing board '%s' based on '%s'\n", boardUtf8, sAllowedBoards[i]);
return false;
}
}
LOG("disallowing board: %s\n", boardUtf8);
return true;
}
} /* mozilla */