diff -r 000000000000 -r bde4ae8d615e os/graphics/m3g/m3gcore11/src/m3g_image.inl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/os/graphics/m3g/m3gcore11/src/m3g_image.inl Fri Jun 15 03:10:57 2012 +0200 @@ -0,0 +1,374 @@ +/* +* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of the License "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: Image implementation for the OpenGL ES API +* +*/ + + +/*! + * \internal + * \file + * \brief Image implementation for the OpenGL ES API + * + * $Id: m3g_image.inl,v 1.11 2006/03/15 13:26:36 roimela Exp $ + */ + +#if defined(M3G_NGL_TEXTURE_API) +# error This file is for the OES API only +#endif + +/*---------------------------------------------------------------------- + * Data types + *--------------------------------------------------------------------*/ + +/*! + * \internal + * \brief Additional data for a "large" image + * + * A large image is an image that is larger than the maximum texture + * size. They basically get split into a bunch of smaller textures so + * that we can use them for drawing backgrounds via OpenGL ES. Some + * optimization is done to make sure we don't waste excessive amounts + * of memory in doing so. + */ +struct LargeImageImpl +{ + M3Gsizei tilesX, tilesY; + M3Gint tileWidth, tileHeight; + M3Gbool dirty; + + /* The size of the tile texture name array is set dynamically upon + * allocation, and it *must* be the last field in the + * structure! */ + GLuint tileNames[1]; +}; + +/*---------------------------------------------------------------------- + * Private functions + *--------------------------------------------------------------------*/ + +/*! + * \internal + * \brief Queries whether an image can be paletted internally or not + */ +static M3Gbool m3gSupportedPaletteFormat(M3GImageFormat format) +{ + return (format == M3G_RGB || format == M3G_RGBA); +} + +/*---------------------------------------------------------------------- + * Internal functions + *--------------------------------------------------------------------*/ + +/*! + * \internal + * \brief Matches an M3G pixel format with a GL texture format + */ +static GLenum m3gGetGLFormat(M3GPixelFormat format) +{ + switch (format) { + case M3G_A8: + return GL_ALPHA; + case M3G_L8: + return GL_LUMINANCE; + case M3G_LA8: + return GL_LUMINANCE_ALPHA; + case M3G_RGB8: + case M3G_RGB8_32: + case M3G_BGR8_32: + return GL_RGB; + case M3G_RGBA8: + case M3G_BGRA8: + case M3G_ARGB8: + return GL_RGBA; + case M3G_PALETTE8_RGB8: + return GL_PALETTE8_RGB8_OES; + case M3G_PALETTE8_RGBA8: + return GL_PALETTE8_RGBA8_OES; + default: + return 0; + } +} + + +/*! + * \internal + * \brief Destroys the additional data of a "large" image + * + * This can be called to save (OpenGL) memory at any time -- the data + * will be recreated when necessary. Performance will obviously + * suffer, though. + */ +static void m3gDestroyLargeImage(Image *img) +{ + LargeImage *lrg = img->large; + M3G_VALIDATE_MEMBLOCK(lrg); + + m3gDeleteGLTextures(M3G_INTERFACE(img), + lrg->tilesX * lrg->tilesY, lrg->tileNames); + m3gFree(M3G_INTERFACE(img), img->large); + + img->large = NULL; +} + +/*! + * \internal + * \brief Binds an image as an OpenGL texture object + * + * The image is bound to the active texture unit, which must be + * selected outside of this function. + */ +static void m3gBindTextureObject(Image *img, M3Gbool mipmap) +{ + Interface *m3g; + M3G_VALIDATE_OBJECT(img); + m3g = M3G_INTERFACE(img); + M3G_ASSERT(img->special == 0); + M3G_ASSERT_NO_LOCK(m3g); + M3G_ASSERT_GL; + + /* Bind the next available texture object; create a new one if it + * doesn't exist yet. */ + { + if (!img->texObject) { + GLint err; + glGenTextures(1, &img->texObject); + err = glGetError(); + if (err == GL_OUT_OF_MEMORY) { + m3gRaiseError(M3G_INTERFACE(img), M3G_OUT_OF_MEMORY); + return; + } + M3G_ASSERT(err == GL_NO_ERROR); + M3G_LOG1(M3G_LOG_OBJECTS, "New GL texture object 0x%08X\n", + (unsigned) img->texObject); + img->dirty = M3G_TRUE; + } + glBindTexture(GL_TEXTURE_2D, img->texObject); + } + + /* Upload the texture image to OpenGL if the one in the texture + * object isn't up to date */ + + if (img->dirty || (mipmap && img->mipmapsDirty)) { + + M3Gubyte *pixels = ((M3Gubyte *)m3gMapObject(m3g, img->data)); + + /* Reload the level 0 image if dirty. Note that paletted + * textures are loaded as compressed, and the mipmap dirty + * flag is only raised for non-paletted textures. */ + + if (img->dirty) { + M3G_ASSERT_PTR(pixels); + if (img->paletteBytes > 0) { + M3G_ASSERT(img->glFormat == GL_PALETTE8_RGBA8_OES + || img->glFormat == GL_PALETTE8_RGB8_OES); + M3G_ASSERT(mipmap == M3G_FALSE); + glCompressedTexImage2D(GL_TEXTURE_2D, + 0, + img->glFormat, + img->width, img->height, + 0, + img->width * img->height + img->paletteBytes, + pixels); + } + else { +# if defined(M3G_GL_ES_1_1) + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, + mipmap ? GL_TRUE : GL_FALSE); +# endif + glTexImage2D(GL_TEXTURE_2D, + 0, + img->glFormat, + img->width, img->height, + 0, + img->glFormat, + GL_UNSIGNED_BYTE, + pixels); +# if defined(M3G_GL_ES_1_1) + img->mipmapsDirty = M3G_FALSE; +# else + img->mipmapsDirty = M3G_TRUE; +# endif + } + m3gUnmapObject(m3g, img->data); + img->dirty = M3G_FALSE; + } + + /* Regenerate mipmap levels if necessary; also regenerate if + * the image will never change again, as this allows us to + * free the user memory copy of the image and keep only the + * mipmap pyramid in OpenGL memory, saving some in total */ +# if !defined(M3G_GL_ES_1_1) + if (img->mipmapsDirty && (mipmap || (img->flags & M3G_DYNAMIC) == 0)) { + int i, n; + M3Gint w, h; + const M3Gubyte *src; + M3Gubyte *temp; + + M3G_ASSERT(!img->dirty); + + w = img->width; + h = img->height; + n = m3gGetNumMipmapLevels(w, h); + + temp = m3gAllocTemp(m3g, + w * h * m3gBytesPerPixel(img->internalFormat)); + if (!temp) { + return; /* automatic out of memory */ + } + src = ((M3Gubyte *)m3gMapObject(m3g, img->data)); + + for (i = 1; i < n; ++i) { + m3gDownsample(img->internalFormat, + src, + &w, &h, + temp); + glTexImage2D(GL_TEXTURE_2D, + i, + img->glFormat, + w, h, + 0, + img->glFormat, + GL_UNSIGNED_BYTE, + temp); + src = temp; + } + + m3gUnmapObject(m3g, img->data); + m3gFreeTemp(m3g); + img->mipmapsDirty = M3G_FALSE; + } +# endif /* !M3G_GL_ES_1_1 */ + + /* Free the pixel data if we can; we've uploaded mipmap + * levels, so OpenGL will keep them for us for the rest of the + * lifetime of this object */ + + if (!img->pinned && !img->mipmapsDirty) { + m3gFreeImageData(img); + } + + /* Raise out-of-memory if the OpenGL implementation ran out of + * resources */ + { + GLint err = glGetError(); + + if (err == GL_OUT_OF_MEMORY) { + m3gRaiseError(M3G_INTERFACE(img), M3G_OUT_OF_MEMORY); + } + else if (err != GL_NO_ERROR) { + M3G_ASSERT(M3G_FALSE); + } + } + } +} + +/*! + * \internal + * \brief Releases one of the texture objects bound for this image + * + * This assumes that the texture unit the image was bound to is + * current. + */ +static void m3gReleaseTextureImage(Image *img) +{ + M3G_VALIDATE_OBJECT(img); + M3G_UNREF(img); + + glBindTexture(GL_TEXTURE_2D, 0); + + M3G_ASSERT_GL; +} + +/*! + * \internal + * \brief Copies an image from the bottom left corner of the frame + * buffer + * + */ +static void m3gCopyFrameBufferImage(Image *img) +{ + Interface *m3g; + M3Gubyte *pixels; + M3G_VALIDATE_OBJECT(img); + M3G_ASSERT_GL; + + m3g = M3G_INTERFACE(img); + + { + int row; + M3Gsizei stride = img->width * m3gBytesPerPixel(img->internalFormat); + + /* An RGBA image we can copy straight into the user memory buffer */ + + if (img->internalFormat == M3G_RGBA8) { + pixels = m3gMapObject(m3g, img->data); + for (row = 0; row < img->height; ++row) { + glReadPixels(0, img->height - row - 1, + img->width, 1, + GL_RGBA, GL_UNSIGNED_BYTE, + pixels + row * stride); + } + m3gUnmapObject(m3g, img->data); + } + else { + + /* For non-RGBA images, we must do a format conversion from + * the RGBA returned by ReadPixels to the destination + * format. We do this one scanline at a time to spare memory. + */ + + M3Gubyte *temp = m3gAllocTemp(m3g, img->width * 4); + if (!temp) { + return; /* out of memory */ + } + pixels = m3gMapObject(m3g, img->data); + + for (row = 0; row < img->height; ++row) { + glReadPixels(0, img->height - row - 1, + img->width, 1, + GL_RGBA, GL_UNSIGNED_BYTE, + temp); + m3gConvertPixels(M3G_RGBA8, temp, + img->internalFormat, pixels + row * stride, + img->width); + } + m3gUnmapObject(m3g, img->data); + m3gFreeTemp(m3g); + } + } + M3G_ASSERT_GL; + + m3gInvalidateImage(img); +} + +/*! + * \internal + * \brief Draws any RGB or RGBA image into the bottom left corner of + * the frame buffer + */ +static void m3gDrawFrameBufferImage(RenderContext *ctx, const Image *img) +{ + M3G_VALIDATE_OBJECT(img); + { + const M3Gubyte *pixels = m3gMapObject(M3G_INTERFACE(img), img->data); + m3gBlitFrameBufferPixels(ctx, + 0, 0, + img->width, img->height, + img->internalFormat, + m3gGetImageStride(img), + pixels); + m3gUnmapObject(M3G_INTERFACE(img), img->data); + } +}