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