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