m3g/m3gcore11/src/m3g_image.inl
changeset 0 5d03bc08d59c
child 11 fed1595b188e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_image.inl	Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,373 @@
+/*
+* 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:
+        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);
+    }
+}