m3g/m3gcore11/src/m3g_rendercontext.c
changeset 0 5d03bc08d59c
child 11 fed1595b188e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_rendercontext.c	Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,1871 @@
+/*
+* 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: Rendering context function implementations
+*
+*/
+
+
+/*!
+ * \internal
+ * \file
+ * \brief Rendering context function implementations
+ */
+
+#ifndef M3G_CORE_INCLUDE
+#   error included by m3g_core.c; do not compile separately.
+#endif
+
+#include "m3g_rendercontext.h"
+#include "m3g_object.h"
+
+#include "m3g_gl.h"
+#include "m3g_memory.h"
+#include "m3g_appearance.h"
+#include "m3g_indexbuffer.h"
+#include "m3g_lightmanager.h"
+#include "m3g_vertexbuffer.h"
+#include "m3g_world.h"
+
+/*----------------------------------------------------------------------
+ * Private data types
+ *--------------------------------------------------------------------*/
+
+#if defined(M3G_NGL_CONTEXT_API)
+/*!
+ * \internal
+ * \brief Depth buffer data
+ */
+typedef struct
+{
+    M3GMemObject handle;
+    M3Gsizei size;
+} DepthBuffer;
+#endif /*M3G_NGL_CONTEXT_API*/
+
+#if !defined(M3G_NGL_CONTEXT_API)
+/*! \internal \brief OpenGL rendering context record */
+typedef struct {
+    EGLContext handle;
+    M3GPixelFormat format;
+    M3Gbitmask bufferBits;
+    M3Gbitmask surfaceTypeBits;
+    M3Gbitmask modeBits;
+    M3Guint lastUseTime;
+} GLContextRecord;
+
+/*! \internal \brief OpenGL surface record */
+typedef struct {
+    EGLSurface handle;
+    M3Gbitmask bufferBits;
+    M3Gbitmask type;
+    M3Guint targetHandle;
+    M3Guint lastUseTime;
+} GLSurfaceRecord;
+#endif /*!M3G_NGL_CONTEXT_API*/
+
+/*!
+ * \internal \brief Rendering target data
+ */
+typedef struct 
+{
+    M3Gbitmask type;
+    M3GPixelFormat format;
+    M3Gint width, height;
+#   if defined(M3G_NGL_CONTEXT_API)
+    M3Guint stride;
+    /*@shared@*/ void *pixels, *lockedPixels;
+#   else
+    EGLSurface surface;
+#   endif
+    M3Guint handle;
+    M3Guint userData;
+    
+    /*!
+     * \internal
+     * \brief Flag set to indicate back buffer rendering
+     *
+     * The final target is only written to, via a format
+     * conversion, when releasing the target.
+     */
+    M3Gbool buffered;
+} RenderTarget;
+
+/*!
+ * \internal
+ * \brief Back color buffer data
+ */
+typedef struct {
+#   if defined(M3G_NGL_CONTEXT_API)
+    M3GMemObject handle;
+    M3Gsizei size;
+#   else
+    M3Gint width, height;
+    EGLSurface glSurface;
+#   endif /* M3G_NGL_CONTEXT_API */
+    M3Gbool contentsValid;
+} BackBuffer;
+
+/*!
+ * \internal
+ * \brief Rendering context data structure
+ *
+ * This includes data related to a specific rendering context,
+ * including e.g.  viewport settings, and active lights and
+ * camera. This is equivalent to the Graphics3D class in the Java API.
+ */
+struct M3GRenderContextImpl
+{
+    Object object;
+    
+    RenderTarget target;
+    BackBuffer backBuffer;
+#   if defined(M3G_NGL_CONTEXT_API)
+    DepthBuffer depthBuffer;
+#   endif
+
+#   if !defined(M3G_NGL_CONTEXT_API)
+    
+    /* OpenGL context and surface caches */
+    
+    GLContextRecord glContext[M3G_MAX_GL_CONTEXTS];
+    GLSurfaceRecord glSurface[M3G_MAX_GL_SURFACES];
+    M3Guint cacheTimeStamp;
+    
+#   endif /* M3G_NGL_CONTEXT_API */
+
+    /*! \internal \brief Current/last rendering mode */
+    M3Genum renderMode;
+    
+    /*! \internal \brief OpenGL viewing transformation */
+    GLfloat viewTransform[16];
+
+    /*! \internal \brief Current camera */
+    const Camera *camera;
+
+    /*! \internal \brief Light manager component */
+    LightManager lightManager;
+
+    /*! \internal \brief Last used scope, to speed up light selection */
+    M3Gint lastScope;
+
+	M3Gfloat depthNear;
+	M3Gfloat depthFar;
+    
+    /*! \internal \brief Clipping rectangle parameters */
+    struct { M3Gint x0, y0, x1, y1; } clip;
+
+    /*! \internal \brief Scissor and viewport rectangles */
+    struct { GLint x, y, width, height; } scissor, viewport;
+
+    /*! \internal \brief Physical display size */
+    struct { M3Gint width, height; } display;
+    
+    M3Gbitmask bufferBits;      /*!< \brief Rendering buffer bits */
+    M3Gbitmask modeBits;        /*!< \brief Rendering mode bits */
+
+    /*! \internal \brief OpenGL subsystem initialization flag */
+    M3Gbool glInitialized;
+
+    /*! \internal \brief HW acceleration status flag */
+    M3Gbool accelerated;
+    
+    /*! \internal \brief Render queue for this context */
+	RenderQueue *renderQueue;
+
+    M3Gbool currentColorWrite;
+    M3Gbool currentAlphaWrite;
+    M3Gbool inSplitDraw;
+    M3Gbool alphaWrite;
+};
+
+/*
+ * Rendering target types; note that the values here MUST match the
+ * respective EGL bit values
+ */
+enum SurfaceType {
+    SURFACE_NONE = 0,
+    SURFACE_IMAGE = 0x01,       /* EGL_PBUFFER_BIT */
+    SURFACE_BITMAP = 0x02,      /* EGL_PIXMAP_BIT */
+    SURFACE_WINDOW = 0x04,      /* EGL_WINDOW_BIT */
+    SURFACE_MEMORY = SURFACE_IMAGE | SURFACE_BITMAP | SURFACE_WINDOW,
+    SURFACE_EGL = 0x80
+};
+
+enum RenderMode {
+    RENDER_IMMEDIATE,
+    RENDER_NODES,
+    RENDER_WORLD
+};
+
+/*----------------------------------------------------------------------
+ * Platform specific code
+ *--------------------------------------------------------------------*/
+
+static M3Gbool m3gBindRenderTarget(RenderContext *ctx,
+                                   M3Genum targetType,
+                                   M3Gint width, M3Gint height,
+                                   M3GPixelFormat format,
+                                   M3Guint handle);
+static void m3gResetRectangles(RenderContext *ctx);
+static void m3gSetGLDefaults(void);
+static void m3gUpdateScissor(RenderContext *ctx);
+static void m3gValidateBuffers(RenderContext *ctx);
+static M3Gbool m3gValidTargetFormat(M3GPixelFormat format);
+
+#include "m3g_rendercontext.inl"
+
+/*----------------------------------------------------------------------
+ * Internal functions
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \internal
+ * \brief Rendering context destructor
+ *
+ */
+static void m3gDestroyContext(/*@only@*/ Object *obj)
+{
+    RenderContext *ctx = (RenderContext *) obj;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_ASSIGN_REF(ctx->camera, NULL);
+    
+#   if defined(M3G_NGL_CONTEXT_API)
+    if (ctx->target.type == SURFACE_MEMORY && ctx->target.pixels == NULL) {
+        m3gSignalTargetRelease(M3G_INTERFACE(ctx), ctx->target.handle);
+    }
+
+    m3gFreeObject(M3G_INTERFACE(ctx), ctx->depthBuffer.handle);
+    m3gFreeObject(M3G_INTERFACE(ctx), ctx->backBuffer.handle);
+    
+#   else /* !M3G_NGL_CONTEXT_API */
+    
+    {
+        int i;
+        for (i = 0; i < M3G_MAX_GL_CONTEXTS; ++i) {
+            if (ctx->glContext[i].handle != 0) {
+                m3gDeleteGLContext(ctx->glContext[i].handle);
+            }
+        }
+        for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) {
+            if (ctx->glSurface[i].handle != 0) {
+                m3gDeleteGLSurface(ctx->glSurface[i].handle);
+            }
+        }
+    }
+
+#   endif /* M3G_NGL_CONTEXT_API */
+    
+    if (ctx->glInitialized) {
+        m3gShutdownGL(M3G_INTERFACE(ctx));
+    }
+
+    m3gDestroyLightManager(&ctx->lightManager, M3G_INTERFACE(ctx));
+    m3gDestroyRenderQueue(M3G_INTERFACE(ctx), ctx->renderQueue);
+    m3gDestroyObject(obj);
+}
+
+/*!
+ * \internal
+ * \brief Resets the clipping and viewport rectangles to defaults
+ *
+ * This is called after binding a new target.
+ */
+static void m3gResetRectangles(RenderContext *ctx)
+{
+    int w = ctx->display.width;
+    int h = ctx->display.height;
+    
+    ctx->clip.x0 = 0;
+    ctx->clip.y0 = ctx->target.height - ctx->display.height;
+    ctx->clip.x1 = w;
+    ctx->clip.y1 = ctx->clip.y0 + h;
+
+    ctx->viewport.x = 0;
+    ctx->viewport.y = 0;
+    ctx->viewport.width = M3G_MIN(w, M3G_MAX_VIEWPORT_DIMENSION);
+    ctx->viewport.height = M3G_MIN(h, M3G_MAX_VIEWPORT_DIMENSION);
+}
+
+/*!
+ * \internal
+ * \brief Constrains the clip rectangle to the rendering target.
+ */
+static void m3gValidateClipRect(RenderContext *ctx)
+{
+    int xMin = 0;
+    int xMax = ctx->display.width;
+    int yMin = ctx->target.height - ctx->display.height;
+    int yMax = yMin + ctx->display.height;
+    
+    ctx->clip.x0 = m3gClampInt(ctx->clip.x0, xMin, xMax);
+    ctx->clip.y0 = m3gClampInt(ctx->clip.y0, yMin, yMax);
+    ctx->clip.x1 = m3gClampInt(ctx->clip.x1, xMin, xMax);
+    ctx->clip.y1 = m3gClampInt(ctx->clip.y1, yMin, yMax);
+}
+
+/*!
+ * \internal
+ * \brief Computes the GL scissor rectangle
+ *
+ * The scissor rectangle is the intersection of the viewport and the
+ * clipping rectangle.
+ */
+static void m3gUpdateScissor(RenderContext *ctx)
+{
+    int sx0 = ctx->viewport.x;
+    int sy0 = ctx->viewport.y;
+    int sx1 = sx0 + ctx->viewport.width;
+    int sy1 = sy0 + ctx->viewport.height;
+
+    sx0 = M3G_MAX(sx0, ctx->clip.x0);
+    sy0 = M3G_MAX(sy0, ctx->clip.y0);
+    sx1 = M3G_MIN(sx1, ctx->clip.x1);
+    sy1 = M3G_MIN(sy1, ctx->clip.y1);
+
+    ctx->scissor.x = sx0;
+    ctx->scissor.y = sy0;
+    
+    if (sx0 < sx1 && sy0 < sy1) {
+        ctx->scissor.width = sx1 - sx0;
+        ctx->scissor.height = sy1 - sy0;
+    }
+    else {
+        ctx->scissor.width = ctx->scissor.height = 0;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Checks whether we can render in a given format
+ */
+static M3Gbool m3gValidTargetFormat(M3GPixelFormat format)
+{
+    return m3gInRange(format, M3G_RGB8, M3G_RGBA4);
+}
+
+/*!
+ * \internal
+ * \brief Checks whether a given format has alpha
+ */
+static M3Gbool m3gFormatHasAlpha(M3GPixelFormat format)
+{
+    switch (format) {
+    case M3G_A8:
+    case M3G_LA8:
+    case M3G_LA4:
+    case M3G_RGBA8:
+    case M3G_BGRA8:
+    case M3G_RGBA4:
+    case M3G_RGB5A1:
+    case M3G_PALETTE8_RGBA8:
+        return M3G_TRUE;
+    default:
+        return M3G_FALSE;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Sets the global alpha write enable flag. 
+ *
+ *	Used for disabling the alpha channel writes when the rendering 
+ *  target is a Java MIDP Image that has an alpha channel.
+ *
+ * \param ctx        the rendering context
+ * \param enable     alpha write enable flag
+ */
+M3G_API void m3gSetAlphaWrite(M3GRenderContext ctx, M3Gbool enable)
+{
+	ctx->alphaWrite = enable;
+}
+
+/*!
+ * \internal
+ * \brief Reads the global alpha write enable flag. 
+ *
+ * \param ctx        the rendering context
+ */
+M3G_API M3Gbool m3gGetAlphaWrite(M3GRenderContext ctx)
+{
+	return ctx->alphaWrite;
+}
+
+
+/*!
+ * \internal
+ * \brief Sets up a new rendering target
+ *
+ * \param ctx        the rendering context
+ * \param targetType rendering target type
+ * \param width      width of the target
+ * \param height     height of the target
+ * \param format     target pixel format
+ * \param handle     user object handle
+ */
+static M3Gbool m3gBindRenderTarget(RenderContext *ctx,
+                                   M3Genum targetType,
+                                   M3Gint width, M3Gint height,
+                                   M3GPixelFormat format,
+                                   M3Guint handle)
+{
+    /* Check for generic errors */
+    
+    if (ctx->target.type != SURFACE_NONE) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OPERATION);
+        return M3G_FALSE;
+    }
+    if (!m3gValidTargetFormat(format)) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_ENUM);
+        return M3G_FALSE;
+    }
+
+    /* If target width or height exceeds maximum viewport width or height
+       an exception is thrown. */
+    
+    if (width > M3G_MAX_VIEWPORT_WIDTH ||
+        height > M3G_MAX_VIEWPORT_HEIGHT) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_ENUM);
+        return M3G_FALSE;
+    }
+
+    /* Everything checks out; set up the target parameters */
+    
+    ctx->target.type = targetType;
+    ctx->target.width = width;
+    ctx->target.height = height;
+    ctx->display.width = width;
+    ctx->display.height = height;
+    ctx->target.format = format;
+    ctx->target.handle = handle;
+    m3gResetRectangles(ctx);
+    m3gUpdateScissor(ctx);
+    m3gValidateBuffers(ctx);
+    
+    /* Invalidate lights in case we're using a different OpenGL
+     * rendering context this time around */
+    
+    ctx->lastScope = 0;
+    
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Initializes the current GL context to default settings.
+ */
+static void m3gSetGLDefaults(void)
+{
+	static const GLfloat black[] = {0.f, 0.f, 0.f, 0.f};
+    glEnable(GL_NORMALIZE);
+    glEnable(GL_SCISSOR_TEST);
+	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, black);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+}
+
+/*!
+ * \internal
+ * \brief Validates the buffers required for a rendering context
+ *
+ * Allocates or reallocates buffers as necessary, according to the
+ * currently set flags of the context.
+ */
+static void m3gValidateBuffers(RenderContext *ctx)
+{
+    M3G_VALIDATE_OBJECT(ctx);
+
+    /* Initialize OpenGL if not already done */
+    
+    if (!ctx->glInitialized) {
+        m3gInitializeGL(M3G_INTERFACE(ctx));
+        ctx->glInitialized = M3G_TRUE;
+    }
+
+    /* Check whether we can render directly to the target or need to
+     * use a back buffer */
+    
+    ctx->target.buffered = !m3gCanDirectRender(ctx);
+#   if defined(M3G_FORCE_BUFFERED_RENDERING)
+    ctx->target.buffered = M3G_TRUE;
+#   endif
+    
+    /* If direct rendering wasn't possible, check that the back buffer
+     * for buffered rendering exists. */
+    
+    if (ctx->target.buffered) {
+        if (!m3gValidateBackBuffer(ctx)) {
+            return; /* out of memory */
+        }
+    }
+
+    /* With the legacy NGL API, we also manage the depth buffer */
+    
+#   if defined(M3G_NGL_CONTEXT_API)
+    if (!m3gValidateDepthBuffer(ctx)) {
+        return; /* out of memory */
+    }
+#   endif
+
+    /* Delay blitting from the front buffer until we know it's
+     * necessary; let's raise a flag to check that later on */
+    
+    if (ctx->target.buffered) {
+        if (ctx->modeBits & M3G_OVERWRITE_BIT) {
+            ctx->backBuffer.contentsValid = M3G_TRUE;
+        }
+        else {
+            ctx->backBuffer.contentsValid = M3G_FALSE;
+        }
+    }
+}
+
+/*!
+ * \internal
+ * \brief Makes a GL context current to this thread and the currently
+ * set rendering target buffer
+ */
+static void m3gMakeCurrent(RenderContext *ctx)
+{
+    m3gMakeGLCurrent(ctx);
+
+    /* Note that the depth buffer may in some cases exist even if not
+     * explicitly requested, so we need to disable the depth test just
+     * in case */
+    
+    if ((ctx->bufferBits & M3G_DEPTH_BUFFER_BIT) == 0) {
+        glDisable(GL_DEPTH_TEST);
+    }
+    else {
+        glEnable(GL_DEPTH_TEST);
+    }
+
+    /* Enable multisampling if required */
+
+    if (ctx->modeBits & M3G_ANTIALIAS_BIT) {
+        glEnable(GL_MULTISAMPLE);
+    }
+    else {
+        glDisable(GL_MULTISAMPLE);
+    }
+    
+    M3G_ASSERT_GL;
+}
+
+/*!
+ * \internal
+ * \brief Returns the HW acceleration status of the current context
+ */
+static M3Gbool m3gIsAccelerated(const RenderContext *ctx)
+{
+    return ctx->accelerated;
+}
+
+/*!
+ * \internal
+ * \brief Sets the currently enabled lights to the GL state
+ *
+ * \note the correct viewing matrix *must* be set prior to calling
+ * this for the lights to be transformed into eye space correctly
+ */
+static M3G_INLINE void m3gApplyLights(RenderContext *ctx, M3Gint scope)
+{
+    if (ctx->lastScope != scope) {
+
+        /* If coming from RenderNode, we have the geometry in camera
+         * space but the lights in world space, so we need to apply
+         * the viewing matrix to the lights only */
+        
+        if (ctx->renderMode == RENDER_NODES) {
+            glPushMatrix();
+            glLoadMatrixf(ctx->viewTransform);
+        }
+        
+        m3gSelectGLLights(&ctx->lightManager, 8, scope, 0, 0, 0);
+        ctx->lastScope = scope;
+        
+        if (ctx->renderMode == RENDER_NODES) {
+            glPopMatrix();
+        }
+    }
+	M3G_ASSERT_GL;
+}
+
+/*!
+ * \internal
+ * \brief Gets the current camera
+ */
+static const Camera *m3gGetCurrentCamera(const RenderContext *ctx) {
+	return ctx->camera;	
+}
+
+/*!
+ * \internal
+ * \brief Sets up some rendering parameters that
+ * do not change during scene renders.
+ */
+static void m3gInitRender(M3GRenderContext context, M3Genum renderMode)
+{
+    RenderContext *ctx = (RenderContext *) context;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    m3gIncrementRenderTimeStamp(ctx);
+    m3gMakeCurrent(ctx);
+    m3gCollectGLObjects(M3G_INTERFACE(ctx));
+    
+    /* If buffered rendering, blit the image to the back buffer at
+     * this point */
+    
+    if (ctx->target.buffered && !ctx->backBuffer.contentsValid) {
+        m3gUpdateBackBuffer(ctx);
+    }
+    
+    /* Set up viewport and scissoring */
+    
+    glViewport(ctx->viewport.x, ctx->viewport.y,
+               ctx->viewport.width, ctx->viewport.height);
+	glDepthRangef(ctx->depthNear, ctx->depthFar);
+    glScissor(ctx->scissor.x, ctx->scissor.y,
+              ctx->scissor.width, ctx->scissor.height);
+    M3G_ASSERT_GL;
+    
+    /* Set up the projection and viewing transformations (static
+     * during rendering) */
+
+	m3gApplyProjection(ctx->camera);
+    if (renderMode == RENDER_NODES) {
+        glLoadIdentity();
+    }
+    else {
+        glLoadMatrixf(ctx->viewTransform);
+    }
+    M3G_ASSERT_GL;
+
+    /* Invalidate any already set GL lights if rendering mode changed */
+    
+    if (renderMode != ctx->renderMode) {
+        ctx->lastScope = 0;
+    }
+    ctx->renderMode = renderMode;
+}
+
+/*!
+ * \internal
+ * \brief A workaround for a broken implementation of glColorMask
+ *
+ * Saves the framebuffer in the OpenGL default texture each time the
+ * color mask changes, for restoring later.  Not very pretty, but
+ * works as long as the default texture is not touched in between --
+ * currently, we only touch that when copying to and from the back
+ * buffer.
+ *
+ * \param newColorWrite the color mask state we're about to change to
+ * \param newAlphaWrite the alpha write state we're about to change to
+ */
+static void m3gUpdateColorMaskStatus(RenderContext *ctx,
+                                     M3Gbool newColorWrite,
+                                     M3Gbool newAlphaWrite)
+{
+    GLint pow2Width, pow2Height;
+
+	/* Get the global alpha write value */
+	newAlphaWrite &= m3gGetAlphaWrite(ctx);
+
+    /* Check that the ColorMask state is actually about to change */
+    
+    if (ctx->currentColorWrite == newColorWrite
+        && (ctx->currentAlphaWrite == newAlphaWrite || !m3gFormatHasAlpha(ctx->target.format))) {
+        return; /* no change, quick exit */
+    }
+    
+    pow2Width = m3gNextPowerOfTwo(ctx->clip.x1 - ctx->clip.x0);
+    pow2Height = m3gNextPowerOfTwo(ctx->clip.y1 - ctx->clip.y0);
+    
+    /* If we previously had stored something, restore it now */
+
+    if (ctx->currentColorWrite != ctx->currentAlphaWrite) {
+        
+        /* Disable any stray state we don't want */
+
+        glDisable(GL_CULL_FACE);
+        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;
+    
+        /* Bind the default texture and set up screen space rendering */
+        
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, 0);
+        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        M3G_ASSERT_GL;
+
+        glScissor(ctx->clip.x0, ctx->clip.y0,
+                  ctx->clip.x1 - ctx->clip.x0, ctx->clip.y1 - ctx->clip.y0);
+        m3gPushScreenSpace(ctx, M3G_FALSE);
+        glViewport(0, 0, ctx->target.width, ctx->target.height);
+        glMatrixMode(GL_PROJECTION);
+        glOrthox(0, ctx->target.width << 16,
+                 0, ctx->target.height << 16,
+                 -1 << 16, 1 << 16);
+        glMatrixMode(GL_MODELVIEW);
+            
+        /* Set up texture and vertex coordinate arrays */
+
+        glClientActiveTexture(GL_TEXTURE0);
+        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+        glEnableClientState(GL_VERTEX_ARRAY);
+        glMatrixMode(GL_TEXTURE);
+        glLoadIdentity();
+        glMatrixMode(GL_MODELVIEW);
+        M3G_ASSERT_GL;
+
+        /* Blend the texture with the frame buffer */
+        {
+            static const M3Gbyte tc[8] = { 0, 0, 0, 1, 1, 0, 1, 1 };
+            GLshort pos[8];
+            
+            GLfixed cm = (GLfixed)(ctx->currentColorWrite ? 0 : 1 << 16);
+            GLfixed am = (GLfixed)(ctx->currentAlphaWrite ? 0 : 1 << 16);
+
+            glVertexPointer(2, GL_SHORT, 0, pos);
+            glTexCoordPointer(2, GL_BYTE, 0, tc);
+                
+            pos[0] = (GLshort) ctx->clip.x0;
+            pos[1] = (GLshort) ctx->clip.y0;
+            pos[2] = pos[0];
+            pos[3] = (GLshort) (pos[1] + pow2Height);
+            pos[4] = (GLshort) (pos[0] + pow2Width);
+            pos[5] = pos[1];
+            pos[6] = pos[4];
+            pos[7] = pos[3];
+            
+            glEnable(GL_BLEND);
+            glColor4x(cm, cm, cm, am);
+
+            /* Zero the masked channels */
+            
+            glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+            /* Add the masked channels from the stored texture */
+            
+            glEnable(GL_TEXTURE_2D);
+            glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+            glBlendFunc(GL_ONE, GL_ONE);
+            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+        }
+            
+        /* Restore the mandatory state */
+            
+        glScissor(ctx->scissor.x, ctx->scissor.y,
+                  ctx->scissor.width, ctx->scissor.height);
+        glViewport(ctx->viewport.x, ctx->viewport.y,
+                   ctx->viewport.width, ctx->viewport.height);
+        m3gPopSpace(ctx);
+    }
+    
+    /* Copy the current clip rectangle into the default texture if
+     * we're going to be rendering with unsupported masks in effect */
+    
+    if (newColorWrite != newAlphaWrite) {
+        GLenum err;
+            
+        glBindTexture(GL_TEXTURE_2D, 0);
+        M3G_ASSERT_GL;
+            
+        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+                         ctx->clip.x0, ctx->clip.y0,
+                         pow2Width, pow2Height,
+                         0);
+        err = glGetError();
+        if (err == GL_INVALID_OPERATION) {
+            /* Incompatible FB format -- must be GL_RGB then */
+            glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
+                             ctx->clip.x0, ctx->clip.y0,
+                             pow2Width, pow2Height,
+                             0);
+            err = glGetError();
+        }
+        if (err == GL_OUT_OF_MEMORY) {
+            m3gRaiseError(M3G_INTERFACE(ctx), M3G_OUT_OF_MEMORY);
+        }
+        M3G_ASSERT(!err);
+    }
+    else {
+        
+        /* Texture not needed for now, so allow GL to free some
+         * resources */
+        
+        glTexImage2D(GL_TEXTURE_2D, 0,
+                     GL_RGBA,
+                     1, 1,
+                     0,
+                     GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    }
+    
+    ctx->currentColorWrite = newColorWrite;
+    ctx->currentAlphaWrite = newAlphaWrite;
+}
+
+/*!
+ * \internal
+ * \brief Sets the GL to input screen space coordinates
+ *
+ * This pushes the current modelview and projection matrices into the
+ * matrix stack, then sets up an orthogonal projection and an identity
+ * modelview matrix.
+ * 
+ * \param ctx the rendering context
+ * \param realPixels M3G_TRUE to use actual pixel coordinates,
+ * M3G_FALSE to use normalized device coordinates
+ */
+static void m3gPushScreenSpace(RenderContext *ctx, M3Gbool realPixels)
+{
+    M3G_VALIDATE_OBJECT(ctx);
+    
+    glMatrixMode(GL_PROJECTION);
+    glPushMatrix();
+    glLoadIdentity();
+    if (realPixels) {
+        int w = ctx->viewport.width;
+        int h = ctx->viewport.height;
+        glOrthox(0, w << 16, 0, h << 16, -1 << 16, 1 << 16);
+    }
+    glMatrixMode(GL_MODELVIEW);
+    glPushMatrix();
+    glLoadIdentity();
+}
+
+/*!
+ * \internal
+ * \brief Restores the projection and modelview matrix modified by
+ * m3gPushScreenSpace
+ */
+static void m3gPopSpace(RenderContext *ctx)
+{
+    M3G_VALIDATE_OBJECT(ctx);
+    
+    M3G_UNREF(ctx);
+    glMatrixMode(GL_PROJECTION);
+    glPopMatrix();
+    glMatrixMode(GL_MODELVIEW);
+    glPopMatrix();
+}
+
+/*!
+ * \internal
+ * \brief Clears the current buffer(s)
+ */
+static void m3gClearInternal(RenderContext *ctx, Background *bg)
+{
+    m3gMakeCurrent(ctx);
+    
+    /* If buffered rendering, copy data to the back buffer at this
+     * point if we're not clearing the whole clip rectangle */
+    
+    if (ctx->target.buffered && !ctx->backBuffer.contentsValid) {
+        if (ctx->scissor.x > ctx->clip.x0 || ctx->scissor.y > ctx->clip.y0 ||
+            ctx->scissor.x + ctx->scissor.width < ctx->clip.x1 ||
+            ctx->scissor.y + ctx->scissor.height < ctx->clip.y1) {
+            m3gUpdateBackBuffer(ctx);
+        }
+    }
+
+    if (m3gGetColorMaskWorkaround(M3G_INTERFACE(ctx))) {
+        m3gUpdateColorMaskStatus(ctx, M3G_TRUE, M3G_TRUE);
+    }
+
+    glDepthMask(GL_TRUE);
+    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, m3gGetAlphaWrite(ctx));
+    glDepthRangef(ctx->depthNear, ctx->depthFar);
+    glViewport(ctx->viewport.x, ctx->viewport.y,
+               ctx->viewport.width, ctx->viewport.height);
+    glScissor(ctx->scissor.x, ctx->scissor.y,
+              ctx->scissor.width, ctx->scissor.height);
+
+    /* Touch the background image to make sure it's created prior to
+     * locking memory for rendering */
+    
+    if (bg != NULL && bg->image != NULL) {
+        if (!m3gGetPowerOfTwoImage(bg->image)) {
+            return; /* out of memory */
+        }
+    }
+
+    /* All clear for clearing... */
+    
+    m3gLockFrameBuffer(ctx);
+    
+    if (bg != NULL) {
+        m3gApplyBackground(ctx, bg);
+        if (ctx->target.buffered && bg->colorClearEnable) {
+            ctx->backBuffer.contentsValid = M3G_TRUE;
+        }
+    }
+    else {
+        glClearColorx(0, 0, 0, 0);
+        glClearDepthx(1 << 16);
+        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+        if (ctx->target.buffered) {
+            ctx->backBuffer.contentsValid = M3G_TRUE;
+        }
+    }
+        
+    m3gReleaseFrameBuffer(ctx);
+}
+
+/*!
+ * \internal
+ * \brief Draws a batch of primitives
+ *
+ * This is the place most rendering commands are eventually routed to;
+ * sprites and backgrounds are the only exception to this. We assume
+ * that all eror checking has been performed at this point.
+ */
+static void m3gDrawMesh(RenderContext *ctx,
+                        const VertexBuffer *vb,
+                        const IndexBuffer *ib,
+                        const Appearance *app,
+                        const M3GMatrix *modelTransform,
+                        M3Gint alphaFactor,
+                        M3Gint scope)
+{
+    M3G_VALIDATE_OBJECT(ctx);
+    M3G_VALIDATE_OBJECT(vb);
+    M3G_VALIDATE_OBJECT(ib);
+    M3G_VALIDATE_OBJECT(app);
+
+    /* Check whether we need to use alternate rendering to get
+     * two-sided lighting */
+    if (m3gGetTwoSidedLightingWorkaround(M3G_INTERFACE(ctx))) {
+        if (m3gSplitDrawMesh(ctx, vb, ib, app, modelTransform, alphaFactor, scope)) {
+            return;
+        }
+    }
+
+    M3G_ASSERT(m3gInRange(alphaFactor, 0, 0x10000));
+    
+    if (m3gGetColorMaskWorkaround(M3G_INTERFACE(ctx))) {
+		m3gUpdateColorMaskStatus(ctx, m3gColorMask(app), m3gAlphaMask(app));
+    }
+    
+    /* Load lights */
+    
+    m3gApplyLights(ctx, scope);
+    
+    /* Apply the extra modeling transformation if present */
+    
+    if (modelTransform != NULL) {
+		float transform[16];
+		m3gGetMatrixColumns(modelTransform, transform);
+        
+        glPushMatrix();
+        glMultMatrixf(transform);
+    }
+
+    /* Check whether we need to create an alpha-factored color cache
+     * for the vertex buffer; this requires unlocking the frame buffer
+     * for a while, and we may even run out of memory in the process,
+     * but we still need to exit with the frame buffer lock and the
+     * matrix stack in the expected state */
+
+    if (alphaFactor < 0x10000 && !m3gValidateAlphaCache(vb)) {
+        M3Gbool ok;
+        m3gReleaseFrameBuffer(ctx);
+        ok = m3gCreateAlphaColorCache(vb->colors);
+        m3gLockFrameBuffer(ctx);
+        if (!ok) {
+            goto RestoreModelview; /* let's just skip the drawing part */
+        }
+    }
+
+#   if defined(M3G_NGL_TEXTURE_API)
+    /* Similarly to the alpha cache above, also check whether any
+     * textures may need to allocate mipmaps at this point */
+    {
+        M3Gint i;
+        for (i = 0; i < M3G_NUM_TEXTURE_UNITS; ++i) {
+            Texture *tex = app->texture[i];
+            if (tex && !m3gValidateTextureMipmapping(tex)) {
+                M3Gbool ok;
+                m3gReleaseFrameBuffer(ctx);
+                ok = m3gValidateMipmapMemory(m3gGetTextureImage(tex));
+                m3gLockFrameBuffer(ctx);
+                if (!ok) {
+                    goto RestoreModelview;
+                }
+            }
+        }
+    }
+#   endif
+    
+    /* Load up the rest of the stuff we need for rendering; note that
+     * the vertex buffer scale and bias apply to the texture matrix
+     * from the appearance object, so they need to be applied last */
+    
+    m3gApplyAppearance(app, ctx, alphaFactor);
+    m3gLockVertexBuffer(vb, alphaFactor);
+    m3gApplyScaleAndBias(vb);
+    
+    /* All ready, render and then release the stuff we bound above */
+    
+    m3gSendIndexBuffer(ib);
+    m3gReleaseVertexBuffer(vb);
+    m3gReleaseTextures(app);
+
+    /* Restore viewing-only modelview if changed */
+
+RestoreModelview:
+    if (modelTransform != NULL) {
+        glPopMatrix();
+    }
+}
+
+/*!
+ * \internal
+ * \brief Validates background format against current target
+ *
+ * \retval M3G_TRUE valid format
+ * \retval M3G_FALSE invalid format
+ */
+static M3Gbool m3gValidateBackground(RenderContext *ctx, Background *bg)
+{
+    /* Check that source image and target formats match */
+    if (bg != NULL && bg->image != NULL) {
+        M3GPixelFormat boundFormat =
+            (ctx->target.type == SURFACE_IMAGE)
+            ? m3gPixelFormat(((const Image *)ctx->target.handle)->format)
+            : ctx->target.format;
+        if (ctx->target.type == SURFACE_IMAGE && boundFormat == M3G_RGBA8) {
+            return (m3gGetFormat(bg->image) == M3G_RGBA);
+        }
+        else {
+            return (m3gGetFormat(bg->image) == M3G_RGB);
+        }
+    }
+
+    return M3G_TRUE;
+}
+
+/*----------------------------------------------------------------------
+ * Virtual function table
+ *--------------------------------------------------------------------*/
+
+static const ObjectVFTable m3gvf_RenderContext = {
+    NULL, /* ApplyAnimation */
+    NULL, /* IsCompatible */
+    NULL, /* UpdateProperty */
+    NULL, /* GetReference */
+    NULL, /* find */
+    NULL, /* CreateClone */
+    m3gDestroyContext
+};
+
+
+/*----------------------------------------------------------------------
+ * Public API
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \brief Creates and initializes a new rendering context
+ *
+ * \param bufferBits buffer bitmask
+ * \param width maximum width of context
+ * \param height maximum height of context
+ * \param modeBits hint bitmask
+ * \param mem pointer to memory block to allocate from
+ */
+/*@access M3GInterface@*/
+/*@access M3GRenderContext@*/
+/*@only@*/
+M3G_API M3GRenderContext m3gCreateContext(M3GInterface interface)/*@*/
+{
+    Interface *m3g = (Interface*) interface;
+    M3G_VALIDATE_INTERFACE(m3g);
+        
+    {
+        RenderContext *ctx =
+            (RenderContext*) m3gAllocZ(m3g, (int) sizeof(RenderContext));
+        if (ctx == NULL) {
+            return NULL; /* m3gAlloc automatically raises out-of-mem */
+        }
+
+		ctx->renderQueue = m3gCreateRenderQueue(m3g);
+        if (ctx->renderQueue == NULL) {
+            m3gFree(m3g, ctx);
+            return NULL;
+        }
+        ctx->bufferBits = M3G_COLOR_BUFFER_BIT|M3G_DEPTH_BUFFER_BIT;
+        ctx->depthNear = 0.0f;
+        ctx->depthFar = 1.0f;
+
+        m3gInitObject(&ctx->object, m3g, M3G_CLASS_RENDER_CONTEXT);
+
+		m3gSetAlphaWrite(ctx, M3G_TRUE);
+
+        if (m3gGetColorMaskWorkaround(M3G_INTERFACE(ctx))) {
+            ctx->currentColorWrite = M3G_TRUE;
+            ctx->currentAlphaWrite = m3gGetAlphaWrite(ctx);
+        }
+        
+		return (M3GRenderContext)ctx;
+    }
+}
+
+/*!
+ * \brief Sets the buffers to use for subsequent rendering
+ */
+M3G_API M3Gbool m3gSetRenderBuffers(M3GRenderContext hCtx,
+                                    M3Gbitmask bufferBits)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    if ((bufferBits & ~(M3G_COLOR_BUFFER_BIT|M3G_DEPTH_BUFFER_BIT|M3G_STENCIL_BUFFER_BIT|M3G_MULTISAMPLE_BUFFER_BIT)) != 0) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_VALUE);
+        return M3G_FALSE;
+    }
+    ctx->bufferBits = bufferBits;
+    return M3G_TRUE;
+}
+
+/*!
+ * \brief Sets the rendering quality hints to use for subsequent
+ * rendering
+ *
+ * \note This may not take effect before the target is released and
+ * rebound
+ */
+M3G_API M3Gbool m3gSetRenderHints(M3GRenderContext hCtx, M3Gbitmask modeBits)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    if ((modeBits & ~(M3G_OVERWRITE_BIT|M3G_ANTIALIAS_BIT|M3G_DITHER_BIT|M3G_TRUECOLOR_BIT)) != 0) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_VALUE);
+        return M3G_FALSE;
+    }
+    
+    /* Disable features not supported in the current configuration */
+
+    if (M3G_SUPPORT_ANTIALIASING == M3G_FALSE ||
+        !m3gIsAntialiasingSupported(M3G_INTERFACE(ctx))) {
+        modeBits &= ~M3G_ANTIALIAS_BIT;
+    }
+    if (M3G_SUPPORT_DITHERING == M3G_FALSE) {
+        modeBits &= ~M3G_DITHER_BIT;
+    }
+    if (M3G_SUPPORT_TRUE_COLOR == M3G_FALSE) {
+        modeBits &= ~M3G_TRUECOLOR_BIT;
+    }
+
+    ctx->modeBits = modeBits;
+    return M3G_TRUE;
+}
+
+M3G_API void m3gBindImageTarget(M3GRenderContext hCtx, M3GImage hImage)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    Image *img = (Image *) hImage;
+    M3G_VALIDATE_OBJECT(ctx);
+    M3G_VALIDATE_OBJECT(img);
+
+    M3G_LOG1(M3G_LOG_RENDERING, "Binding image target 0x%08X\n",
+             (unsigned) img);
+
+    /* Check for image-specific errors */
+    
+    if ((img->flags & M3G_DYNAMIC) == 0
+        || !m3gValidTargetFormat(img->internalFormat)) {
+        
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_ENUM);
+        return;
+    }
+
+    /* Do the generic checking and set-up */
+    
+    if (!m3gBindRenderTarget(ctx,
+                             SURFACE_IMAGE,
+                             img->width, img->height,
+                             img->internalFormat,
+                             (M3Guint) hImage)) {
+        return; /* appropriate error raised automatically */
+    }
+
+    /* Set up image-specific parameters */
+    
+#   if defined(M3G_NGL_CONTEXT_API)
+    ctx->target.stride = m3gGetImageStride(img);
+    ctx->target.pixels = NULL;
+#   endif
+    
+    m3gAddRef((Object*) img);
+}
+
+/*!
+ */
+M3G_API M3Guint m3gGetUserHandle(M3GRenderContext hCtx)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    if (ctx->target.type == SURFACE_MEMORY) {
+        return ctx->target.handle;
+    }
+    return 0;
+}
+
+/*!
+ */
+M3G_API void m3gSetUserData(M3GRenderContext hCtx, M3Guint hData)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    ctx->target.userData = hData;
+}
+
+/*!
+ */
+M3G_API M3Guint m3gGetUserData(M3GRenderContext hCtx)
+{
+    RenderContext *ctx = (RenderContext *) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    return ctx->target.userData;
+}
+
+/*!
+ * \brief Clears the current buffer(s)
+ */
+M3G_API void m3gClear(M3GRenderContext context, M3GBackground hBackground)
+{
+    RenderContext *ctx = (RenderContext*) context;
+    Background *bg = (Background *) hBackground;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_LOG(M3G_LOG_STAGES, "Clearing frame buffer\n");
+    
+    /* Check errors */
+    
+    if (ctx->target.type == SURFACE_NONE) {
+        m3gRaiseError(M3G_INTERFACE(context), M3G_INVALID_OPERATION);
+        return;
+    }
+
+    if(m3gValidateBackground(ctx, bg)) {
+        m3gClearInternal(ctx, bg);
+    }
+    else {
+        m3gRaiseError(M3G_INTERFACE(bg), M3G_INVALID_VALUE);
+    }
+}
+
+/*!
+ * \brief Release the currently bound color buffer
+ *
+ * Flushes all rendering and commits the final result to the currently
+ * bound target color buffer. Any changes to the target buffer since
+ * it was bound may be overwritten.
+ */
+M3G_API void m3gReleaseTarget(M3GRenderContext context)
+{
+    RenderContext *ctx = (RenderContext*) context;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_LOG(M3G_LOG_RENDERING, "Releasing target\n");
+    
+    if (ctx->target.type == SURFACE_NONE) {
+        return;
+    }
+    
+    m3gMakeCurrent(ctx);
+
+    if (m3gGetColorMaskWorkaround(M3G_INTERFACE(ctx))) {
+        m3gUpdateColorMaskStatus(ctx, M3G_TRUE, M3G_TRUE);
+    }
+
+    glFinish();
+
+    /* Update the real target if we rendered into the back buffer */
+    
+    if (ctx->target.buffered) {
+        m3gUpdateTargetBuffer(ctx);
+    }
+
+    /* Invalidate Image targets so that mipmap levels and/or OpenGL
+     * texture objects are updated accordingly */
+    
+    if (ctx->target.type == SURFACE_IMAGE) {
+        Image *img = (Image *) ctx->target.handle;
+        M3G_VALIDATE_OBJECT(img);
+        m3gInvalidateImage(img);
+        m3gDeleteRef((Object*) img);
+    }
+
+    /* Swap in case we rendered onto a double-buffered surface,
+     * release any GL resources that might have been release since the
+     * last time we rendered, then release the GL context so we don't
+     * hog resources */
+#   if !defined(M3G_NGL_CONTEXT_API)
+    if (ctx->target.type != SURFACE_EGL) {
+    m3gSwapBuffers(ctx->target.surface);
+    }
+#   endif
+    m3gCollectGLObjects(M3G_INTERFACE(ctx));
+#   if !defined(M3G_NGL_CONTEXT_API)
+    m3gMakeGLCurrent(NULL);
+    ctx->target.surface = NULL;
+#   else
+    if (ctx->target.type == SURFACE_MEMORY && ctx->target.pixels == NULL) {
+        m3gSignalTargetRelease(M3G_INTERFACE(ctx), ctx->target.handle);
+    }
+#   endif
+
+    ctx->target.type = SURFACE_NONE;
+    ctx->renderQueue->root = NULL;
+
+#   if (M3G_PROFILE_LOG_INTERVAL > 0)
+    m3gLogProfileCounters(M3G_INTERFACE(ctx));
+#   endif
+}
+
+/*!
+ * \brief Sets a camera for this context
+ */
+M3G_API void m3gSetCamera(M3GRenderContext context,
+                          M3GCamera hCamera,
+                          M3GMatrix *transform)
+{
+	Matrix m;
+    RenderContext *ctx = (RenderContext*) context;
+	const Camera *camera = (Camera *)hCamera;
+
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_ASSIGN_REF(ctx->camera, camera);
+
+	if (transform != NULL) {
+		if (!m3gMatrixInverse(&m, transform)) {
+            m3gRaiseError(M3G_INTERFACE(ctx), M3G_ARITHMETIC_ERROR);
+            return;
+        }
+	}
+	else {
+		m3gIdentityMatrix(&m);
+	}
+
+	m3gGetMatrixColumns(&m, ctx->viewTransform);
+
+    ctx->lastScope = 0;
+}
+
+/*!
+ * \brief Adds a light to the light array for this context
+ */
+M3G_API M3Gint m3gAddLight(M3GRenderContext hCtx,
+                           M3GLight hLight,
+                           const M3GMatrix *transform)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    Light *light = (Light *)hLight;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    if (light == NULL) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_VALUE);
+        return -1;
+    }
+    else {
+        LightManager *mgr = &ctx->lightManager;
+        M3G_VALIDATE_OBJECT(light);
+        ctx->lastScope = 0;
+        return m3gInsertLight(mgr, light, transform, M3G_INTERFACE(ctx));
+    }
+}
+
+/**
+ * \brief Sets a light for this context
+ */
+M3G_API void m3gSetLight(M3GRenderContext context,
+                         M3Gint lightIndex,
+                         M3GLight hLight,
+                         const M3GMatrix *transform)
+{
+    RenderContext *ctx = (RenderContext*) context;
+	Light *light = (Light *)hLight;
+    M3G_VALIDATE_OBJECT(ctx);
+
+	/* Check for invalid arguments */
+	if (lightIndex < 0 || lightIndex >= m3gLightArraySize(&ctx->lightManager)) {
+		m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_INDEX);
+        return;
+	}
+
+    ctx->lastScope = 0;
+    m3gReplaceLight(&ctx->lightManager, lightIndex, light, transform);
+}
+
+/*!
+ * \brief Removes all lights from this context
+ */
+M3G_API void m3gClearLights(M3GRenderContext context)
+{
+    RenderContext *ctx = (RenderContext *)context;
+    M3G_VALIDATE_OBJECT(ctx);
+    ctx->lastScope = 0;
+    m3gClearLights2(&ctx->lightManager);
+}
+
+/*!
+ * \brief Sets the viewport
+ *
+ */
+M3G_API void m3gSetViewport(M3GRenderContext hCtx,
+                            M3Gint x, M3Gint y,
+                            M3Gint width, M3Gint height)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    /* Note that the error checking here differs from that specified
+     * for the Java API; this is to avoid complications when setting
+     * from BindTarget where the clip rectangle may be zero.
+     * Additional checks are performed in the Java glue code. */
+    
+    if (width < 0 || height < 0) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_VALUE);
+        return;
+    }
+    
+    width = M3G_MIN(width, M3G_MAX_VIEWPORT_DIMENSION);
+    height = M3G_MIN(height, M3G_MAX_VIEWPORT_DIMENSION);
+    
+    ctx->viewport.x = x;
+    ctx->viewport.y = ctx->target.height - (y + height);
+    ctx->viewport.width = width;
+    ctx->viewport.height = height;
+    m3gUpdateScissor(ctx);
+}
+
+
+/*!
+ * \brief Gets the viewport
+ */
+M3G_API void m3gGetViewport(M3GRenderContext hCtx,
+                            M3Gint *x, M3Gint *y,
+                            M3Gint *width, M3Gint *height)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    *x = ctx->viewport.x;
+    *y = ctx->target.height - (ctx->viewport.y + ctx->viewport.height);
+    *width = ctx->viewport.width;
+    *height = ctx->viewport.height;
+}
+
+/*!
+ * \brief Sets the scissor rectangle
+ */
+M3G_API void m3gSetClipRect(M3GRenderContext hCtx,
+                            M3Gint x, M3Gint y,
+                            M3Gint width, M3Gint height)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    if (width < 0 || height < 0) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_VALUE);
+        return;
+    }
+    ctx->clip.x0 = x;
+    ctx->clip.y0 = ctx->target.height - (y + height);
+    ctx->clip.x1 = x + width;
+    ctx->clip.y1 = ctx->clip.y0 + height;
+    m3gValidateClipRect(ctx);
+    m3gUpdateScissor(ctx);
+}
+
+/*!
+ * \brief Sets the physical display area
+ *
+ * The display are is normally set to the full rendering targte size
+ * in m3gBindTarget, but this function allows overriding the default
+ * setting.
+ *
+ * Any pixels outside of the display area can be discarded for
+ * performance.  The origin is assumed to be in the top-left corner of
+ * the rendering target.
+ */
+M3G_API void m3gSetDisplayArea(M3GRenderContext hCtx,
+                               M3Gint width, M3Gint height)
+{
+    RenderContext *ctx = (RenderContext*) hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    
+    ctx->display.width = M3G_MIN(width, ctx->target.width);
+    ctx->display.height = M3G_MIN(height, ctx->target.height);
+}
+
+/*!
+ * \brief Sets depth range
+ * 
+ */
+M3G_API void m3gSetDepthRange(M3GRenderContext hCtx,
+                              M3Gfloat depthNear, M3Gfloat depthFar)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+	
+	if (depthNear < 0 || depthNear > 1.0f || depthFar < 0 || depthFar > 1.0f) {
+		m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_VALUE);
+        return;
+    }
+
+	ctx->depthNear = depthNear;
+	ctx->depthFar = depthFar;
+}
+
+/*!
+ * \brief Gets depth range
+ * 
+ */
+M3G_API void m3gGetDepthRange(M3GRenderContext hCtx,
+                              M3Gfloat *depthNear, M3Gfloat *depthFar)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+	
+	*depthNear = ctx->depthNear;
+	*depthFar= ctx->depthFar;
+}
+
+/*!
+ * \brief Gets current view transform
+ * 
+ */
+
+M3G_API void m3gGetViewTransform(M3GRenderContext hCtx,
+                                 M3GMatrix *transform)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    m3gSetMatrixColumns(transform, ctx->viewTransform);
+    m3gInvertMatrix(transform); /*lint !e534 always invertible */
+}
+
+/*!
+ * \brief Gets current Camera
+ * 
+ */
+
+M3G_API M3GCamera m3gGetCamera(M3GRenderContext hCtx)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    return (M3GCamera) ctx->camera;
+}
+
+/*!
+ * \brief Gets light transform of given light
+ * 
+ */
+
+M3G_API M3GLight m3gGetLightTransform (M3GRenderContext hCtx,
+                                       M3Gint lightIndex, M3GMatrix *transform)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    return m3gGetLightTransformInternal(&ctx->lightManager, lightIndex, transform);
+}
+
+/*!
+ * \brief Gets light count
+ * 
+ */
+
+M3G_API M3Gsizei m3gGetLightCount (M3GRenderContext hCtx)
+{
+    RenderContext *ctx = (RenderContext *)hCtx;
+    M3G_VALIDATE_OBJECT(ctx);
+    return m3gLightArraySize(&ctx->lightManager);
+}
+
+/*!
+ * \brief Renders a world
+ *
+ */
+M3G_API void m3gRenderWorld(M3GRenderContext context, M3GWorld hWorld)
+{
+	Camera *camera;
+    RenderContext *ctx = (RenderContext*) context;
+	World *world = (World *) hWorld;
+
+    M3G_LOG1(M3G_LOG_STAGES, "Rendering World 0x%08X\n", (unsigned) world);
+    
+    M3G_VALIDATE_OBJECT(ctx);
+    M3G_VALIDATE_OBJECT(world);
+
+	camera = m3gGetActiveCamera(world);
+
+    /* Check for errors */
+    
+    if (ctx->target.type == SURFACE_NONE) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OPERATION);
+        return;
+    }
+    
+	if (camera == NULL ||
+        !m3gIsChildOf((Node *)world, (Node *)camera)) {
+		m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OPERATION);
+        return;
+	}
+
+    /* Exit if the camera will show nothing (zero view volume) */
+    
+    if (!m3gValidProjection(camera)) {
+        return;
+    }
+
+    /* Override the currently set viewing transformation with identity
+     * (will fix this before we return) */
+    
+	m3gSetCamera(ctx, camera, NULL);
+
+    if (m3gValidateBackground(ctx, world->background)) {
+    	m3gClearInternal(ctx, world->background);
+    }
+    else {
+        m3gRaiseError(M3G_INTERFACE(world), M3G_INVALID_OPERATION);
+        return;
+    }
+
+    /* All clear for rendering */
+    
+    M3G_LOG(M3G_LOG_RENDERING, "Rendering: start\n");    
+    M3G_ASSERT(ctx->renderQueue->root == NULL);
+    M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_VALIDATE);
+    
+    if (m3gValidateNode((Node*) world, NODE_RENDER_BIT, camera->node.scope)) {
+        M3Gbool setup;
+        SetupRenderState s;
+        M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_VALIDATE);
+
+        /* We start the traversal from the camera, so set the initial
+         * camera-space transformation to identity */
+        
+        m3gIdentityMatrix(&s.toCamera);
+        s.cullMask = CULLMASK_ALL;
+        
+        m3gClearLights2(&ctx->lightManager);
+        
+        ctx->renderQueue->root = (Node *)world;
+        ctx->renderQueue->scope = camera->node.scope;
+        ctx->renderQueue->lightManager = &ctx->lightManager;
+        ctx->renderQueue->camera = camera;
+
+        M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_SETUP);
+        
+        setup = M3G_VFUNC(Node, camera, setupRender)((Node *) camera,
+                                                     NULL,
+                                                     &s,
+                                                     ctx->renderQueue);
+        M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_SETUP);
+        M3G_LOG(M3G_LOG_RENDERING, "Rendering: commit\n");
+        M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_COMMIT);
+
+        if (setup) {
+            m3gInitRender(ctx, RENDER_WORLD);
+            m3gLockFrameBuffer(ctx);
+            m3gCommit(ctx->renderQueue, ctx);
+            m3gReleaseFrameBuffer(ctx);
+        }
+        
+        M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_COMMIT);
+
+        /* Fix light and camera transformations to be relative to world
+         * space on exit */
+    
+        if (setup) {
+            Matrix m;
+            if (m3gGetTransformTo((Node*) world, (Node*) camera, &m)) {
+                m3gGetMatrixColumns(&m, ctx->viewTransform);
+                if (m3gInvertMatrix(&m)) {
+                    m3gTransformLights(&ctx->lightManager, &m);
+                }
+                else {
+                    M3G_ASSERT(M3G_FALSE);
+                }
+            }
+            else {
+                M3G_ASSERT(M3G_FALSE);
+            }
+        }
+    }
+    
+	m3gClearRenderQueue(ctx->renderQueue);
+    M3G_LOG(M3G_LOG_RENDERING, "Rendering: end\n");
+}
+
+/*!
+ * \brief Renders a node or subtree
+ */
+M3G_API void m3gRenderNode(M3GRenderContext context,
+                           M3GNode hNode,
+                           const M3GMatrix *transform)
+{
+    RenderContext *ctx = (RenderContext*) context;
+    Node *node = (Node *) hNode;
+
+    M3G_LOG1(M3G_LOG_STAGES, "Rendering Node 0x%08X\n", (unsigned) node);
+    
+    M3G_VALIDATE_OBJECT(ctx);
+    M3G_VALIDATE_OBJECT(node);
+
+    /* Check for errors */
+    
+    if (node == NULL) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_NULL_POINTER);
+        return;
+    }
+
+    if (ctx->target.type == SURFACE_NONE || ctx->camera == NULL) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OPERATION);
+        return;
+    }
+
+    /* Exit if the camera will show nothing (zero view volume) */
+    
+    if (!m3gValidProjection(ctx->camera)) {
+        return;
+    }
+
+    /* All clear, draw away */
+
+    M3G_LOG(M3G_LOG_RENDERING, "Rendering: start\n");
+	M3G_ASSERT(ctx->renderQueue->root == NULL);
+    M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_VALIDATE);
+    
+    if (m3gValidateNode(node, NODE_RENDER_BIT, ctx->camera->node.scope)) {
+        M3Gbool setup;
+        SetupRenderState s;
+        M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_VALIDATE);
+        
+        s.cullMask = CULLMASK_ALL;
+
+        /* We start the traversal from world space, so preload the
+         * current camera-space transformation to get camera-space
+         * meshes and correct view frustum culling */
+        
+        m3gSetMatrixColumns(&s.toCamera, ctx->viewTransform);
+        if (transform) {
+            m3gMulMatrix(&s.toCamera, transform);
+        }
+        ctx->renderQueue->root = (Node *) node;
+        ctx->renderQueue->scope = ctx->camera->node.scope;
+        ctx->renderQueue->lightManager = NULL;
+        ctx->renderQueue->camera = ctx->camera;
+        
+        M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_SETUP);
+        
+        setup = M3G_VFUNC(Node, node, setupRender)(node,
+                                                   NULL,
+                                                   &s,
+                                                   ctx->renderQueue);
+        M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_SETUP);
+        M3G_LOG(M3G_LOG_RENDERING, "Rendering: commit\n");
+        M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_COMMIT);
+        
+        if (setup) {
+            m3gInitRender(ctx, RENDER_NODES);
+            m3gLockFrameBuffer(ctx);
+    		m3gCommit(ctx->renderQueue, ctx);
+            m3gReleaseFrameBuffer(ctx);
+        }
+        
+        M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_COMMIT);
+	}
+
+	m3gClearRenderQueue(ctx->renderQueue);
+    
+    M3G_LOG(M3G_LOG_RENDERING, "Rendering: end\n");
+}
+
+/*!
+ * \brief Render a set of primitives
+ * 
+ */
+M3G_API void m3gRender(M3GRenderContext context,
+                       M3GVertexBuffer hVertices,
+                       M3GIndexBuffer hIndices,
+                       M3GAppearance hAppearance,
+                       const M3GMatrix *transformMatrix,
+                       M3Gfloat alphaFactor,
+                       M3Gint scope)
+{
+    RenderContext *ctx = (RenderContext *) context;
+    const VertexBuffer *vb = (const VertexBuffer *) hVertices;
+    const IndexBuffer *ib = (const IndexBuffer *) hIndices;
+    const Appearance *app = (const Appearance *) hAppearance;
+    M3G_VALIDATE_OBJECT(ctx);
+
+    M3G_LOG1(M3G_LOG_STAGES, "Rendering vertex buffer 0x%08X\n",
+             (unsigned) vb);
+    
+    /* Check validity of input */
+    
+    if (ctx->target.type == SURFACE_NONE || ctx->camera == NULL) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OPERATION);
+        return;
+    }
+    
+    /* Quick exit if out of scope or zero view volume */
+
+    if ((scope & ctx->camera->node.scope) == 0
+        || !m3gValidProjection(ctx->camera)) {
+        return;
+    }
+
+    if (vb == NULL || ib == NULL || app == NULL) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OBJECT);
+        return;
+    }
+
+    if (!m3gValidateVertexBuffer(vb, app, m3gGetMaxIndex(ib))) {
+        m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OPERATION);
+        return;
+    }
+
+    /* Everything checks out, so draw */
+
+    M3G_LOG(M3G_LOG_RENDERING, "Rendering: start immediate\n");
+    
+    m3gInitRender(ctx, RENDER_IMMEDIATE);
+    m3gLockFrameBuffer(ctx);
+    m3gDrawMesh(ctx,
+                vb, ib, app,
+                transformMatrix,
+                m3gRoundToInt(
+                    m3gMul(alphaFactor,
+                           (M3Gfloat)(1 << NODE_ALPHA_FACTOR_BITS))),
+                scope);
+    m3gReleaseFrameBuffer(ctx);
+    
+    M3G_LOG(M3G_LOG_RENDERING, "Rendering: end immediate\n");
+}
+