m3g/m3gcore11/src/m3g_rendercontext.inl
author Gareth Stockwell <gareth.stockwell@accenture.com>
Fri, 05 Nov 2010 17:31:20 +0000
branchbug235_bringup_0
changeset 215 097e92a68d68
parent 11 fed1595b188e
child 45 36b2e23a8629
permissions -rw-r--r--
Added GLES 1.x spinning cube-rendering code to eglbringuptest The coordinate, color and index data are uploaded to server-side buffers by the CGLES1Cube::KhrSetup function. CGLES1Cube::KhrPaint just sets the view matrix and issues a draw command. Which demo to display can be selected by passing its name on the command line, e.g. eglbringuptest vgline eglbringuptest gles1cube If no name is provided, the application defaults to vgline.

/*
* 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 <EGL/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,
                                   M3GNativeBitmap bitmapHandle)
{
    struct { int attrib, value; } attribs[10];
    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;


    if (bitmapHandle) {
        /* This attribute is matched only for pixmap targets */
        attribs[6].attrib = EGL_MATCH_NATIVE_PIXMAP;
        attribs[6].value = bitmapHandle;

        /* Try to get multisampling if requested */

        attribs[7].attrib = EGL_SAMPLE_BUFFERS;
        attribs[8].attrib = EGL_SAMPLES;

        attribs[9].attrib = EGL_NONE;
    } else {
        /* 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 (bitmapHandle) {
            if (samples > 1) {
                attribs[7].value = 1;
                attribs[8].value = samples;
            }
            else {
                attribs[7].value = EGL_FALSE;
                attribs[8].value = 0;
            }
        } else {
            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;  
            EGLint error;            
        
            eglChooseConfig(eglGetDisplay(EGL_DEFAULT_DISPLAY),
                            (const int *) attribs,
                            &config, 1,
                            &numConfigs);
            
            error = eglGetError();
            if (error != EGL_SUCCESS) {
                M3G_LOG1(M3G_LOG_FATAL_ERRORS, "eglChooseConfig  failed: %d\n", error);
            }
            
            
            M3G_ASSERT(error == 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);
    eglBindAPI(EGL_OPENGL_ES_API);
}

/*!
 * \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, NULL);
    
    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, NULL);
    
    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, bmp);
    
    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, NULL);
    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 || internalFormat == M3G_ARGB8) {
        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;
    M3GNativeBitmap bitmapHandle = ctx->target.handle;
    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, bitmapHandle) != 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 || ctx->target.type == SURFACE_MEMORY) {

        M3Gubyte *src;
        M3Gsizei stride;

        M3Gint clipWidth = ctx->clip.x1 - ctx->clip.x0;
        M3Gint clipHeight = ctx->clip.y1 - ctx->clip.y0;
        M3Gint srcOffset;

        if (ctx->target.type == SURFACE_BITMAP) {
            /* Obtain a pointer to the native bitmap and copy the data to
             * the backbuffer from there */
            if (!m3gglLockNativeBitmap((M3GNativeBitmap) ctx->target.handle,
                                      &src, &stride)) {
                /* No dice! There's no way that we know of to copy the
                 * data between the buffers */
                M3G_ASSERT(M3G_FALSE);
            }
        } else {
            /* Memory target */
            src = ctx->target.pixels;
            stride = ctx->target.stride;
        }

        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);

        if (ctx->target.type == SURFACE_BITMAP) {
            m3gglReleaseNativeBitmap((M3GNativeBitmap) ctx->target.handle);
        }
    }
    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 || ctx->target.type == SURFACE_MEMORY) {
        
        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 *dst;
        M3Gsizei stride;
        M3Gubyte *temp;

        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)) 
            {
                return;
            }
            
            /* Fast copy failed, try the generic approach */
            if (!m3gglLockNativeBitmap((M3GNativeBitmap) ctx->target.handle,
                                      &dst, &stride)) {
                /* No dice! There's no way that we know of to copy the
                 * data between the buffers */
                M3G_ASSERT(M3G_FALSE);
            }
        } else {
            /* Memory target */
            dst = ctx->target.pixels;
            stride = ctx->target.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 */

        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));

        if (ctx->target.type == SURFACE_BITMAP) {
            m3gglReleaseNativeBitmap((M3GNativeBitmap) ctx->target.handle);
        }
    }
    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,
                            surface,//(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->width == ctx->target.width)
            && (surf->height == ctx->target.height)
            && (surf->format == ctx->target.format)
            && (surf->pixels == ctx->target.pixels)) {
            
            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->width        = ctx->target.width;
            lru->height       = ctx->target.height;
            lru->format       = ctx->target.format;
            lru->pixels       = ctx->target.pixels;
            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)
{
    eglBindAPI(EGL_OPENGL_ES_API);

    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, pixels;
    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, &pixels)) {
        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OBJECT);
        return;
    }

    if (!m3gBindRenderTarget(ctx,
                             SURFACE_BITMAP,
                             width, height,
                             format,
                             hBitmap)) {
        return; /* appropriate error raised automatically */
    }

    /* Set the bitmap target specific parameters */
    
    ctx->target.pixels = (void*)pixels;

}

/*!
 * \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 Binds a new memory rendering target to this rendering
 * context
 *
 * Upon first binding of a specific target, binding the buffer may
 * require auxiliary data to be allocated, depending on the rendering
 * modes set for this context. In that case, the binding will be
 * canceled, and the function will return a non-zero value giving the
 * number of bytes of additional memory that needs to be supplied for
 * binding of that target to succeed. The function must then be called
 * again and a pointer to a sufficient memory block supplied as the \c
 * mem parameter.
 *
 * \param pixels NULL to signal that the frame buffer is accessed
 * using a callback upon rendering time
 */
/*@access M3GGLContext@*/
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_LOG1(M3G_LOG_RENDERING, "Binding memory buffer 0x%08X\n",
             (unsigned) pixels);
    
    /* Check for bitmap specific errors */
    
    if (width == 0 || height == 0 || stride < width) {
        m3gRaiseError(m3g, M3G_INVALID_VALUE);
        return; 
    }

    /* Effect the generic target binding */
    
    if (!m3gBindRenderTarget(ctx,
                             SURFACE_MEMORY,
                             width, height,
                             format,
                             userHandle)) {
        return; /* appropriate error raised automatically */
    }

    /* Set the memory target specific parameters */
    
    ctx->target.pixels = pixels;
    ctx->target.stride = stride;
}

/*!
 * \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);
}

/*!
 * \brief Invalidate a previously bound memorytarget
 *
 * This should be called prior to deleting a memory buffer that has
 * been used as an M3G rendering target in the past. 
 *
 * \param hCtx    M3G rendering context
 * \param pixels  pointer to the memory buffer
 */
M3G_API void m3gInvalidateMemoryTarget(M3GRenderContext hCtx,
                                       void *pixels)
{
    RenderContext *ctx = (RenderContext *) hCtx;
    M3G_VALIDATE_OBJECT(ctx);

    M3G_LOG1(M3G_LOG_RENDERING, "Invalidating memory target 0x%08X\n",
             (unsigned) pixels);
    
    m3gDeleteGLSurfaces(ctx, (M3Gbitmask) SURFACE_MEMORY, (M3Guint) pixels);
}