--- /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");
+}
+