| /* | |
| * 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 | |
| android_native_buffer_t* 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)); | |
| 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 |