/* * Copyright 2010, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "GLUtils" #define LOG_NDEBUG 1 #include "config.h" #include "GLUtils.h" #if USE(ACCELERATED_COMPOSITING) #include "AndroidLog.h" #include "BaseRenderer.h" #include "TextureInfo.h" #include "Tile.h" #include "TilesManager.h" #include "TransferQueue.h" #include #include #include // We will limit GL error logging for LOG_VOLUME_PER_CYCLE times every // LOG_VOLUME_PER_CYCLE seconds. #define LOG_CYCLE 30.0 #define LOG_VOLUME_PER_CYCLE 20 struct ANativeWindowBuffer; namespace WebCore { using namespace android; ///////////////////////////////////////////////////////////////////////////////////////// // Matrix utilities ///////////////////////////////////////////////////////////////////////////////////////// void GLUtils::toGLMatrix(GLfloat* flattened, const TransformationMatrix& m) { flattened[0] = m.m11(); // scaleX flattened[1] = m.m12(); // skewY flattened[2] = m.m13(); flattened[3] = m.m14(); // persp0 flattened[4] = m.m21(); // skewX flattened[5] = m.m22(); // scaleY flattened[6] = m.m23(); flattened[7] = m.m24(); // persp1 flattened[8] = m.m31(); flattened[9] = m.m32(); flattened[10] = m.m33(); flattened[11] = m.m34(); flattened[12] = m.m41(); // transX flattened[13] = m.m42(); // transY flattened[14] = m.m43(); flattened[15] = m.m44(); // persp2 } void GLUtils::toSkMatrix(SkMatrix& matrix, const TransformationMatrix& m) { matrix[0] = m.m11(); // scaleX matrix[1] = m.m21(); // skewX matrix[2] = m.m41(); // transX matrix[3] = m.m12(); // skewY matrix[4] = m.m22(); // scaleY matrix[5] = m.m42(); // transY matrix[6] = m.m14(); // persp0 matrix[7] = m.m24(); // persp1 matrix[8] = m.m44(); // persp2 } void GLUtils::setOrthographicMatrix(TransformationMatrix& ortho, float left, float top, float right, float bottom, float nearZ, float farZ) { float deltaX = right - left; float deltaY = top - bottom; float deltaZ = farZ - nearZ; if (!deltaX || !deltaY || !deltaZ) return; ortho.setM11(2.0f / deltaX); ortho.setM41(-(right + left) / deltaX); ortho.setM22(2.0f / deltaY); ortho.setM42(-(top + bottom) / deltaY); ortho.setM33(-2.0f / deltaZ); ortho.setM43(-(nearZ + farZ) / deltaZ); } bool GLUtils::has3dTransform(const TransformationMatrix& matrix) { return matrix.m13() != 0 || matrix.m23() != 0 || matrix.m31() != 0 || matrix.m32() != 0 || matrix.m33() != 1 || matrix.m34() != 0 || matrix.m43() != 0; } ///////////////////////////////////////////////////////////////////////////////////////// // GL & EGL error checks ///////////////////////////////////////////////////////////////////////////////////////// double GLUtils::m_previousLogTime = 0; int GLUtils::m_currentLogCounter = 0; bool GLUtils::allowGLLog() { if (m_currentLogCounter < LOG_VOLUME_PER_CYCLE) { m_currentLogCounter++; return true; } // when we are in Log cycle and over the log limit, just return false double currentTime = WTF::currentTime(); double delta = currentTime - m_previousLogTime; bool inLogCycle = (delta <= LOG_CYCLE) && (delta > 0); if (inLogCycle) return false; // When we are out of Log Cycle and over the log limit, we need to reset // the counter and timer. m_previousLogTime = currentTime; m_currentLogCounter = 0; return false; } static void crashIfOOM(GLint errorCode) { const GLint OOM_ERROR_CODE = 0x505; if (errorCode == OOM_ERROR_CODE) { ALOGE("ERROR: Fatal OOM detected."); CRASH(); } } void GLUtils::checkEglError(const char* op, EGLBoolean returnVal) { if (returnVal != EGL_TRUE) { #ifndef DEBUG if (allowGLLog()) #endif ALOGE("EGL ERROR - %s() returned %d\n", op, returnVal); } for (EGLint error = eglGetError(); error != EGL_SUCCESS; error = eglGetError()) { #ifndef DEBUG if (allowGLLog()) #endif ALOGE("after %s() eglError (0x%x)\n", op, error); crashIfOOM(error); } } bool GLUtils::checkGlError(const char* op) { bool ret = false; for (GLint error = glGetError(); error; error = glGetError()) { #ifndef DEBUG if (allowGLLog()) #endif ALOGE("GL ERROR - after %s() glError (0x%x)\n", op, error); crashIfOOM(error); ret = true; } return ret; } bool GLUtils::checkGlErrorOn(void* p, const char* op) { bool ret = false; for (GLint error = glGetError(); error; error = glGetError()) { #ifndef DEBUG if (allowGLLog()) #endif ALOGE("GL ERROR on %x - after %s() glError (0x%x)\n", p, op, error); crashIfOOM(error); ret = true; } return ret; } void GLUtils::checkSurfaceTextureError(const char* functionName, int status) { if (status != NO_ERROR) { #ifndef DEBUG if (allowGLLog()) #endif ALOGE("ERROR at calling %s status is (%d)", functionName, status); } } ///////////////////////////////////////////////////////////////////////////////////////// // GL & EGL extension checks ///////////////////////////////////////////////////////////////////////////////////////// bool GLUtils::isEGLImageSupported() { const char* eglExtensions = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS); const char* glExtensions = reinterpret_cast(glGetString(GL_EXTENSIONS)); return eglExtensions && glExtensions && strstr(eglExtensions, "EGL_KHR_image_base") && strstr(eglExtensions, "EGL_KHR_gl_texture_2D_image") && strstr(glExtensions, "GL_OES_EGL_image"); } bool GLUtils::isEGLFenceSyncSupported() { const char* eglExtensions = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS); return eglExtensions && strstr(eglExtensions, "EGL_KHR_fence_sync"); } ///////////////////////////////////////////////////////////////////////////////////////// // Textures utilities ///////////////////////////////////////////////////////////////////////////////////////// static GLenum getInternalFormat(SkBitmap::Config config) { switch (config) { case SkBitmap::kA8_Config: return GL_ALPHA; case SkBitmap::kARGB_4444_Config: return GL_RGBA; case SkBitmap::kARGB_8888_Config: return GL_RGBA; case SkBitmap::kRGB_565_Config: return GL_RGB; default: return -1; } } static GLenum getType(SkBitmap::Config config) { switch (config) { case SkBitmap::kA8_Config: return GL_UNSIGNED_BYTE; case SkBitmap::kARGB_4444_Config: return GL_UNSIGNED_SHORT_4_4_4_4; case SkBitmap::kARGB_8888_Config: return GL_UNSIGNED_BYTE; case SkBitmap::kIndex8_Config: return -1; // No type for compressed data. case SkBitmap::kRGB_565_Config: return GL_UNSIGNED_SHORT_5_6_5; default: return -1; } } static EGLConfig defaultPbufferConfig(EGLDisplay display) { EGLConfig config; EGLint numConfigs; static const EGLint configAttribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; eglChooseConfig(display, configAttribs, &config, 1, &numConfigs); GLUtils::checkEglError("eglPbufferConfig"); if (numConfigs != 1) ALOGI("eglPbufferConfig failed (%d)\n", numConfigs); return config; } static EGLSurface createPbufferSurface(EGLDisplay display, const EGLConfig& config, EGLint* errorCode) { const EGLint attribList[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; EGLSurface surface = eglCreatePbufferSurface(display, config, attribList); if (errorCode) *errorCode = eglGetError(); else GLUtils::checkEglError("eglCreatePbufferSurface"); if (surface == EGL_NO_SURFACE) return EGL_NO_SURFACE; return surface; } void GLUtils::deleteTexture(GLuint* texture) { glDeleteTextures(1, texture); GLUtils::checkGlError("glDeleteTexture"); *texture = 0; } GLuint GLUtils::createSampleColorTexture(int r, int g, int b) { GLuint texture; glGenTextures(1, &texture); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); GLubyte pixels[4 *3] = { r, g, b, r, g, b, r, g, b, r, g, b }; glBindTexture(GL_TEXTURE_2D, texture); GLUtils::checkGlError("glBindTexture"); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels); GLUtils::checkGlError("glTexImage2D"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); return texture; } GLuint GLUtils::createSampleTexture() { GLuint texture; glGenTextures(1, &texture); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); GLubyte pixels[4 *3] = { 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0 }; glBindTexture(GL_TEXTURE_2D, texture); GLUtils::checkGlError("glBindTexture"); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels); GLUtils::checkGlError("glTexImage2D"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); return texture; } GLuint GLUtils::createTileGLTexture(int width, int height) { GLuint texture; glGenTextures(1, &texture); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); GLubyte* pixels = 0; #ifdef DEBUG int length = width * height * 4; pixels = new GLubyte[length]; for (int i = 0; i < length; i++) pixels[i] = i % 256; #endif glBindTexture(GL_TEXTURE_2D, texture); GLUtils::checkGlError("glBindTexture"); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); GLUtils::checkGlError("glTexImage2D"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #ifdef DEBUG delete pixels; #endif return texture; } bool GLUtils::isPureColorBitmap(const SkBitmap& bitmap, Color& pureColor) { // If the bitmap is the pure color, skip the transfer step, and update the Tile Info. // This check is taking < 1ms if we do full bitmap check per tile. // TODO: use the SkPicture to determine whether or not a tile is single color. TRACE_METHOD(); pureColor = Color(Color::transparent); bitmap.lockPixels(); bool sameColor = true; int bitmapWidth = bitmap.width(); // Create a row of pure color using the first pixel. // TODO: improve the perf here, by either picking a random pixel, or // creating an array of rows with pre-defined commonly used color, add // smart LUT to speed things up if possible. int* firstPixelPtr = static_cast (bitmap.getPixels()); int* pixelsRow = new int[bitmapWidth]; for (int i = 0; i < bitmapWidth; i++) pixelsRow[i] = (*firstPixelPtr); // Then compare the pure color row with each row of the bitmap. for (int j = 0; j < bitmap.height(); j++) { if (memcmp(pixelsRow, &firstPixelPtr[bitmapWidth * j], 4 * bitmapWidth)) { sameColor = false; break; } } delete pixelsRow; pixelsRow = 0; if (sameColor) { unsigned char* rgbaPtr = static_cast(bitmap.getPixels()); pureColor = Color(rgbaPtr[0], rgbaPtr[1], rgbaPtr[2], rgbaPtr[3]); ALOGV("sameColor tile found , %x at (%d, %d, %d, %d)", *firstPixelPtr, rgbaPtr[0], rgbaPtr[1], rgbaPtr[2], rgbaPtr[3]); } bitmap.unlockPixels(); return sameColor; } // Return true when the tile is pure color. bool GLUtils::skipTransferForPureColor(const TileRenderInfo* renderInfo, const SkBitmap& bitmap) { bool skipTransfer = false; Tile* tilePtr = renderInfo->baseTile; if (tilePtr) { TileTexture* tileTexture = tilePtr->backTexture(); // Check the bitmap, and make everything ready here. if (tileTexture && renderInfo->isPureColor) { // update basetile's info // Note that we are skipping the whole TransferQueue. renderInfo->textureInfo->m_width = bitmap.width(); renderInfo->textureInfo->m_height = bitmap.height(); renderInfo->textureInfo->m_internalFormat = GL_RGBA; TilesManager::instance()->transferQueue()->addItemInPureColorQueue(renderInfo); skipTransfer = true; } } return skipTransfer; } void GLUtils::paintTextureWithBitmap(const TileRenderInfo* renderInfo, SkBitmap& bitmap) { if (!renderInfo) return; const SkSize& requiredSize = renderInfo->tileSize; TextureInfo* textureInfo = renderInfo->textureInfo; if (skipTransferForPureColor(renderInfo, bitmap)) return; if (requiredSize.equals(textureInfo->m_width, textureInfo->m_height)) GLUtils::updateQueueWithBitmap(renderInfo, bitmap); else { if (!requiredSize.equals(bitmap.width(), bitmap.height())) { ALOGV("The bitmap size (%d,%d) does not equal the texture size (%d,%d)", bitmap.width(), bitmap.height(), requiredSize.width(), requiredSize.height()); } GLUtils::updateQueueWithBitmap(renderInfo, bitmap); textureInfo->m_width = bitmap.width(); textureInfo->m_height = bitmap.height(); textureInfo->m_internalFormat = GL_RGBA; } } void GLUtils::updateQueueWithBitmap(const TileRenderInfo* renderInfo, SkBitmap& bitmap) { if (!renderInfo || !renderInfo->textureInfo || !renderInfo->baseTile) return; TilesManager::instance()->transferQueue()->updateQueueWithBitmap(renderInfo, bitmap); } bool GLUtils::updateSharedSurfaceTextureWithBitmap(ANativeWindow* anw, const SkBitmap& bitmap) { TRACE_METHOD(); SkAutoLockPixels alp(bitmap); if (!bitmap.getPixels()) return false; ANativeWindow_Buffer buffer; if (ANativeWindow_lock(anw, &buffer, 0)) return false; if (buffer.width < bitmap.width() || buffer.height < bitmap.height()) { ALOGW("bitmap (%dx%d) too large for buffer (%dx%d)!", bitmap.width(), bitmap.height(), buffer.width, buffer.height); ANativeWindow_unlockAndPost(anw); return false; } uint8_t* img = (uint8_t*)buffer.bits; int row; int bpp = 4; // Now we only deal with RGBA8888 format. bitmap.lockPixels(); uint8_t* bitmapOrigin = static_cast(bitmap.getPixels()); if (buffer.stride != bitmap.width()) // Copied line by line since we need to handle the offsets and stride. for (row = 0 ; row < bitmap.height(); row ++) { uint8_t* dst = &(img[buffer.stride * row * bpp]); uint8_t* src = &(bitmapOrigin[bitmap.width() * row * bpp]); memcpy(dst, src, bpp * bitmap.width()); } else memcpy(img, bitmapOrigin, bpp * bitmap.width() * bitmap.height()); bitmap.unlockPixels(); ANativeWindow_unlockAndPost(anw); return true; } void GLUtils::createTextureWithBitmap(GLuint texture, const SkBitmap& bitmap, GLint filter) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glBindTexture(GL_TEXTURE_2D, texture); GLUtils::checkGlError("glBindTexture"); SkBitmap::Config config = bitmap.getConfig(); int internalformat = getInternalFormat(config); int type = getType(config); bitmap.lockPixels(); glTexImage2D(GL_TEXTURE_2D, 0, internalformat, bitmap.width(), bitmap.height(), 0, internalformat, type, bitmap.getPixels()); bitmap.unlockPixels(); if (GLUtils::checkGlError("glTexImage2D")) { #ifndef DEBUG if (allowGLLog()) #endif ALOGE("GL ERROR: glTexImage2D parameters are : textureId %d," " bitmap.width() %d, bitmap.height() %d," " internalformat 0x%x, type 0x%x, bitmap.getPixels() %p", texture, bitmap.width(), bitmap.height(), internalformat, type, bitmap.getPixels()); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); } void GLUtils::updateTextureWithBitmap(GLuint texture, const SkBitmap& bitmap, const IntRect& inval, GLint filter) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glBindTexture(GL_TEXTURE_2D, texture); GLUtils::checkGlError("glBindTexture"); SkBitmap::Config config = bitmap.getConfig(); int internalformat = getInternalFormat(config); int type = getType(config); bitmap.lockPixels(); if (inval.isEmpty()) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), internalformat, type, bitmap.getPixels()); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, inval.x(), inval.y(), inval.width(), inval.height(), internalformat, type, bitmap.getPixels()); } bitmap.unlockPixels(); if (GLUtils::checkGlError("glTexSubImage2D")) { #ifndef DEBUG if (allowGLLog()) #endif ALOGE("GL ERROR: glTexSubImage2D parameters are : textureId %d," " bitmap.width() %d, bitmap.height() %d," " internalformat 0x%x, type 0x%x, bitmap.getPixels() %p", texture, bitmap.width(), bitmap.height(), internalformat, type, bitmap.getPixels()); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); } void GLUtils::createEGLImageFromTexture(GLuint texture, EGLImageKHR* image) { EGLClientBuffer buffer = reinterpret_cast(texture); static const EGLint attr[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; *image = eglCreateImageKHR(eglGetCurrentDisplay(), eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR, buffer, attr); GLUtils::checkEglError("eglCreateImage", (*image != EGL_NO_IMAGE_KHR)); } void GLUtils::createTextureFromEGLImage(GLuint texture, EGLImageKHR image, GLint filter) { glBindTexture(GL_TEXTURE_2D, texture); GLUtils::checkGlError("glBindTexture"); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); } void GLUtils::convertToTransformationMatrix(const float* matrix, TransformationMatrix& transformMatrix) { transformMatrix.setMatrix( matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6], matrix[7], matrix[8], matrix[9], matrix[10], matrix[11], matrix[12], matrix[13], matrix[14], matrix[15]); } void GLUtils::clearBackgroundIfOpaque(const Color* backgroundColor) { if (!backgroundColor->hasAlpha()) { if (TilesManager::instance()->invertedScreen()) { float color = 1.0 - ((((float) backgroundColor->red() / 255.0) + ((float) backgroundColor->green() / 255.0) + ((float) backgroundColor->blue() / 255.0)) / 3.0); glClearColor(color, color, color, 1); } else { glClearColor((float)backgroundColor->red() / 255.0, (float)backgroundColor->green() / 255.0, (float)backgroundColor->blue() / 255.0, 1); } glClear(GL_COLOR_BUFFER_BIT); } } bool GLUtils::deepCopyBitmapSubset(const SkBitmap& sourceBitmap, SkBitmap& subset, int leftOffset, int topOffset) { sourceBitmap.lockPixels(); subset.lockPixels(); char* srcPixels = (char*) sourceBitmap.getPixels(); char* dstPixels = (char*) subset.getPixels(); if (!dstPixels || !srcPixels || !subset.lockPixelsAreWritable()) { ALOGD("no pixels :( %p, %p (writable=%d)", srcPixels, dstPixels, subset.lockPixelsAreWritable()); subset.unlockPixels(); sourceBitmap.unlockPixels(); return false; } int srcRowSize = sourceBitmap.rowBytes(); int destRowSize = subset.rowBytes(); for (int i = 0; i < subset.height(); i++) { int srcOffset = (i + topOffset) * srcRowSize; srcOffset += (leftOffset * sourceBitmap.bytesPerPixel()); int dstOffset = i * destRowSize; memcpy(dstPixels + dstOffset, srcPixels + srcOffset, destRowSize); } subset.unlockPixels(); sourceBitmap.unlockPixels(); return true; } } // namespace WebCore #endif // USE(ACCELERATED_COMPOSITING)