m3g/m3gcore11/src/m3g_rendercontext.inl
changeset 0 5d03bc08d59c
child 11 fed1595b188e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_rendercontext.inl	Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,1517 @@
+/*
+* 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: EGL rendering context management functions
+*
+*/
+
+
+/*!
+ * \internal
+ * \file
+ * \brief EGL rendering context management functions
+ */
+
+#if defined(M3G_NGL_CONTEXT_API)
+#   error This file is for the OES API only
+#endif
+
+#include <GLES/egl.h>
+#include "m3g_image.h"
+
+/*----------------------------------------------------------------------
+ * Private functions
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \internal
+ * \brief Queries for an EGL configuration matching given M3G format
+ * parameters
+ */
+static EGLConfig m3gQueryEGLConfig(M3Genum format,
+                                   M3Gbitmask bufferBits,
+                                   EGLint surfaceBits)
+{
+    struct { int attrib, value; } attribs[9];
+    int samples;
+
+    /* Determine color depth */
+    
+    attribs[0].attrib = EGL_RED_SIZE;
+    attribs[1].attrib = EGL_GREEN_SIZE;
+    attribs[2].attrib = EGL_BLUE_SIZE;
+    attribs[3].attrib = EGL_ALPHA_SIZE;
+        
+    switch (format) {
+    case M3G_RGB4:
+        attribs[0].value = 4;
+        attribs[1].value = 4;
+        attribs[2].value = 4;
+        attribs[3].value = 0;
+        break;
+    case M3G_RGB565:
+        attribs[0].value = 5;
+        attribs[1].value = 6;
+        attribs[2].value = 5;
+        attribs[3].value = 0;
+        break;
+    case M3G_RGB8:
+    case M3G_BGR8_32:
+        attribs[0].value = 8;
+        attribs[1].value = 8;
+        attribs[2].value = 8;
+        attribs[3].value = 0;
+        break;
+    case M3G_RGBA8:
+    case M3G_BGRA8:
+        attribs[0].value = 8;
+        attribs[1].value = 8;
+        attribs[2].value = 8;
+        attribs[3].value = 8;
+        break;
+    default:
+        return NULL;
+    }
+
+    /* Set up the depth buffer */
+    
+    attribs[4].attrib = EGL_DEPTH_SIZE;
+    attribs[4].value = (bufferBits & M3G_DEPTH_BUFFER_BIT) ? 16 : 0;
+    
+    /* Set target surface type mask */
+    
+    attribs[5].attrib = EGL_SURFACE_TYPE;
+    attribs[5].value = surfaceBits;
+
+    /* Try to get multisampling if requested */
+
+    attribs[6].attrib = EGL_SAMPLE_BUFFERS;
+    attribs[7].attrib = EGL_SAMPLES;
+    attribs[8].attrib = EGL_NONE;
+
+    /* Try 4 samples if multisampling enabled, then 2, then 1 */
+    
+    samples = (bufferBits & M3G_MULTISAMPLE_BUFFER_BIT) ? 4 : 1;
+    for ( ; samples > 0; samples >>= 1) {
+        
+        if (samples > 1) {
+            attribs[6].value = 1;
+            attribs[7].value = samples;
+        }
+        else {
+            attribs[6].value = EGL_FALSE;
+            attribs[7].value = 0;
+        }
+
+        /* Get the first matching config; according to EGL sorting
+         * rules, this should be an accelerated one if possible */
+        {
+            EGLConfig config;
+            int numConfigs;
+            eglChooseConfig(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                            (const int *) attribs,
+                            &config, 1,
+                            &numConfigs);
+            
+            M3G_ASSERT(eglGetError() == EGL_SUCCESS);
+
+            /* If we got a config, return that; otherwise, drop the
+             * number of multisampling samples and try again, or
+             * return NULL for no config if we already have zero
+             * samples */
+            
+            if (numConfigs > 0) {
+                M3G_LOG1(M3G_LOG_RENDERING, "Selected EGL config #%d\n", config);
+                return config;
+            }
+
+            if (samples == 2) {
+                M3G_LOG(M3G_LOG_WARNINGS, "Warning: multisampling not available\n");
+            }
+        }
+    }
+
+    /* No matching configuration found */
+    
+    return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Initializes EGL
+ */
+static void m3gInitializeEGL(void)
+{
+    M3G_LOG(M3G_LOG_INTERFACE, "Initializing EGL\n");
+    eglInitialize(eglGetDisplay(EGL_DEFAULT_DISPLAY), NULL, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Terminates EGL
+ */
+static void m3gTerminateEGL(void)
+{
+    M3G_LOG(M3G_LOG_INTERFACE, "Shutting down EGL\n");
+    eglTerminate(eglGetDisplay(EGL_DEFAULT_DISPLAY));
+}
+
+/*!
+ * \internal
+ * \brief Creates a new EGL context
+ */
+static EGLContext m3gCreateGLContext(M3Genum format,
+                                     M3Gbitmask bufferBits,
+                                     M3Gbitmask reqSurfaceBits,
+                                     EGLContext share,
+                                     M3Gbitmask *outSurfaceBits)
+{
+    EGLContext ctx;
+    EGLConfig config;
+
+    M3G_ASSERT((reqSurfaceBits & ~(EGL_PIXMAP_BIT|EGL_PBUFFER_BIT|EGL_WINDOW_BIT)) == 0);
+    
+    config = m3gQueryEGLConfig(format, bufferBits, reqSurfaceBits);
+    
+    if (!config || !eglGetConfigAttrib(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                                       config,
+                                       EGL_SURFACE_TYPE,
+                                       (EGLint *) outSurfaceBits)) {
+        return NULL;
+    }
+    
+    ctx = eglCreateContext(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                           config,
+                           share,
+                           NULL);
+    
+#   if defined(M3G_DEBUG)
+    {
+        EGLint err = eglGetError();
+        M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC);
+    }
+#   endif
+
+    M3G_LOG1(M3G_LOG_OBJECTS, "New GL context 0x%08X\n", (unsigned) ctx);
+    return ctx;
+}
+
+/*!
+ * \internal
+ * \brief Deletes an EGL context
+ */
+static void m3gDeleteGLContext(EGLContext ctx)
+{
+    eglDestroyContext(eglGetDisplay(EGL_DEFAULT_DISPLAY), ctx);
+#   if defined(M3G_DEBUG)
+    {
+        EGLint err = eglGetError();
+        if (err != EGL_SUCCESS) {
+            M3G_LOG1(M3G_LOG_FATAL_ERRORS, "EGL error 0x%08X\n", (unsigned) err);
+        }
+        M3G_ASSERT(err == EGL_SUCCESS);
+    }
+#   endif
+    M3G_LOG1(M3G_LOG_OBJECTS, "Destroyed GL context 0x%08X\n",
+             (unsigned) ctx);
+}
+
+    
+/*!
+ * \internal
+ * \brief Creates a new EGL window surface
+ */
+static EGLSurface m3gCreateWindowSurface(M3Genum format,
+                                         M3Gbitmask bufferBits,
+                                         M3GNativeWindow wnd)
+{
+    EGLSurface surf;
+    EGLConfig config = m3gQueryEGLConfig(format, bufferBits, EGL_WINDOW_BIT);
+    
+    if (!config) {
+        return NULL;
+    }
+
+    surf = eglCreateWindowSurface(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                                  config,
+                                  (NativeWindowType) wnd,
+                                  NULL);
+
+#   if defined(M3G_DEBUG)
+    {
+        EGLint err = eglGetError();
+        M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC);
+    }
+#   endif
+
+    if (surf != EGL_NO_SURFACE) {
+        M3G_LOG1(M3G_LOG_OBJECTS, "New GL window surface 0x%08X\n",
+                 (unsigned) surf);
+        return surf;
+    }
+    return NULL;
+}
+
+
+/*!
+ * \internal
+ * \brief Creates a new EGL pixmap surface
+ */
+static EGLSurface m3gCreateBitmapSurface(M3Genum format,
+                                         M3Gbitmask bufferBits,
+                                         M3GNativeBitmap bmp)
+{
+    EGLSurface surf;
+    EGLConfig config = m3gQueryEGLConfig(format, bufferBits, EGL_PIXMAP_BIT);
+    
+    if (!config) {
+        return NULL;
+    }
+    
+    surf = eglCreatePixmapSurface(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                                  config,
+                                  (NativePixmapType) bmp,
+                                  NULL);
+
+#   if defined(M3G_DEBUG)
+    {
+        EGLint err = eglGetError();
+        M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC);
+    }
+#   endif
+    
+    if (surf != EGL_NO_SURFACE) {
+        M3G_LOG1(M3G_LOG_OBJECTS, "New GL pixmap surface 0x%08X\n",
+                 (unsigned) surf);
+        return surf;
+    }
+    return NULL;
+}
+
+
+/*!
+ * \internal
+ * \brief Creates a new PBuffer
+ */
+static EGLSurface m3gCreatePBufferSurface(M3Genum format,
+                                          M3Gbitmask bufferBits,
+                                          M3Gint width, M3Gint height)
+{
+    EGLSurface surf;
+    EGLConfig config;
+    EGLint attrib[5];
+    
+    attrib[0] = EGL_WIDTH;
+    attrib[1] = width;
+    attrib[2] = EGL_HEIGHT;
+    attrib[3] = height;
+    attrib[4] = EGL_NONE;
+    
+    config = m3gQueryEGLConfig(format, bufferBits, EGL_PBUFFER_BIT);
+    if (!config) {
+        return NULL;
+    }
+
+    surf = eglCreatePbufferSurface(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                                   config,
+                                   attrib);
+#   if defined(M3G_DEBUG)
+    {
+        EGLint err = eglGetError();
+        M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC);
+    }
+#   endif
+                                              
+    if (surf != EGL_NO_SURFACE) {
+        M3G_LOG1(M3G_LOG_OBJECTS, "New GL pbuffer surface 0x%08X\n",
+                 (unsigned) surf);
+        return surf;
+    }
+    return NULL;
+}
+
+
+/*!
+ * \internal
+ * \brief Deletes an EGL surface
+ */
+static void m3gDeleteGLSurface(EGLSurface surface)
+{
+    eglDestroySurface(eglGetDisplay(EGL_DEFAULT_DISPLAY), surface);
+
+#   if defined(M3G_DEBUG)
+    {
+        EGLint err = eglGetError();
+        M3G_ASSERT(err == EGL_SUCCESS);
+    }
+#   endif
+
+    M3G_LOG1(M3G_LOG_OBJECTS, "Destroyed GL surface 0x%08X\n",
+             (unsigned) surface);
+}
+
+/*!
+ * \brief Swap buffers on a rendering surface
+ */
+static M3Gbool m3gSwapBuffers(EGLSurface surface)
+{
+    EGLBoolean success = eglSwapBuffers(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                                        surface);
+    
+#   if defined(M3G_DEBUG)
+    EGLint err = eglGetError();
+    M3G_ASSERT(err == EGL_SUCCESS);
+#   endif
+
+    return (M3Gbool) success;
+}
+
+/*!
+ * \brief Does a sub-blit of a frame buffer blit operation
+ */
+static void m3gBlitFrameBufferPixels2(RenderContext *ctx,
+                                      M3Gint xOffset, M3Gint yOffset,
+                                      M3Gint width, M3Gint height,
+                                      M3GPixelFormat internalFormat,
+                                      M3Gsizei stride,
+                                      const M3Gubyte *pixels)
+{
+#   define MAX_TEMP_TEXTURES    8
+    GLuint glFormat;
+    static const int MAX_TILE_SIZE = 256; /* -> 256 KB temp buffer(s) */
+    static const M3Gbyte tc[8] = { 0, 0, 0, 1, 1, 0, 1, 1 };
+    GLshort pos[8];
+    int tileWidth = MAX_TILE_SIZE, tileHeight = MAX_TILE_SIZE;
+    M3Gbool mustConvert = M3G_FALSE;
+    M3Gubyte *tempPixels = 0; /* initialize to avoid compiler warnings */
+    GLuint tempTexObj[MAX_TEMP_TEXTURES];
+    GLint tempTexCount;
+        
+    M3G_VALIDATE_OBJECT(ctx);
+    M3G_ASSERT_GL;
+
+    /* Analyze source and destination formats for possible conversion */
+    
+    glFormat = m3gGetGLFormat(internalFormat);
+    if (!glFormat) {
+        M3G_ASSERT(M3G_FALSE);  /* internal format not supported in GL */
+        return;
+    }
+    if (internalFormat == M3G_RGB8_32) {
+        glFormat = GL_RGBA;
+    }
+    if (internalFormat == M3G_BGR8_32) {
+        glFormat = GL_RGBA;
+        mustConvert = M3G_TRUE;
+    }
+
+    /* Tweak tile size to avoid using excessive amounts of memory for
+     * portions outside the blit area */
+
+    M3G_ASSERT((width > 0) && (height > 0));
+    
+    while (tileWidth >= width * 2) {
+        tileWidth >>= 1;
+        tileHeight <<= 1;
+    }
+    while (tileHeight >= height * 2) {
+        tileHeight >>= 1;
+    }
+        
+    /* Allocate temp memory for conversion or adjust tile size for
+     * optimal direct download to GL */
+    
+    if (mustConvert) {
+        tempPixels = m3gAllocTemp(M3G_INTERFACE(ctx),
+                                  tileWidth * tileHeight * 4);
+        if (!tempPixels) {
+            return; /* out of memory */
+        }
+    }
+    else {
+
+        /* Attempt to adjust the tile size so that we can copy
+         * complete scanlines at a time -- this is because OpenGL ES
+         * is missing PixelStore settings that could be used for
+         * stride control during image uploading */
+
+        M3Gint maxWidth;
+        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxWidth);
+        
+        while (tileWidth < width &&
+               tileWidth < maxWidth &&
+               tileHeight > 1) {
+            tileWidth <<= 1;
+            tileHeight >>= 1;
+        }
+    }
+    
+    /* Load default images into the temp texture objects */
+    
+    glActiveTexture(GL_TEXTURE0);
+    glEnable(GL_TEXTURE_2D);
+    {
+        int ti;
+        tempTexCount = ((width + tileWidth - 1) / tileWidth)
+            * ((height + tileHeight - 1) / tileHeight);
+        tempTexCount = m3gMinInt(tempTexCount, MAX_TEMP_TEXTURES);
+        
+        glGenTextures(tempTexCount, tempTexObj);
+        
+        for (ti = 0; ti < tempTexCount; ++ti) {
+            glBindTexture(GL_TEXTURE_2D, tempTexObj[ti]);
+            glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+            glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+            glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+            M3G_ASSERT_GL;
+            
+            glTexImage2D(GL_TEXTURE_2D, 0,
+                         glFormat,
+                         tileWidth, tileHeight,
+                         0,
+                         glFormat,
+                         GL_UNSIGNED_BYTE, NULL);
+
+            /* Raise out-of-memory if OpenGL ran out of resources */
+            {
+                GLint err = glGetError();
+
+                if (err == GL_OUT_OF_MEMORY) {
+                    m3gRaiseError(M3G_INTERFACE(ctx), M3G_OUT_OF_MEMORY);
+                    goto CleanUpAndExit;
+                }
+                else if (err != GL_NO_ERROR) {
+                    M3G_ASSERT(M3G_FALSE);
+                }
+            }
+        }
+    }
+
+    /* Set up texture and vertex coordinate arrays for the image tiles */
+
+    glClientActiveTexture(GL_TEXTURE0);
+    glTexCoordPointer(2, GL_BYTE, 0, tc);
+    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+    glVertexPointer(2, GL_SHORT, 0, pos);
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glMatrixMode(GL_TEXTURE);
+    glLoadIdentity();
+    glMatrixMode(GL_MODELVIEW);
+    M3G_ASSERT_GL;
+
+    /* Load each image tile into a texture and draw */
+
+    {
+        M3Gint nextTexTile = 0;
+        M3Gint x, y, bpp;
+        bpp = m3gBytesPerPixel(internalFormat);
+        if (stride == 0) {
+            stride = bpp * width;
+        }
+
+        for (y = 0; y < height; y += tileHeight) {
+            for (x = 0; x < width; x += tileWidth) {
+                M3Gint w, h;
+
+                w = M3G_MIN(tileWidth, width - x);
+                h = M3G_MIN(tileHeight, height - y);
+
+                glBindTexture(GL_TEXTURE_2D, tempTexObj[nextTexTile]);
+                nextTexTile = (nextTexTile + 1) % MAX_TEMP_TEXTURES;
+                              
+                if (mustConvert) {
+                    m3gConvertPixelRect(internalFormat,
+                                        pixels + y * stride + x * bpp,
+                                        stride,
+                                        w, h,
+                                        M3G_RGBA8, tempPixels, w * 4);
+                    glTexSubImage2D(GL_TEXTURE_2D, 0,
+                                    0, 0,
+                                    w, h,
+                                    GL_RGBA, GL_UNSIGNED_BYTE, tempPixels);
+                }
+                else {
+                    if (w*bpp == stride) {
+                        glTexSubImage2D(GL_TEXTURE_2D, 0,
+                                        0, 0,
+                                        w, h,
+                                        glFormat,
+                                        GL_UNSIGNED_BYTE,
+                                        pixels + y * stride + x * bpp);
+                    }
+                    else {
+                        int k;
+                        for (k = 0; k < h; ++k) {
+                            glTexSubImage2D(GL_TEXTURE_2D, 0,
+                                            0, k,
+                                            w, 1,
+                                            glFormat,
+                                            GL_UNSIGNED_BYTE,
+                                            pixels + (y+k) * stride + x * bpp);
+                        }
+                    }
+                }
+                
+                pos[0] = (GLshort)(x + xOffset);
+                pos[1] = (GLshort)((height - y) + yOffset);
+                pos[2] = pos[0];
+                pos[3] = (GLshort)((height - (y + tileHeight)) + yOffset);
+                pos[4] = (GLshort)((x + tileWidth) + xOffset);
+                pos[5] = pos[1];
+                pos[6] = pos[4];
+                pos[7] = pos[3];
+                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+            }
+        }
+        M3G_ASSERT_GL;
+    }
+
+    /* Restore required OpenGL state and release resources */
+
+CleanUpAndExit:
+    if (mustConvert) {
+        m3gFreeTemp(M3G_INTERFACE(ctx));
+    }
+
+    glDeleteTextures(tempTexCount, tempTexObj);
+        
+    M3G_ASSERT_GL;
+    
+#   undef MAX_TEMP_TEXTURES
+}
+
+/*----------------------------------------------------------------------
+ * Internal functions
+ *--------------------------------------------------------------------*/
+
+/* The frame buffer should be the first thing locked and the last one
+ * released, so let's mandate that even though it has no real effect
+ * with EGL */
+#define m3gLockFrameBuffer(ctx)    M3G_ASSERT_NO_LOCK(M3G_INTERFACE(ctx))
+#define m3gReleaseFrameBuffer(ctx) M3G_ASSERT_NO_LOCK(M3G_INTERFACE(ctx))
+
+/*!
+ * \internal
+ * \brief Alternate rendering function for two-sided lighting on buggy
+ * hardware
+ */
+static M3Gbool m3gSplitDrawMesh(RenderContext *ctx,
+                                const VertexBuffer *vb,
+                                const IndexBuffer *ib,
+                                const Appearance *app,
+                                const M3GMatrix *modelTransform,
+                                M3Gint alphaFactor,
+                                M3Gint scope)
+{
+    if (!ctx->inSplitDraw && m3gGetMaterial((M3GAppearance) app) && vb->normals) {
+        PolygonMode *pm = m3gGetPolygonMode((M3GAppearance) app);
+        if (pm && pm->enableTwoSidedLighting) {
+            M3Gint originalCulling = m3gGetCulling(pm);
+            if (originalCulling != M3G_CULL_BACK) {
+                
+                /* OK, we must render the back sides separately with
+                 * flipped normals */
+                    
+                Interface *m3g = M3G_INTERFACE(ctx);
+                VertexArray *tempNormals;
+                
+                M3Gint normalCount = vb->vertexCount;
+                M3Gint normalStride = vb->normals->stride;
+
+                /* Duplicate the normal array */
+                
+                m3gReleaseFrameBuffer(ctx);
+                tempNormals = m3gCloneVertexArray(vb->normals);
+                if (!tempNormals) {
+                    m3gLockFrameBuffer(ctx);
+                    return M3G_TRUE; /* automatic out-of-memory */
+                }
+
+                /* Flip the signs of the temp normals */
+
+                if (tempNormals->elementType == GL_BYTE) {
+                    M3Gbyte *p = (M3Gbyte*) m3gMapObject(m3g, tempNormals->data);
+                    int i;
+                    for (i = 0; i < normalCount; ++i) {
+                        p[0] = (M3Gbyte) -m3gClampInt(p[0], -127, 127);
+                        p[1] = (M3Gbyte) -m3gClampInt(p[1], -127, 127);
+                        p[2] = (M3Gbyte) -m3gClampInt(p[2], -127, 127);
+                        p += normalStride;
+                    }
+                }
+                else {
+                    M3Gshort *p = (M3Gshort*) m3gMapObject(m3g, tempNormals->data);
+                    int i;
+                    for (i = 0; i < normalCount; ++i) {
+                        p[0] = (M3Gshort) -m3gClampInt(p[0], -32767, 32767);
+                        p[1] = (M3Gshort) -m3gClampInt(p[1], -32767, 32767);
+                        p[2] = (M3Gshort) -m3gClampInt(p[2], -32767, 32767);
+                        p += normalStride / 2;
+                    }
+                }
+                m3gUnmapObject(m3g, tempNormals->data);
+                m3gLockFrameBuffer(ctx);
+                
+                ctx->inSplitDraw = M3G_TRUE;
+
+                /* Set culling to front faces only and render with the
+                 * flipped normals */
+                {
+                    VertexArray *orgNormals = vb->normals;
+                    ((VertexBuffer*)vb)->normals = tempNormals;
+                    m3gSetCulling(pm, M3G_CULL_FRONT);
+                    m3gDrawMesh(ctx,
+                                vb, ib, app,
+                                modelTransform,
+                                alphaFactor, scope);
+                    ((VertexBuffer*)vb)->normals = orgNormals;
+                }
+
+                /* If no culling was enabled, render the front faces
+                 * with the original normals */
+
+                if (originalCulling == M3G_CULL_NONE) {
+                    m3gSetCulling(pm, M3G_CULL_BACK);
+                    m3gDrawMesh(ctx,
+                                vb, ib, app,
+                                modelTransform,
+                                alphaFactor, scope);
+                }
+
+                /* Restore original culling and free the temp normals */
+                    
+                m3gSetCulling(pm, originalCulling);
+                
+                m3gReleaseFrameBuffer(ctx);
+                m3gDeleteObject((M3GObject) tempNormals);
+                m3gLockFrameBuffer(ctx);
+
+                ctx->inSplitDraw = M3G_FALSE;
+                return M3G_TRUE;
+            }
+        }
+    }
+    return M3G_FALSE;
+}
+
+/*!
+ * \internal
+ * \brief Determines whether a format/mode combination can be directly
+ * rendered
+ */
+static M3Gbool m3gCanDirectRender(const RenderContext *ctx)
+{
+    M3GPixelFormat format = ctx->target.format;
+    M3Gbitmask bufferBits = ctx->bufferBits;
+    M3Gbitmask surfaceType = ctx->target.type;
+    int i;
+
+    /* Images always go via pbuffers; EGL surfaces can always be
+     * rendered to */
+    
+    if (surfaceType == SURFACE_IMAGE) {
+        return M3G_FALSE;
+    }
+    if (surfaceType == SURFACE_EGL) {
+        return M3G_TRUE;
+    }
+    
+    /* First scan the context cache for a matching previously used
+     * context; this should be faster than querying EGL */
+    
+    for (i = 0; i < M3G_MAX_GL_CONTEXTS; ++i) {
+        const GLContextRecord *rc = &ctx->glContext[i];
+        
+        if ((rc->surfaceTypeBits & surfaceType) == surfaceType
+            && rc->format == format
+            && (rc->bufferBits & bufferBits) == bufferBits) {
+            
+            return M3G_TRUE;
+        }
+    }
+
+    /* No dice; must resort to querying from EGL */
+
+    return (m3gQueryEGLConfig(format, bufferBits, (EGLint) surfaceType) != NULL);
+}
+
+/*!
+ * \internal
+ * \brief Ensures that a sufficient back buffer exists
+ *
+ * Creates a new PBuffer for the back buffer if required.
+ */
+static M3Gbool m3gValidateBackBuffer(RenderContext *ctx)
+{
+    BackBuffer *bbuf = &ctx->backBuffer;
+    int w = ctx->target.width;
+    int h = ctx->target.height;
+
+    /* NOTE the EGL specification is fuzzy on eglCopyBuffers when the
+     * pbuffer is larger than the target, so we require an exact match
+     * (can be relaxed by #undefining the flag, see m3g_defs.h) */
+
+#   if defined(M3G_GL_FORCE_PBUFFER_SIZE)
+    if (bbuf->width != w || bbuf->height != h) {
+#   else
+    if (bbuf->width < w || bbuf->height < h) {
+#   endif
+        
+        M3G_LOG(M3G_LOG_WARNINGS,
+                "Warning (performance): Buffered rendering.\n");
+        
+        if (bbuf->glSurface != NULL) {
+            m3gDeleteGLSurface(bbuf->glSurface);
+        }
+        
+        bbuf->glSurface = m3gCreatePBufferSurface(
+            M3G_RGBA8,
+            (M3Gbitmask)(M3G_COLOR_BUFFER_BIT|M3G_DEPTH_BUFFER_BIT|M3G_MULTISAMPLE_BUFFER_BIT),
+            w, h);
+        
+        bbuf->width = w;
+        bbuf->height = h;
+        
+        if (!bbuf->glSurface) {
+            if (eglGetError() == EGL_BAD_ALLOC) {
+                return M3G_FALSE; /* ouf of memory */
+            }
+            else {
+                M3G_ASSERT(M3G_FALSE);
+            }
+        }
+    }
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Increment the rendering time stamp
+ *
+ * The time stamp is used to manage the various caches for the
+ * context, so it needs to be updated often enough for the caches to
+ * function optimally.
+ * 
+ * In the rare case that the time stamp should wrap around(!), we
+ * reset the time stamps dependent on it to avoid sub-optimal cache
+ * performance.
+ */
+static void m3gIncrementRenderTimeStamp(RenderContext *ctx)
+{
+    if (++ctx->cacheTimeStamp == 0) {
+        int i;
+        for (i = 0; i < M3G_MAX_GL_CONTEXTS; ++i) {
+            ctx->glContext[i].lastUseTime = 0;
+        }
+        for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) {
+            ctx->glSurface[i].lastUseTime = 0;
+        }
+    }
+}
+
+/*!
+ * \internal
+ * \brief Draws a raw RGB or RGBA pixel rectangle of arbitrary size
+ * into the frame buffer
+ *
+ * The offset only affects the position of the blitted rectangle in
+ * the frame buffer. The source data is read starting at the given
+ * pointer.
+ * 
+ * \param ctx            the rendering context
+ * \param xOffset        offset from the left edge of the frame buffer
+ * \param yOffset        offset from the bottom of the frame buffer
+ * \param width          width of the rectangle
+ * \param height         height of the rectangle
+ * \param internalFormat format of the source pixels
+ * \param stride         stride of the source data
+ * \param pixels         pointer to the source pixels in top-to-bottom order
+ */
+static void m3gBlitFrameBufferPixels(RenderContext *ctx,
+                                     M3Gint xOffset, M3Gint yOffset,
+                                     M3Gint width, M3Gint height,
+                                     M3GPixelFormat internalFormat,
+                                     M3Gsizei stride,
+                                     const M3Gubyte *pixels)
+{
+    /* Skip this if nothing to copy */
+
+    if (width <= 0 || height <= 0) {
+        return;
+    }
+    
+    /* Set viewport, projection and modelview to map coordinates to
+     * pixel boundaries */
+
+    glScissor(xOffset, yOffset, width, height);
+    glViewport(0, 0, ctx->target.width, ctx->target.height);
+    glMatrixMode(GL_PROJECTION);
+    glLoadIdentity();
+    glOrthox(0, ctx->target.width << 16,
+             0, ctx->target.height << 16,
+             -1 << 16, 1 << 16);
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+    
+    /* Disable any stray state we don't want */
+
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_BLEND);
+    glDisable(GL_ALPHA_TEST);
+    glDisableClientState(GL_NORMAL_ARRAY);
+    glDisableClientState(GL_COLOR_ARRAY);
+    glDisable(GL_LIGHTING);
+    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+    glDepthMask(GL_FALSE);
+    glDepthFunc(GL_ALWAYS);
+    m3gDisableTextures();
+    M3G_ASSERT_GL;
+    
+    /* Split the large blit operation into smaller chunks that are
+     * efficiently taken care of using power-of-two textures */
+    {
+        const int MAX_BLIT_SIZE = 256; /* should be power of two */
+    
+        int xBlits = (width / MAX_BLIT_SIZE) + 1;
+        int yBlits = (height / MAX_BLIT_SIZE) + 1;
+
+        int xBlit, yBlit;
+    
+        for (yBlit = yBlits-1; yBlit >= 0; yBlit--) {
+            for (xBlit = 0; xBlit < xBlits; ++xBlit) {
+            
+                M3Gint xStart = xOffset + xBlit * MAX_BLIT_SIZE;
+                M3Gint yStart = yOffset + yBlit * MAX_BLIT_SIZE;
+                M3Gint xSize = m3gMinInt(MAX_BLIT_SIZE, width - (xStart - xOffset));
+                M3Gint ySize = m3gMinInt(MAX_BLIT_SIZE, height - (yStart - yOffset));
+
+                M3Gint srcOffset = (height - (yStart - yOffset + ySize)) * stride + (xStart - xOffset) * m3gBytesPerPixel(ctx->target.format);
+                
+                m3gBlitFrameBufferPixels2(ctx,
+                                          xStart, yStart,
+                                          xSize, ySize,
+                                          internalFormat,
+                                          stride,
+                                          pixels + srcOffset);
+            }
+        }
+    }
+}
+
+/*!
+ * \internal
+ * \brief Synchronizes the contents of the back buffer with the target
+ * buffer
+ */
+static void m3gUpdateBackBuffer(RenderContext *ctx)
+{
+    if (ctx->target.type == SURFACE_IMAGE) {
+        m3gDrawFrameBufferImage(ctx, (Image *) ctx->target.handle);
+    }
+    else if (ctx->target.type == SURFACE_BITMAP) {
+
+        M3Gubyte *src;
+        M3Gsizei stride;
+
+        /* Obtain a pointer to the native bitmap and copy the data to
+         * the backbuffer from there */
+        
+        if (m3gglLockNativeBitmap((M3GNativeBitmap) ctx->target.handle,
+                                  &src, &stride)) {
+
+            M3Gint clipWidth = ctx->clip.x1 - ctx->clip.x0;
+            M3Gint clipHeight = ctx->clip.y1 - ctx->clip.y0;
+            M3Gint srcOffset =
+                (ctx->target.height - ctx->clip.y1) * stride
+                + ctx->clip.x0 * m3gBytesPerPixel(ctx->target.format);
+            
+            m3gBlitFrameBufferPixels(
+                ctx,
+                ctx->clip.x0, ctx->clip.y0,
+                clipWidth, clipHeight,
+                ctx->target.format,
+                stride,
+                src + srcOffset);
+                
+            m3gglReleaseNativeBitmap((M3GNativeBitmap) ctx->target.handle);
+        }
+        else {
+            /* No dice! There's no way that we know of to copy the
+             * data between the buffers */
+            M3G_ASSERT(M3G_FALSE);
+        }
+    }
+    else {
+        /* Buffered rendering is not supported for window and pbuffer
+         * targets */
+        M3G_ASSERT(M3G_FALSE);
+    }
+    ctx->backBuffer.contentsValid = M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Synchronizes the contents of the target buffer with the back
+ * buffer
+ */
+static void m3gUpdateTargetBuffer(RenderContext *ctx)
+{
+    if (ctx->target.type == SURFACE_IMAGE) {
+        m3gCopyFrameBufferImage((Image *) ctx->target.handle);
+    }
+    else if (ctx->target.type == SURFACE_BITMAP) {
+        
+        /* We must copy the back buffer to a native bitmap: first
+         * attempt a fast buffer-to-buffer copy using EGL, but if that
+         * fails, obtain a pointer and do the copy ourselves */
+
+        /* We can only do the fast copy for the full buffer */
+
+        M3Gbool fullClip = (ctx->clip.x0 == 0)
+            && (ctx->clip.y0 <= ctx->target.height - ctx->display.height)
+            && (ctx->clip.x1 >= ctx->display.width)
+            && (ctx->clip.y1 >= ctx->clip.y0 + ctx->display.height);
+        
+        if (!fullClip || !eglCopyBuffers(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                                         ctx->backBuffer.glSurface,
+                                         (NativePixmapType) ctx->target.handle)) {
+            
+            /* Fast copy failed, try the generic approach */
+
+            M3Gubyte *dst;
+            M3Gsizei stride;
+            
+            if (m3gglLockNativeBitmap((M3GNativeBitmap) ctx->target.handle,
+                                      &dst, &stride)) {
+                
+                /* OK, got the pointer; now, copy a scanline at a
+                 * time, and we can pretty much assume conversion
+                 * since the fast method didn't work */
+
+                M3GPixelFormat format = ctx->target.format;
+                M3Gint width = ctx->clip.x1 - ctx->clip.x0;
+                M3Gint height = ctx->clip.y1 - ctx->clip.y0;
+                M3Gint xOffset = ctx->clip.x0;
+                M3Gint yOffset = ctx->clip.y0;
+                M3Gint row;
+
+                M3Gubyte *temp = m3gAllocTemp(M3G_INTERFACE(ctx), width * 4);
+                if (!temp) {
+                    return; /* out of memory */
+                }
+
+                dst += (ctx->target.height - (yOffset + height)) * stride
+                    + xOffset * m3gBytesPerPixel(format);
+                
+                for (row = 0; row < height; ++row) {
+                    glReadPixels(xOffset, yOffset + height - row - 1,
+                                 width, 1,
+                                 GL_RGBA, GL_UNSIGNED_BYTE,
+                                 temp);
+                    m3gConvertPixels(M3G_RGBA8, temp, format, dst, width);
+                    dst += stride;
+                }
+                m3gFreeTemp(M3G_INTERFACE(ctx));
+                
+                m3gglReleaseNativeBitmap((M3GNativeBitmap) ctx->target.handle);
+            }
+            else {
+                /* No dice! There's no way that we know of to copy the
+                 * data between the buffers */
+                M3G_ASSERT(M3G_FALSE);
+            }
+        }
+    }
+    else {
+        /* Buffered rendering is not supported for window and pbuffer
+         * targets */
+        M3G_ASSERT(M3G_FALSE);
+    }
+}
+
+/*!
+ * \internal
+ * \brief Selects a GL context matching a given GL surface and a set
+ * of rendering parameters
+ *
+ * If no existing context matches, a new one is created. Contexts are
+ * stored in a fixed-size cache and managed using a LRU policy.
+ */
+static EGLContext m3gSelectGLContext(RenderContext *ctx,
+                                     M3GPixelFormat format,
+                                     M3Gbitmask bufferBits,
+                                     M3Gbitmask surfaceTypeBits,
+                                     EGLSurface surface)
+{
+    int i;
+    
+    /* Look for a matching cached context and attempt to make it
+     * current; on success, update the time in the context record and
+     * return the GL context handle */
+
+    for (i = 0; i < M3G_MAX_GL_CONTEXTS; ++i) {
+        GLContextRecord *rc = &ctx->glContext[i];
+        
+        if ((rc->surfaceTypeBits & surfaceTypeBits) == surfaceTypeBits
+            && rc->format == format
+            && (rc->bufferBits & bufferBits) == bufferBits) {
+
+            if (eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), 
+                               surface, surface, rc->handle)) {
+                rc->lastUseTime = ctx->cacheTimeStamp;
+                return rc->handle;
+            }
+            else {
+                /* NOTE we intentionally clear the error flag, since
+                 * the MakeCurrent call above can fail in case of a
+                 * context mismatch */
+                eglGetError();
+            }
+        }
+    }
+
+    /* No match found, we must create a new context */
+    {
+        GLContextRecord *lru = &ctx->glContext[0];
+        EGLContext shareRc = lru->handle;
+        EGLContext glrc;
+
+        /* Find the least recently used context entry */
+        
+        for (i = 1; i < M3G_MAX_GL_CONTEXTS; ++i) {
+            GLContextRecord *rc = &ctx->glContext[i];
+            if (rc->handle) {
+                shareRc = rc->handle; /* keep this for sharing */
+            }
+            if (!rc->handle || rc->lastUseTime < lru->lastUseTime) {
+                lru = rc;
+            }
+        }
+
+        /* Create a new GL context, then delete the LRU one. This is
+         * done in this order so that we don't lose any shared texture
+         * objects when deleting a context. */
+
+        if (surfaceTypeBits == SURFACE_EGL) {
+            EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+            EGLint configID;
+            eglQuerySurface(dpy,
+                            (EGLSurface) ctx->target.handle,
+                            EGL_CONFIG_ID,
+                            &configID);
+            glrc = eglCreateContext(dpy, (EGLConfig) configID, shareRc, NULL);
+            M3G_ASSERT(glrc);
+        }
+        else {
+            glrc = m3gCreateGLContext(format,
+                                      bufferBits,
+                                      surfaceTypeBits,
+                                      shareRc,
+                                      &lru->surfaceTypeBits);
+        }
+        
+        if (!glrc) {
+            m3gRaiseError(M3G_INTERFACE(ctx), M3G_OUT_OF_MEMORY);
+            return NULL;
+        }
+        if (lru->handle) {
+            m3gDeleteGLContext(lru->handle);
+        }
+        
+        /* Store the parameters for the new context and make it
+         * current */
+        
+        lru->handle = glrc;
+		lru->surfaceTypeBits = surfaceTypeBits;
+        lru->format = format;
+        lru->bufferBits = bufferBits;
+        lru->modeBits = ctx->modeBits;
+        {
+            M3Gbool ok = eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY),
+                                        surface, surface, glrc);
+            M3G_ASSERT(ok);
+            if (!ok) {
+                return NULL;
+            }
+        }
+        lru->lastUseTime = ctx->cacheTimeStamp;
+        m3gSetGLDefaults();
+        return glrc;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Selects a GL surface suitable for rendering into the current
+ * target using the currently set rendering parameters
+ *
+ * If no existing surface matches, a new one is created. Surfaces are
+ * stored in a fixed-size LRU cache.
+ */
+static EGLSurface m3gSelectGLSurface(RenderContext *ctx)
+{
+    int attempts = 0;
+    int i;
+
+    /* Quick exit for EGL surfaces */
+
+    if (ctx->target.type == SURFACE_EGL) {
+        return (EGLSurface) ctx->target.handle;
+    }
+    
+    /* Buffered rendering is handled elsewhere! */
+    
+    if (ctx->target.buffered) {
+        M3G_ASSERT(M3G_FALSE);
+        return NULL;
+    }
+
+    /* Find the first matching surface and return it */
+    
+    for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) {
+        GLSurfaceRecord *surf = &ctx->glSurface[i];
+        
+        if (surf->type == ctx->target.type
+            && surf->targetHandle == ctx->target.handle
+            && (ctx->bufferBits & surf->bufferBits) == ctx->bufferBits) {
+            
+            surf->lastUseTime = ctx->cacheTimeStamp;
+            return surf->handle;
+        }
+    }
+
+    /* No matching surface found; must create a new one. If the cache
+     * is fully occupied, or if we run out of memory, one of the
+     * existing surfaces is swapped out */
+    
+    while (attempts <= 1) {
+        
+        GLSurfaceRecord *lru = &ctx->glSurface[0];
+
+        /* Find the first entry without a GL surface handle, or the
+         * least recently used one if all are occupied. */
+        
+        for (i = 1; lru->handle != NULL && i < M3G_MAX_GL_SURFACES; ++i) {
+            GLSurfaceRecord *surf = &ctx->glSurface[i];
+            if (!surf->handle || surf->lastUseTime < lru->lastUseTime) {
+                lru = surf;
+            }
+        }
+
+        /* Delete the existing surface if we hit an occupied slot */
+        
+        if (lru->handle) {
+            m3gDeleteGLSurface(lru->handle);
+        }
+
+        /* Create a new surface depending on the type of the current
+         * rendering target */
+        
+        switch (ctx->target.type) {
+        case SURFACE_BITMAP:
+            lru->handle =
+                m3gCreateBitmapSurface(ctx->target.format,
+                                       ctx->bufferBits,
+                                       (M3GNativeBitmap) ctx->target.handle);
+            break;
+        case SURFACE_WINDOW:
+            lru->handle =
+                m3gCreateWindowSurface(ctx->target.format,
+                                       ctx->bufferBits,
+                                       (M3GNativeWindow) ctx->target.handle);
+            break;
+        default:
+            M3G_ASSERT(M3G_FALSE);
+            return NULL;
+        }
+
+        /* Success, return the new surface */
+
+        if (lru->handle) {
+            lru->type         = ctx->target.type;
+            lru->targetHandle = ctx->target.handle;
+            lru->bufferBits   = ctx->bufferBits;
+            lru->lastUseTime  = ctx->cacheTimeStamp;
+            return lru->handle;
+        }
+
+        /* No surface created, likely due to running out of memory;
+         * delete all existing surfaces and try again */
+        
+        if (!lru->handle) {
+            for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) {
+                GLSurfaceRecord *surf = &ctx->glSurface[i];
+                if (surf->handle) {
+                    m3gDeleteGLSurface(surf->handle);
+                    surf->handle = NULL;
+                    surf->type = SURFACE_NONE;
+                }
+            }
+            ++attempts;
+            continue;
+        }
+    }
+
+    /* Couldn't create a new surface; must return with an error */
+    
+    m3gRaiseError(M3G_INTERFACE(ctx), M3G_OUT_OF_MEMORY);
+    return NULL;
+}
+
+
+/*!
+ * \internal
+ * \brief Deletes all native surfaces for a specific target
+ *
+ * \param ctx    rendering context
+ * \param type   bitmask of the types of surfaces to remove
+ * \param handle native target handle of the surfaces to remove
+ */
+static void m3gDeleteGLSurfaces(RenderContext *ctx,
+                                M3Gbitmask type,
+                                M3Guint handle)
+{
+    int i;
+    M3G_VALIDATE_OBJECT(ctx);
+    
+    for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) {
+        GLSurfaceRecord *surf = &ctx->glSurface[i];
+
+        if ((surf->type & type) != 0 && surf->targetHandle == handle) {
+            m3gDeleteGLSurface(surf->handle);
+            
+            surf->type = SURFACE_NONE;
+            surf->handle = NULL;
+        }
+    }
+}
+    
+/*!
+ * \internal
+ * \brief Makes an OpenGL context current to the current rendering target
+ */
+static void m3gMakeGLCurrent(RenderContext *ctx)
+{
+    if (ctx != NULL) {
+        EGLContext eglCtx = NULL;
+        if (ctx->target.buffered) {
+            eglCtx = m3gSelectGLContext(
+                ctx,
+                M3G_RGBA8,
+                (M3Gbitmask) M3G_COLOR_BUFFER_BIT |
+                             M3G_DEPTH_BUFFER_BIT |
+                             M3G_MULTISAMPLE_BUFFER_BIT,
+                (M3Gbitmask) EGL_PBUFFER_BIT,
+                ctx->backBuffer.glSurface);
+            ctx->target.surface = ctx->backBuffer.glSurface;
+        }
+        else {
+            EGLSurface surface = m3gSelectGLSurface(ctx);
+            if (surface) {
+                eglCtx = m3gSelectGLContext(ctx,
+                                   ctx->target.format,
+                                   ctx->bufferBits,
+                                   ctx->target.type,
+                                   surface);
+                ctx->target.surface = surface;
+            }
+        }
+
+        /* Update the current acceleration status */
+        
+        if (eglCtx) {
+            EGLint param;
+            eglQueryContext(eglGetCurrentDisplay(),
+                            eglCtx, EGL_CONFIG_ID,
+                            &param);
+            eglGetConfigAttrib(eglGetCurrentDisplay(),
+                               (EGLConfig) param, EGL_CONFIG_CAVEAT,
+                               &param);
+            ctx->accelerated = (param == EGL_NONE);
+        }
+    }
+    else {
+        eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), NULL, NULL, NULL);
+    }
+}
+
+
+/*----------------------------------------------------------------------
+ * Public API
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \brief
+ */
+void m3gBindBitmapTarget(M3GRenderContext hCtx,
+                         M3GNativeBitmap hBitmap)
+{
+    M3GPixelFormat format;
+    M3Gint width, height;
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    
+    M3G_LOG1(M3G_LOG_RENDERING, "Binding bitmap 0x%08X\n", (unsigned) hBitmap);
+    
+    if (!m3gglGetNativeBitmapParams(hBitmap, &format, &width, &height)) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OBJECT);
+        return;
+    }
+
+    if (!m3gBindRenderTarget(ctx,
+                             SURFACE_BITMAP,
+                             width, height,
+                             format,
+                             hBitmap)) {
+        return; /* appropriate error raised automatically */
+    }
+
+    /* placeholder for bitmap target specific setup */
+}
+
+/*!
+ * \brief Binds an external EGL surface as a rendering target
+ *
+ * \param context the M3G rendering context
+ * \param surface an EGLSurface cast to M3GEGLSurface
+ */
+M3G_API void m3gBindEGLSurfaceTarget(M3GRenderContext context,
+                                     M3GEGLSurface surface)
+{
+    RenderContext *ctx = (RenderContext*) context;
+    Interface *m3g = M3G_INTERFACE(ctx);
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_LOG1(M3G_LOG_RENDERING, "Binding EGL surface 0x%08X\n", (unsigned) surface);
+    {
+        EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        EGLSurface surf = (EGLSurface) surface;
+        M3Gint width, height;
+        
+        if (!(eglQuerySurface(dpy, surf, EGL_WIDTH, &width) &&
+              eglQuerySurface(dpy, surf, EGL_HEIGHT, &height))) {
+            m3gRaiseError(m3g, M3G_INVALID_OBJECT);
+            return;
+        }
+
+        if (!m3gBindRenderTarget(ctx,
+                                 SURFACE_EGL,
+                                 width, height,
+                                 M3G_RGBA8,
+                                 surface)) {
+            return; /* error raised automatically */
+        }
+
+        /* placeholder for target type specific setup */
+    }
+}
+
+/*!
+ * \brief Unsupported with OpenGL ES
+ */
+/*@access EGLContext@*/
+M3G_API void m3gBindMemoryTarget(M3GRenderContext context,
+                                 /*@shared@*/ void *pixels,
+                                 M3Guint width, M3Guint height,
+                                 M3GPixelFormat format,
+                                 M3Guint stride,
+                                 M3Guint userHandle)
+{
+    RenderContext *ctx = (RenderContext*) context;
+    Interface *m3g = M3G_INTERFACE(ctx);
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_UNREF(pixels);
+    M3G_UNREF(width);
+    M3G_UNREF(height);
+    M3G_UNREF(format);
+    M3G_UNREF(stride);
+    M3G_UNREF(userHandle);
+    
+    m3gRaiseError(m3g, M3G_INVALID_OPERATION);
+}
+
+/*!
+ * \brief
+ */
+M3G_API void m3gBindWindowTarget(M3GRenderContext hCtx,
+                                 M3GNativeWindow hWindow)
+{
+    M3GPixelFormat format;
+    M3Gint width, height;
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    
+    M3G_LOG1(M3G_LOG_RENDERING, "Binding window 0x%08X\n", (unsigned) hWindow);
+    
+    if (!m3gglGetNativeWindowParams(hWindow, &format, &width, &height)) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OBJECT);
+        return;
+    }
+
+    if (!m3gBindRenderTarget(ctx,
+                             SURFACE_WINDOW,
+                             width, height,
+                             format,
+                             hWindow)) {
+        return; /* appropriate error raised automatically */
+    }
+
+    /* placeholder for window target specific setup */
+}
+
+/*!
+ * \brief Invalidate a previously bound bitmap target
+ *
+ * This should be called prior to deleting a native bitmap that has
+ * been used as an M3G rendering target in the past. This erases the
+ * object from any internal caches and ensures it will not be accessed
+ * in the future.
+ *
+ * \param hCtx    M3G rendering context
+ * \param hBitmap native handle of the bitmap object
+ */
+M3G_API void m3gInvalidateBitmapTarget(M3GRenderContext hCtx,
+                                       M3GNativeBitmap hBitmap)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_LOG1(M3G_LOG_RENDERING, "Invalidating bitmap 0x%08X\n",
+             (unsigned) hBitmap);
+    
+    m3gDeleteGLSurfaces(ctx, (M3Gbitmask) SURFACE_BITMAP, (M3Guint) hBitmap);
+}
+
+/*!
+ * \brief Invalidate a previously bound window target
+ *
+ * This should be called prior to deleting a native window that has
+ * been used as an M3G rendering target in the past. This erases the
+ * object from any internal caches and ensures it will not be accessed
+ * in the future.
+ *
+ * \param hCtx    M3G rendering context
+ * \param hWindow native handle of the bitmap object
+ */
+M3G_API void m3gInvalidateWindowTarget(M3GRenderContext hCtx,
+                                       M3GNativeWindow hWindow)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_LOG1(M3G_LOG_RENDERING, "Invalidating window 0x%08X\n",
+             (unsigned) hWindow);
+    
+    m3gDeleteGLSurfaces(ctx, (M3Gbitmask) SURFACE_WINDOW, (M3Guint) hWindow);
+}