/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <stdlib.h> #include <stdint.h> #include <sys/types.h> #include <utils/Errors.h> #include <utils/Log.h> #include <ui/GraphicBuffer.h> #include <GLES/gl.h> #include <GLES/glext.h> #include <hardware/hardware.h> #include "clz.h" #include "DisplayHardware/DisplayHardware.h" #include "GLExtensions.h" #include "TextureManager.h" namespace android { // --------------------------------------------------------------------------- TextureManager::TextureManager() : mGLExtensions(GLExtensions::getInstance()) { } GLenum TextureManager::getTextureTarget(const Image* image) { #if defined(GL_OES_EGL_image_external) switch (image->target) { case Texture::TEXTURE_EXTERNAL: return GL_TEXTURE_EXTERNAL_OES; } #endif return GL_TEXTURE_2D; } status_t TextureManager::initTexture(Texture* texture) { if (texture->name != -1UL) return INVALID_OPERATION; GLuint textureName = -1; glGenTextures(1, &textureName); texture->name = textureName; texture->width = 0; texture->height = 0; const GLenum target = GL_TEXTURE_2D; glBindTexture(target, textureName); glTexParameterx(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterx(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); return NO_ERROR; } status_t TextureManager::initTexture(Image* pImage, int32_t format) { if (pImage->name != -1UL) return INVALID_OPERATION; GLuint textureName = -1; glGenTextures(1, &textureName); pImage->name = textureName; pImage->width = 0; pImage->height = 0; GLenum target = GL_TEXTURE_2D; #if defined(GL_OES_EGL_image_external) if (GLExtensions::getInstance().haveTextureExternal()) { if (format && isYuvFormat(format)) { target = GL_TEXTURE_EXTERNAL_OES; pImage->target = Texture::TEXTURE_EXTERNAL; } } #endif glBindTexture(target, textureName); glTexParameterx(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterx(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); return NO_ERROR; } bool TextureManager::isSupportedYuvFormat(int format) { switch (format) { case HAL_PIXEL_FORMAT_YV12: return true; } return false; } bool TextureManager::isYuvFormat(int format) { switch (format) { // supported YUV formats case HAL_PIXEL_FORMAT_YV12: // Legacy/deprecated YUV formats case HAL_PIXEL_FORMAT_YCbCr_422_SP: case HAL_PIXEL_FORMAT_YCrCb_420_SP: case HAL_PIXEL_FORMAT_YCbCr_422_I: return true; } // Any OEM format needs to be considered if (format>=0x100 && format<=0x1FF) return true; return false; } status_t TextureManager::initEglImage(Image* pImage, EGLDisplay dpy, const sp<GraphicBuffer>& buffer) { status_t err = NO_ERROR; if (!pImage->dirty) return err; // free the previous image if (pImage->image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(dpy, pImage->image); pImage->image = EGL_NO_IMAGE_KHR; } // construct an EGL_NATIVE_BUFFER_ANDROID ANativeWindowBuffer* clientBuf = buffer->getNativeBuffer(); // create the new EGLImageKHR const EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE, EGL_NONE }; pImage->image = eglCreateImageKHR( dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, (EGLClientBuffer)clientBuf, attrs); if (pImage->image != EGL_NO_IMAGE_KHR) { if (pImage->name == -1UL) { initTexture(pImage, buffer->format); } const GLenum target = getTextureTarget(pImage); glBindTexture(target, pImage->name); glEGLImageTargetTexture2DOES(target, (GLeglImageOES)pImage->image); GLint error = glGetError(); if (error != GL_NO_ERROR) { LOGE("glEGLImageTargetTexture2DOES(%p) failed err=0x%04x", pImage->image, error); err = INVALID_OPERATION; } else { // Everything went okay! pImage->dirty = false; pImage->width = clientBuf->width; pImage->height = clientBuf->height; } } else { LOGE("eglCreateImageKHR() failed. err=0x%4x", eglGetError()); err = INVALID_OPERATION; } return err; } status_t TextureManager::loadTexture(Texture* texture, const Region& dirty, const GGLSurface& t) { if (texture->name == -1UL) { status_t err = initTexture(texture); LOGE_IF(err, "loadTexture failed in initTexture (%s)", strerror(err)); if (err != NO_ERROR) return err; } if (texture->target != Texture::TEXTURE_2D) return INVALID_OPERATION; glBindTexture(GL_TEXTURE_2D, texture->name); /* * In OpenGL ES we can't specify a stride with glTexImage2D (however, * GL_UNPACK_ALIGNMENT is a limited form of stride). * So if the stride here isn't representable with GL_UNPACK_ALIGNMENT, we * need to do something reasonable (here creating a bigger texture). * * extra pixels = (((stride - width) * pixelsize) / GL_UNPACK_ALIGNMENT); * * This situation doesn't happen often, but some h/w have a limitation * for their framebuffer (eg: must be multiple of 8 pixels), and * we need to take that into account when using these buffers as * textures. * * This should never be a problem with POT textures */ int unpack = __builtin_ctz(t.stride * bytesPerPixel(t.format)); unpack = 1 << ((unpack > 3) ? 3 : unpack); glPixelStorei(GL_UNPACK_ALIGNMENT, unpack); /* * round to POT if needed */ if (!mGLExtensions.haveNpot()) { texture->NPOTAdjust = true; } if (texture->NPOTAdjust) { // find the smallest power-of-two that will accommodate our surface texture->potWidth = 1 << (31 - clz(t.width)); texture->potHeight = 1 << (31 - clz(t.height)); if (texture->potWidth < t.width) texture->potWidth <<= 1; if (texture->potHeight < t.height) texture->potHeight <<= 1; texture->wScale = float(t.width) / texture->potWidth; texture->hScale = float(t.height) / texture->potHeight; } else { texture->potWidth = t.width; texture->potHeight = t.height; } Rect bounds(dirty.bounds()); GLvoid* data = 0; if (texture->width != t.width || texture->height != t.height) { texture->width = t.width; texture->height = t.height; // texture size changed, we need to create a new one bounds.set(Rect(t.width, t.height)); if (t.width == texture->potWidth && t.height == texture->potHeight) { // we can do it one pass data = t.data; } if (t.format == HAL_PIXEL_FORMAT_RGB_565) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture->potWidth, texture->potHeight, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data); } else if (t.format == HAL_PIXEL_FORMAT_RGBA_4444) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->potWidth, texture->potHeight, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data); } else if (t.format == HAL_PIXEL_FORMAT_RGBA_8888 || t.format == HAL_PIXEL_FORMAT_RGBX_8888) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->potWidth, texture->potHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); } else if (isSupportedYuvFormat(t.format)) { // just show the Y plane of YUV buffers glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, texture->potWidth, texture->potHeight, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); } else { // oops, we don't handle this format! LOGE("texture=%d, using format %d, which is not " "supported by the GL", texture->name, t.format); } } if (!data) { if (t.format == HAL_PIXEL_FORMAT_RGB_565) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bounds.top, t.width, bounds.height(), GL_RGB, GL_UNSIGNED_SHORT_5_6_5, t.data + bounds.top*t.stride*2); } else if (t.format == HAL_PIXEL_FORMAT_RGBA_4444) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bounds.top, t.width, bounds.height(), GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, t.data + bounds.top*t.stride*2); } else if (t.format == HAL_PIXEL_FORMAT_RGBA_8888 || t.format == HAL_PIXEL_FORMAT_RGBX_8888) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bounds.top, t.width, bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, t.data + bounds.top*t.stride*4); } else if (isSupportedYuvFormat(t.format)) { // just show the Y plane of YUV buffers glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bounds.top, t.width, bounds.height(), GL_LUMINANCE, GL_UNSIGNED_BYTE, t.data + bounds.top*t.stride); } } return NO_ERROR; } void TextureManager::activateTexture(const Texture& texture, bool filter) { const GLenum target = getTextureTarget(&texture); if (target == GL_TEXTURE_2D) { glBindTexture(GL_TEXTURE_2D, texture.name); glEnable(GL_TEXTURE_2D); #if defined(GL_OES_EGL_image_external) if (GLExtensions::getInstance().haveTextureExternal()) { glDisable(GL_TEXTURE_EXTERNAL_OES); } } else { glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture.name); glEnable(GL_TEXTURE_EXTERNAL_OES); glDisable(GL_TEXTURE_2D); #endif } if (filter) { glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } else { glTexParameterx(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterx(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } } void TextureManager::deactivateTextures() { glDisable(GL_TEXTURE_2D); #if defined(GL_OES_EGL_image_external) if (GLExtensions::getInstance().haveTextureExternal()) { glDisable(GL_TEXTURE_EXTERNAL_OES); } #endif } // --------------------------------------------------------------------------- }; // namespace android