| /* |
| * 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_texture_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_texture_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: |
| case HAL_PIXEL_FORMAT_YV16: |
| return true; |
| } |
| return false; |
| } |
| |
| bool TextureManager::isYuvFormat(int format) |
| { |
| switch (format) { |
| // supported YUV formats |
| case HAL_PIXEL_FORMAT_YV12: |
| case HAL_PIXEL_FORMAT_YV16: |
| // 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: |
| case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED: |
| 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 != GL_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_texture_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_texture_external) |
| if (GLExtensions::getInstance().haveTextureExternal()) { |
| glDisable(GL_TEXTURE_EXTERNAL_OES); |
| } |
| #endif |
| } |
| |
| // --------------------------------------------------------------------------- |
| |
| }; // namespace android |