--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_interface.c Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,1864 @@
+/*
+* 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: Interface function implementation
+*
+*/
+
+
+/*!
+ * \internal
+ * \file
+ * \brief Interface function implementation
+ */
+
+#ifndef M3G_CORE_INCLUDE
+# error included by m3g_core.c; do not compile separately.
+#endif
+
+#include "m3g_array.h"
+#include "m3g_gl.h"
+
+#if defined(M3G_DEBUG_OUT_OF_MEMORY)
+# include <stdlib.h> /* for getenv() and atoi() */
+#endif
+
+/*----------------------------------------------------------------------
+ * Private data structure(s)
+ *--------------------------------------------------------------------*/
+
+#define MAX_LOCKHEAP_SIZE 100
+
+typedef struct StartGuardRec HeapBlock;
+
+/*!
+ * \internal
+ * \brief "Interface" structure
+ *
+ * Holds global state for an M3G instance
+ */
+struct M3GInterfaceImpl
+{
+ /*!
+ * \internal
+ * \brief Interface function pointers
+ *
+ * Each interface can have a separate set of functions for memory
+ * allocation.
+ */
+ struct {
+ /*@shared@*/ m3gMallocFunc *malloc;
+ /*@shared@*/ m3gFreeFunc *free;
+ /*@shared@*/ m3gObjectAllocator *objAlloc;
+ /*@shared@*/ m3gObjectResolver *objResolve;
+ /*@shared@*/ m3gObjectDeallocator *objFree;
+ /*@shared@*/ m3gErrorHandler *error;
+ /*@shared@*/ m3gBeginRenderFunc *getFrameBuffer;
+ /*@shared@*/ m3gEndRenderFunc *releaseFrameBuffer;
+ /*@shared@*/ m3gReleaseTargetFunc *releaseTarget;
+ } func;
+
+ /*Object *objects;*/
+
+ /*! \internal \brief Latest error code for this interface */
+ M3Genum error;
+
+ /*! \internal \brief Associated user context data */
+ void *userContext;
+
+# if defined(M3G_DEBUG)
+ /*! \internal \brief Number of memory lock requests in effect */
+ M3Gint lockCount;
+ struct {
+ const char *file;
+ int line;
+ } lockHeap[MAX_LOCKHEAP_SIZE];
+# endif
+
+# if !defined(M3G_NGL_CONTEXT_API)
+ /*! \internal \brief Number of GL activation requests */
+ M3Gint glRefCount;
+# endif
+
+ /*! \internal \brief Number of objects registered for this interface */
+ M3Gint objCount;
+
+ /*! \internal \brief "Shutdown" flag for when we need to wait for
+ * objects to be deleted */
+ M3Gbool shutdown;
+
+ /* Temporary buffer */
+ void *tempBuffer;
+ M3Gsizei tempSize;
+ M3Gbool tempLocked;
+
+ /* Transformation cache */
+ TCache *tcache;
+
+# if !defined(M3G_NGL_TEXTURE_API)
+ PointerArray deadGLObjects;
+# endif
+
+# if defined(M3G_ENABLE_PROFILING)
+ /*!
+ * \internal
+ * \brief Statistics counters
+ */
+ M3Gint statistics[M3G_STAT_MAX];
+ M3Gint lastPeak;
+ M3Gint profileInterval;
+# endif /*M3G_ENABLE_PROFILING*/
+
+ /* Memory allocation debug counters */
+# if defined(M3G_DEBUG)
+ M3Gint mallocCount;
+ M3Gint objAllocCount;
+# endif
+
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ M3Gsize mallocBytes, mallocLimit;
+ M3Gsize objAllocBytes, objAllocLimit;
+ M3Gint mallocFailureCounter, mallocFailRate;
+ M3Gint objAllocFailureCounter, objAllocFailRate;
+# endif
+
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ HeapBlock *blockList;
+# endif
+
+ M3Gint maxTextureDimension;
+ M3Gint maxViewportWidth;
+ M3Gint maxViewportHeight;
+ M3Gint maxViewportDim;
+ M3Gbool supportAntialiasing;
+ M3Gbool colorMaskWorkaround;
+ M3Gbool twoSidedLightingWorkaround;
+};
+
+#if defined(M3G_DEBUG)
+
+typedef struct StartGuardRec
+{
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ const char *allocFile;
+ int allocLine : 31;
+ int isObject : 1;
+ HeapBlock *next, *prev;
+# endif
+
+ M3Guint endOffset;
+ M3Guint magic;
+} StartGuard;
+
+typedef struct
+{
+ M3Guint magic;
+} EndGuard;
+
+/* Magic number used to tag memory blocks */
+#define MEMORY_MAGIC 0xAE352001u
+
+/* Macros for computing the instrumentated and "user" sizes and
+ * pointers of memory blocks */
+
+/*@notfunction@*/
+# define INSTRUMENTATED_SIZE(bytes) \
+ ((M3Guint) (sizeof(StartGuard) + sizeof(EndGuard) \
+ + M3G_ALIGN_TO(bytes, sizeof(M3Guint))))
+/*@notfunction@*/
+# define PAYLOAD_BLOCK(physicalPtr) \
+ (((M3Gubyte*)physicalPtr) + sizeof(StartGuard))
+/*@notfunction@*/
+# define PHYSICAL_BLOCK(payloadPtr) \
+ (((M3Gubyte*)payloadPtr) - sizeof(StartGuard))
+/*@notfunction@*/
+# define PAYLOAD_SIZE(blockPtr) \
+ (((const StartGuard *)PHYSICAL_BLOCK(blockPtr))->endOffset \
+ - sizeof(StartGuard))
+
+#else /* !M3G_DEBUG */
+
+/*@notfunction@*/
+# define INSTRUMENTATED_SIZE(bytes) ((M3Guint)(bytes))
+/*@notfunction@*/
+# define PAYLOAD_BLOCK(ptr) (ptr)
+/*@notfunction@*/
+# define PHYSICAL_BLOCK(ptr) (ptr)
+/*@notfunction@*/
+# define PAYLOAD_SIZE(blockPtr) 0
+
+#endif /* M3G_DEBUG */
+
+/*----------------------------------------------------------------------
+ * Static data for managing NGL contexts
+ *--------------------------------------------------------------------*/
+
+#if defined(M3G_NGL_CONTEXT_API)
+static M3Gint m3gs_glRefCount = 0;
+
+#if defined(M3G_BUILD_ISA)
+# define M3G_SYSTEM_ALLOC os_block_alloc
+# define M3G_SYSTEM_DEALLOC os_block_dealloc
+#else
+# define M3G_SYSTEM_ALLOC malloc
+# define M3G_SYSTEM_DEALLOC free
+#endif
+
+static void *m3gs_nglMem;
+static void *m3gs_nglTexMgr;
+#endif
+
+#if defined(M3G_NGL_TEXTURE_API)
+M3Gubyte *m3gs_nglTexUnit[M3G_NUM_TEXTURE_UNITS];
+#endif
+
+/*----------------------------------------------------------------------
+ * Private functions
+ *--------------------------------------------------------------------*/
+
+/*@access M3GMemObject@*/
+static /*@dependent@*/ void *defaultResolver(M3GMemObject handle)
+{
+ return (void *)handle;
+}
+
+/*--------------------------------------------------------------------*/
+
+#if defined(M3G_DEBUG)
+
+static void validateBlock(/*@reldef@*//*@temp@*//*@null@*/ const void *ptr)
+{
+ if (ptr != NULL && M3G_IS_ALIGNED(ptr)) {
+ const StartGuard *start = (const StartGuard *) PHYSICAL_BLOCK(ptr);
+ if (start->magic == MEMORY_MAGIC) {
+ const EndGuard *end = (const EndGuard *)
+ (((M3Gubyte *)start) + start->endOffset);
+ if (end->magic == MEMORY_MAGIC) {
+ return; /* all clear */
+ }
+ }
+ M3G_LOG1(M3G_LOG_FATAL_ERRORS,
+ "Corrupted memory block 0x%08X!\n", (unsigned) ptr);
+ M3G_ASSERT(M3G_FALSE);
+ }
+ else {
+ M3G_LOG1(M3G_LOG_FATAL_ERRORS,
+ "Invalid pointer to 0x%08X!\n", (unsigned) ptr);
+ M3G_ASSERT(M3G_FALSE);
+ }
+}
+
+#if defined(M3G_DEBUG_HEAP_TRACKING)
+static void instrumentateBlock(/*@reldef@*//*@temp@*/ void *ptr,
+ M3Gsize bytes,
+ const char *file,
+ int line)
+#else
+static void instrumentateBlock(/*@reldef@*//*@temp@*/ void *ptr, M3Gsize bytes)
+#endif
+{
+ M3Guint offset = M3G_ALIGN_TO(bytes, M3G_ALIGNMENT) + sizeof(StartGuard);
+ StartGuard *start;
+ EndGuard *end;
+
+ M3G_ASSERT_PTR(ptr);
+ M3G_ASSERT_ALIGNMENT(offset);
+
+ /* Insert start and end guard blocks for holding debugging data as
+ * well as guarding against (short) over- and underruns */
+
+ start = (StartGuard *) PHYSICAL_BLOCK(ptr);
+ end = (EndGuard *) (((M3Gubyte *)start) + offset);
+
+ start->endOffset = offset;
+ start->magic = MEMORY_MAGIC;
+ end->magic = MEMORY_MAGIC;
+
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ start->isObject = 0;
+# endif
+
+ /* Fill with garbage that will show up on the debugger if used
+ * before initialized */
+ {
+ M3Guint *p = (M3Guint *) ptr;
+ M3Guint count = bytes >> 2;
+
+ while (count--) {
+ *p++ = 0xBAADF00Du;
+ }
+ }
+
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ /* Register allocation location */
+ start->allocFile = file;
+ start->allocLine = line;
+# endif
+
+ validateBlock(ptr);
+}
+
+static void destroyBlock(/*@reldef@*//*@temp@*//*@null@*/ void *ptr)
+{
+ if (ptr != NULL) {
+ validateBlock(ptr);
+ {
+ /* Fill with garbage that will show up on the debugger if
+ * used after deallocation */
+
+ StartGuard *start = (StartGuard *) PHYSICAL_BLOCK(ptr);
+ M3Guint *p = (M3Guint *) start;
+ M3Guint count = (start->endOffset + sizeof(EndGuard)) >> 2;
+
+ while (count--) {
+ *p++ = 0xDEADBEEFu;
+ }
+ }
+ }
+}
+
+#if defined(M3G_DEBUG_HEAP_TRACKING)
+static void insertBlock(void *ptr, HeapBlock **blockListHead)
+{
+ HeapBlock *head = *blockListHead;
+ HeapBlock *block = (HeapBlock *) PHYSICAL_BLOCK(ptr);
+
+ M3G_ASSERT_PTR(block);
+
+ *blockListHead = block;
+ block->prev = NULL;
+ block->next = head;
+
+ if (head != NULL) {
+ head->prev = block;
+ }
+}
+#endif
+
+#if defined(M3G_DEBUG_HEAP_TRACKING)
+static void removeBlock(void *ptr, HeapBlock **blockListHead)
+{
+ if (ptr != NULL) {
+ HeapBlock *head = *blockListHead;
+ HeapBlock *block = (HeapBlock *) PHYSICAL_BLOCK(ptr);
+
+ validateBlock(ptr);
+
+ M3G_ASSERT_PTR(head);
+ M3G_ASSERT_PTR(block);
+
+ if (block->prev) {
+ block->prev->next = block->next;
+ }
+ if (block->next) {
+ block->next->prev = block->prev;
+ }
+ if (block == head) {
+ *blockListHead = block->next;
+ }
+
+ block->next = NULL;
+ block->prev = NULL;
+ }
+}
+#endif
+
+#if defined(M3G_DEBUG_HEAP_TRACKING) && defined(M3G_LOGLEVEL)
+static void dumpBlocks(const HeapBlock *head)
+{
+ while (head) {
+ M3G_LOG4(M3G_LOG_FATAL_ERRORS,
+ "0x%08X: %s:%d, %d bytes\n",
+ (unsigned) PAYLOAD_BLOCK(head),
+ head->allocFile,
+ head->allocLine,
+ (M3Gsizei) PAYLOAD_SIZE(PAYLOAD_BLOCK(head)));
+ head = head->next;
+ }
+}
+#endif
+
+#undef MEMORY_MAGIC
+
+#else /* !M3G_DEBUG */
+# define instrumentateBlock(ptr, bytes)
+# define validateBlock(ptr)
+# define destroyBlock(ptr)
+#endif /* M3G_DEBUG */
+
+/*--------------------------------------------------------------------*/
+
+/*----------------------------------------------------------------------
+ * Internal functions
+ *--------------------------------------------------------------------*/
+
+#if defined(M3G_DEBUG)
+
+/*!
+ * \internal
+ * \brief Locks all memory allocation for an interface
+ *
+ * This is used to ensure that memory compacting does not affect the
+ * addresses of our "memory objects" while they're in use. A count of
+ * locks is maintained; the same interface may be locked several
+ * times, and each lock must be separately released with a call to
+ * m3gUnlockMemory.
+ *
+ * Memory is also automatically locked while a handle is mapped to a
+ * pointer; see m3gMapObject.
+ *
+ */
+static void m3gDebugLockMemory(Interface *m3g, const char *file, int line)
+{
+ M3G_ASSERT(m3gInRange(m3g->lockCount, 0, 0x7FFFFFFE));
+ M3G_ASSERT(m3g->lockCount < MAX_LOCKHEAP_SIZE);
+
+ m3g->lockHeap[m3g->lockCount].file = file;
+ m3g->lockHeap[m3g->lockCount].line = line;
+ m3g->lockCount++;
+
+ m3gIncStat(m3g, M3G_STAT_MEMORY_LOCKS, 1);
+}
+
+/*!
+ * \internal
+ * \brief Releases a memory allocation lock on an interface
+ */
+static void m3gUnlockMemory(Interface *m3g)
+{
+ M3G_ASSERT(m3g->lockCount > 0);
+ m3g->lockHeap[m3g->lockCount].file = "";
+ m3g->lockHeap[m3g->lockCount].line = 0;
+ m3g->lockCount--;
+}
+
+/*!
+ * \internal
+ * \brief Checks whether memory is currently locked (debug only)
+ */
+static M3Gbool m3gMemoryLocked(Interface *m3g)
+{
+ M3G_ASSERT(m3g->lockCount >= 0);
+
+ if (m3g->lockCount > 0) {
+# if defined(M3G_LOGLEVEL)
+ int i;
+ M3G_LOG1(M3G_LOG_FATAL_ERRORS,
+ "%d memory lock(s) in effect:\n", m3g->lockCount);
+ for (i = m3g->lockCount - 1; i >= 0; --i) {
+ M3G_LOG2(M3G_LOG_FATAL_ERRORS, "%s, line %d\n",
+ m3g->lockHeap[i].file, m3g->lockHeap[i].line);
+ }
+# endif
+ return M3G_TRUE;
+ }
+
+ return M3G_FALSE;
+}
+#endif
+
+/*!
+ * \internal
+ * \brief Allocates a block of memory from a regular C-style heap
+ *
+ */
+#if defined(M3G_DEBUG)
+static void *m3gDebugAlloc(
+ Interface *m3g, M3Gsize bytes, const char *file, int line)
+#else
+static void *m3gAlloc(Interface *m3g, M3Gsize bytes)
+#endif
+{
+ void *ptr;
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_NO_LOCK(m3g);
+
+ /* Simulate memory allocation failures if enabled */
+
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ if (m3g->mallocFailRate > 0 &&
+ m3g->mallocFailureCounter++ >= m3g->mallocFailRate) {
+ m3g->mallocFailureCounter = 0;
+ goto AllocFailed;
+ }
+ if (m3g->mallocLimit > 0 &&
+ m3g->mallocBytes + bytes > m3g->mallocLimit) {
+ goto AllocFailed;
+ }
+# endif
+
+ /* First just try to allocate more memory; if that fails, garbage
+ * collect and try again before returning with an error */
+
+ ptr = (*m3g->func.malloc)(INSTRUMENTATED_SIZE(bytes));
+ if (ptr == NULL) {
+ M3G_LOG(M3G_LOG_WARNINGS|M3G_LOG_MEMORY_ALL,
+ "Warning: heap alloc failed\n");
+ m3gGarbageCollectAll(m3g);
+ ptr = (*m3g->func.malloc)(INSTRUMENTATED_SIZE(bytes));
+ }
+ if (ptr == NULL) {
+ goto AllocFailed;
+ }
+
+ /* Succesfully allocated some, so update statistics */
+
+# if defined(M3G_DEBUG)
+ m3g->mallocCount++;
+# endif
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ m3g->mallocBytes += bytes;
+# endif
+
+ /* Add instrumentation to the block */
+
+ M3G_ASSERT_ALIGNMENT(ptr);
+ ptr = PAYLOAD_BLOCK(ptr);
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ insertBlock(ptr, &m3g->blockList);
+ instrumentateBlock(ptr, bytes, file, line);
+# else
+ instrumentateBlock(ptr, bytes);
+# endif
+
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCS, 1);
+# if defined(M3G_DEBUG) && defined(M3G_ENABLE_PROFILING)
+ {
+ M3Gint size = PAYLOAD_SIZE(ptr);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCATED, size);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_MALLOC_BYTES, size);
+ }
+# else
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCATED, bytes);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_MALLOC_BYTES, bytes);
+# endif
+
+# if defined(M3G_DEBUG)
+ M3G_LOG4(M3G_LOG_MEMORY_BLOCKS,
+ "Alloc 0x%08X, %d bytes (%s, line %d)\n",
+ (unsigned) ptr, bytes, file, line);
+# else
+ M3G_LOG2(M3G_LOG_MEMORY_BLOCKS, "Alloc 0x%08X, %d bytes\n",
+ (unsigned) ptr, bytes);
+# endif
+
+ m3gUpdateMemoryPeakCounter(m3g);
+
+ return ptr;
+
+AllocFailed:
+ m3gRaiseError(m3g, M3G_OUT_OF_MEMORY);
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Same as m3gAlloc, but also zero-initializes the allocated
+ * block
+ */
+#if !defined(M3G_DEBUG)
+static void *m3gAllocZ(Interface *m3g, M3Gsize bytes)
+{
+ void *ptr = m3gAlloc(m3g, bytes);
+ if (ptr != NULL) {
+ m3gZero(ptr, bytes);
+ }
+ return ptr;
+}
+#else
+static void *m3gDebugAllocZ(
+ Interface *m3g, M3Gsize bytes, const char *file, int line)
+{
+ void *ptr = m3gDebugAlloc(m3g, bytes, file, line);
+ if (ptr != NULL) {
+ m3gZero(ptr, bytes);
+ }
+ return ptr;
+}
+#endif /* M3G_DEBUG_HEAP_TRACKING */
+
+/*!
+ * \internal
+ * \brief Frees a block of memory allocated using m3gAlloc
+ */
+#if defined(M3G_DEBUG)
+static void m3gDebugFree(Interface *m3g, void *ptr, const char *file, int line)
+#else
+static void m3gFree(Interface *m3g, void *ptr)
+#endif
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_ALIGNMENT(ptr);
+ M3G_ASSERT_NO_LOCK(m3g);
+
+ if (ptr != NULL) {
+ M3G_VALIDATE_MEMBLOCK(ptr);
+ M3G_ASSERT(!m3gIsObject(ptr));
+
+# if defined(M3G_DEBUG)
+ m3g->mallocCount--;
+ M3G_ASSERT(m3g->mallocCount >= 0);
+# endif
+
+#if defined(M3G_ENABLE_PROFILING) || defined(M3G_DEBUG_OUT_OF_MEMORY)
+ {
+ M3Gint size = PAYLOAD_SIZE(ptr);
+# if defined(M3G_ENABLE_PROFILING)
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCATED, -size);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_MALLOC_BYTES, -size);
+# endif
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ m3g->mallocBytes -= size;
+# endif
+ }
+#endif
+
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ removeBlock(ptr, &m3g->blockList);
+# endif
+
+
+# if defined(M3G_DEBUG)
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ M3G_LOG4(M3G_LOG_MEMORY_BLOCKS,
+ "Free 0x%08X, %d bytes (%s, line %d)\n",
+ (unsigned) ptr, PAYLOAD_SIZE(ptr), file, line);
+# else
+ M3G_LOG3(M3G_LOG_MEMORY_BLOCKS,
+ "Free 0x%08X (%s, line %d)\n",
+ (unsigned) ptr, file, line);
+# endif
+# else
+ M3G_LOG1(M3G_LOG_MEMORY_BLOCKS, "Free 0x%08X\n", (unsigned) ptr);
+# endif
+
+ destroyBlock(ptr);
+ (*m3g->func.free)(PHYSICAL_BLOCK(ptr));
+ }
+}
+
+#if 0
+
+/*!
+ * \brief Recycles a block of memory for later use
+ *
+ * Same as free, but instead of always returning the memory to the OS,
+ * may place it on a list of free blocks for reuse. Blocks in the
+ * free list can be quickly reused by m3gAlloc.
+ *
+ * \param m3g Interface instance
+ * \param ptr pointer to block to recycle
+ * \param bytes size of the block, in bytes
+ */
+static void m3gRecycle(Interface *m3g, void *ptr, M3Gsize bytes)
+{
+ M3G_UNREF(bytes);
+ m3gFree(m3g, ptr);
+}
+
+#endif
+
+#if defined(M3G_DEBUG)
+/*!
+ * \internal
+ * \brief Checks the integrity of a memory block
+ */
+static void m3gValidateMemory(const void *ptr)
+{
+ validateBlock(ptr);
+}
+#endif /* M3G_DEBUG */
+
+#if defined(M3G_DEBUG)
+/*!
+ * \internal
+ * \brief Checks the integrity of an Interface object
+ */
+static void m3gValidateInterface(const Interface *m3g)
+{
+ M3G_VALIDATE_MEMBLOCK(m3g);
+}
+#endif /* M3G_DEBUG */
+
+#if defined (M3G_DEBUG_HEAP_TRACKING)
+/*!
+ * \internal
+ * \brief Marks a block as a live object block
+ *
+ * Live objects can never have their memory freed.
+ */
+static void m3gMarkObject(void *ptr)
+{
+ StartGuard *start;
+ validateBlock(ptr);
+
+ start = (StartGuard *) PHYSICAL_BLOCK(ptr);
+ start->isObject = 1;
+}
+#endif
+
+#if defined (M3G_DEBUG_HEAP_TRACKING)
+/*!
+ * \internal
+ * \brief Checks if a block is an object block
+ */
+static M3Gbool m3gIsObject(const void *ptr)
+{
+ const StartGuard *start;
+ validateBlock(ptr);
+
+ start = (StartGuard *) PHYSICAL_BLOCK(ptr);
+ return (start->isObject != 0);
+}
+#endif
+
+#if defined (M3G_DEBUG_HEAP_TRACKING)
+/*!
+ * \internal
+ * \brief Unmarks an object block
+ */
+static void m3gUnmarkObject(void *ptr)
+{
+ StartGuard *start;
+ validateBlock(ptr);
+
+ start = (StartGuard *) PHYSICAL_BLOCK(ptr);
+ start->isObject = 0;
+}
+#endif
+
+
+/*!
+ * \internal
+ * \brief Allocates a "memory object" from a potentially compacting heap
+ *
+ * A block of memory of \c bytes is allocated, but its address is
+ * available only via the m3gMapObject function.
+ *
+ * \return a handle used to refer to the allocated block during the
+ * rest of its lifetime
+ */
+/*@access M3GMemObject@*/
+#if defined(M3G_DEBUG)
+static M3GMemObject m3gDebugAllocObject(
+ Interface *m3g, M3Gsize bytes, const char *file, int line)
+#else
+static M3GMemObject m3gAllocObject(Interface *m3g, M3Gsize bytes)
+#endif
+{
+ M3GMemObject handle;
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_NO_LOCK(m3g);
+
+ /* Simulate memory allocation failures if enabled */
+
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ if (m3g->objAllocFailRate > 0 &&
+ m3g->objAllocFailureCounter++ >= m3g->objAllocFailRate) {
+ m3g->objAllocFailureCounter = 0;
+ goto AllocFailed;
+ }
+ if (m3g->objAllocLimit > 0 &&
+ m3g->objAllocBytes + bytes > m3g->objAllocLimit) {
+ goto AllocFailed;
+ }
+# endif
+
+ /* Similarly to Alloc, garbage collect and try again if the
+ * first allocation fails */
+
+ handle = (*m3g->func.objAlloc)(INSTRUMENTATED_SIZE(bytes));
+ if (!handle) {
+ M3G_LOG(M3G_LOG_WARNINGS|M3G_LOG_MEMORY_ALL,
+ "Warning: object alloc failed\n");
+ m3gGarbageCollectAll(m3g);
+ handle = (*m3g->func.objAlloc)(INSTRUMENTATED_SIZE(bytes));
+ }
+ if (!handle) {
+ goto AllocFailed;
+ }
+
+ /* Succesfully allocated, update statistics */
+
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCS, 1);
+# if defined(M3G_DEBUG)
+ m3g->objAllocCount++;
+ if (handle != 0) {
+ void *ptr = PAYLOAD_BLOCK((*m3g->func.objResolve)(handle));
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ instrumentateBlock(ptr, bytes, file, line);
+# else
+ instrumentateBlock(ptr, bytes);
+# endif
+# if defined(M3G_ENABLE_PROFILING)
+ {
+ M3Gint size = PAYLOAD_SIZE(ptr);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCATED, size);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_OBJECT_BYTES, size);
+ }
+# endif
+ }
+# else
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCATED, bytes);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_OBJECT_BYTES, bytes);
+# endif
+
+# if defined(M3G_DEBUG)
+ M3G_LOG4(M3G_LOG_MEMORY_BLOCKS,
+ "ObjAlloc 0x%08X, %d bytes (%s, line %d)\n",
+ (unsigned) handle, bytes, file, line);
+# else
+ M3G_LOG2(M3G_LOG_MEMORY_BLOCKS, "ObjAlloc 0x%08X, %d bytes\n",
+ (unsigned) handle, bytes);
+# endif
+
+ m3gUpdateMemoryPeakCounter(m3g);
+
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ m3g->objAllocBytes += bytes;
+# endif
+
+ return handle;
+
+AllocFailed:
+ m3gRaiseError(m3g, M3G_OUT_OF_MEMORY);
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Frees a memory object allocated with m3gAllocObject
+ */
+/*@access M3GMemObject@*/
+#if defined(M3G_DEBUG)
+static void m3gDebugFreeObject(Interface *m3g, M3GMemObject handle, const char *file, int line)
+#else
+static void m3gFreeObject(Interface *m3g, M3GMemObject handle)
+#endif
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_NO_LOCK(m3g);
+
+ /* Debugging code */
+
+# if defined(M3G_DEBUG)
+ if (handle != 0) {
+ void *ptr = m3gMapObject(m3g, handle);
+ M3G_VALIDATE_MEMBLOCK(ptr);
+ M3G_ASSERT(!m3gIsObject(ptr));
+
+ if (ptr != NULL) {
+# if defined(M3G_ENABLE_PROFILING) || defined(M3G_DEBUG_OUT_OF_MEMORY)
+ M3Gint size = PAYLOAD_SIZE(ptr);
+# endif
+# if defined(M3G_ENABLE_PROFILING)
+ m3gIncStat(m3g, M3G_STAT_MEMORY_ALLOCATED, -size);
+ m3gIncStat(m3g, M3G_STAT_MEMORY_OBJECT_BYTES, -size);
+# endif
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ m3g->objAllocBytes -= size;
+# endif
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ M3G_LOG4(M3G_LOG_MEMORY_BLOCKS,
+ "ObjFree 0x%08X, %d bytes (%s, line %d)\n",
+ (unsigned) handle, PAYLOAD_SIZE(ptr), file, line);
+# else
+ M3G_LOG3(M3G_LOG_MEMORY_BLOCKS,
+ "ObjFree 0x%08X (%s, line %d)\n",
+ (unsigned) handle, file, line);
+# endif
+ }
+ m3gUnmapObject(m3g, handle);
+
+ destroyBlock(ptr);
+
+ m3g->objAllocCount--;
+ M3G_ASSERT(m3g->objAllocCount >= 0);
+ }
+# else /* !M3G_DEBUG*/
+ M3G_LOG1(M3G_LOG_MEMORY_BLOCKS, "ObjFree 0x%08X\n", (unsigned) handle);
+# endif
+
+ /* Actual operation */
+
+ (*m3g->func.objFree)(handle);
+}
+
+/*!
+ * \internal
+ * \brief Allocates a temporary data buffer
+ *
+ * The temporary buffer is intended for situations where more
+ * temporary data is required than can be allocated on the stack.
+ * Only one temporary buffer exists for each M3G interface, and it
+ * must be freed via \c m3gFreeTemp before reallocating.
+ *
+ * \param m3g interface object
+ * \param bytes size of the temp buffer requested, in bytes
+ */
+static void *m3gAllocTemp(Interface *m3g, M3Gsizei bytes)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_NO_LOCK(m3g);
+ M3G_ASSERT(!m3g->tempLocked);
+
+ if (m3g->tempSize < bytes) {
+ m3gFree(m3g, m3g->tempBuffer);
+ m3g->tempBuffer = NULL;
+ }
+
+ if (m3g->tempBuffer == NULL) {
+ m3g->tempBuffer = m3gAlloc(m3g, bytes);
+ if (m3g->tempBuffer == NULL) {
+ return NULL; /* automatic out of memory */
+ }
+ m3g->tempSize = bytes;
+ }
+
+ m3g->tempLocked = M3G_TRUE;
+ return m3g->tempBuffer;
+}
+
+/*!
+ * \internal
+ * \brief Release the currently allocated temporary data buffer
+ */
+static void m3gFreeTemp(Interface *m3g)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_NO_LOCK(m3g);
+ M3G_ASSERT(m3g->tempLocked);
+
+ m3g->tempLocked = M3G_FALSE;
+}
+
+/*!
+ * \internal
+ * \brief
+ */
+static void m3gAddChildObject(Interface *m3g)
+{
+ M3G_ASSERT(!m3g->shutdown);
+ M3G_ASSERT(m3gInRange(m3g->objCount, 0, 0x7FFFFFFF));
+ ++m3g->objCount;
+}
+
+/*!
+ * \internal
+ * \brief
+ */
+static void m3gDelChildObject(Interface *m3g)
+{
+ M3G_ASSERT(m3g->objCount > 0);
+ if (--m3g->objCount == 0 && m3g->shutdown) {
+ m3gDeleteInterface(m3g);
+ }
+}
+
+#if !defined(M3G_NGL_TEXTURE_API)
+/*!
+ * \internal
+ * \brief Queue OpenGL texture objects for deletion
+ *
+ * The objects will be deleted when a GL context is next made current.
+ *
+ */
+static void m3gDeleteGLTextures(Interface *m3g, M3Gsizei n, M3Guint *t)
+{
+ PointerArray *objs = &m3g->deadGLObjects;
+ while (n--) {
+ if (m3gArrayAppend(objs, (void*) t[n], m3g) < 0) {
+ return;
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Delete queued OpenGL objects
+ *
+ * This function should be called at suitable points during execution
+ * to delete dead GL texture objects. A GL context must be current.
+ */
+static void m3gCollectGLObjects(Interface *m3g)
+{
+ PointerArray *objs = &m3g->deadGLObjects;
+ M3Gsizei n = m3gArraySize(objs);
+ M3Gint i;
+ for (i = 0; i < n; ++i) {
+ GLuint t = (GLuint) m3gGetArrayElement(objs, i);
+ glDeleteTextures(1, &t);
+ M3G_LOG1(M3G_LOG_OBJECTS, "Destroyed GL texture object 0x%08X\n",
+ (unsigned) t);
+ }
+ m3gClearArray(objs);
+}
+#endif /* !defined(M3G_NGL_TEXTURE_API)*/
+
+
+/*!
+ * \internal
+ * \brief Locks a memory object in place and returns a pointer to it
+ *
+ * The block is mapped to the returned address until a matching call
+ * to m3gUnmapObject. While any object is mapped, memory allocation is
+ * prohibited on the whole interface; see m3gLockMemory. Every
+ * m3gMapObject call must be followed by a matching m3gUnmapObject
+ * call to release the memory lock.
+ */
+/*@access M3GMemObject@*/
+static void *m3gMapObject(Interface *m3g, M3GMemObject handle)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+
+ if (handle == 0) {
+ return NULL;
+ }
+ else {
+ void *ptr;
+
+ m3gLockMemory(m3g);
+
+ ptr = (*m3g->func.objResolve)(handle);
+ ptr = PAYLOAD_BLOCK(ptr);
+
+ M3G_LOG2(M3G_LOG_MEMORY_MAPPING, "MapObj 0x%08X -> 0x%08X\n",
+ (unsigned) handle, (unsigned) ptr);
+
+ validateBlock(ptr);
+ return ptr;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Releases a memory object locked with m3gMapObject
+ *
+ * The memory address of the object, as obtained from m3gMapObject,
+ * must not be used again until a new m3gMapObject call.
+ */
+/*@access M3GMemObject@*/
+static void m3gUnmapObject(Interface *m3g, M3GMemObject handle)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+
+# if defined(M3G_DEBUG)
+ if (handle != 0) {
+ void *ptr = (*m3g->func.objResolve)(handle);
+ validateBlock(PAYLOAD_BLOCK(ptr));
+ M3G_LOG1(M3G_LOG_MEMORY_MAPPING,
+ "UnmapObj 0x%08X\n", (unsigned) handle);
+ m3gUnlockMemory(m3g);
+ }
+# else
+ M3G_UNREF(m3g);
+ M3G_UNREF(handle);
+# endif
+}
+
+/*!
+ * \internal
+ * \brief Garbage collect until no more objects can be freed
+ *
+ * \note This is currently a no-op if reference counting has not been
+ * enabled at build time.
+ */
+static void m3gGarbageCollectAll(Interface *m3g)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_NO_LOCK(m3g);
+ M3G_ASSERT(!m3g->tempLocked);
+
+ /* Free the temporary buffer */
+
+ m3gFree(m3g, m3g->tempBuffer);
+ m3g->tempBuffer = NULL;
+ m3g->tempSize = 0;
+}
+
+#if defined(M3G_NGL_CONTEXT_API)
+/*!
+ * \internal
+ * \brief Gets the address of an external frame buffer and locks the
+ * buffer in place
+ *
+ * Used for memory rendering targets only; see m3gBindMemoryTarget.
+ */
+static void *m3gGetExternalFB(Interface *m3g, M3Guint userTarget)
+{
+ void *ptr = NULL;
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_ASSERT_NO_LOCK(m3g);
+
+ if (m3g->func.getFrameBuffer != NULL) {
+ ptr = (*m3g->func.getFrameBuffer)(userTarget);
+ }
+
+ m3gLockMemory(m3g); /* note that this must be done *after* calling
+ * the callback, or we'll get asserts if GC is
+ * triggered */
+ return ptr;
+}
+#endif /* defined(M3G_NGL_CONTEXT_API) */
+
+#if defined(M3G_NGL_CONTEXT_API)
+/*!
+ * \internal
+ * \brief Releases the external frame buffer locked with
+ * m3gGetExternalFB
+ */
+static void m3gReleaseExternalFB(Interface *m3g, M3Guint userTarget)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ m3gUnlockMemory(m3g);
+ if (m3g->func.releaseFrameBuffer != NULL) {
+ (*m3g->func.releaseFrameBuffer)(userTarget);
+ }
+}
+#endif /* defined(M3G_NGL_CONTEXT_API) */
+
+#if defined(M3G_NGL_CONTEXT_API)
+/*!
+ * \internal
+ * \brief Signals to a user callback that the bound rendering target
+ * has been released
+ *
+ * \note This is a callback because it is possible in Java for a
+ * rendering context to be destroyed without first releasing the
+ * target; in that case, this callback is called to ensure that all
+ * external resources are freed.
+ */
+static void m3gSignalTargetRelease(Interface *m3g, M3Guint userTarget)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ if (m3g->func.releaseTarget != NULL) {
+ (*m3g->func.releaseTarget)(userTarget);
+ }
+}
+#endif /* defined(M3G_NGL_CONTEXT_API) */
+
+/*!
+ * \internal
+ * \brief Raise an error status on this interface
+ *
+ * Any previous error code will be overwritten.
+ */
+#if defined(M3G_DEBUG)
+static void m3gDebugRaiseError(Interface *m3g,
+ M3Genum errorCode,
+ const char *filename,
+ int line)
+#else
+static void m3gRaiseError(Interface *m3g, M3Genum errorCode)
+#endif
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+
+ if (errorCode == M3G_OUT_OF_MEMORY) {
+ M3G_LOG(M3G_LOG_MEMORY_ALL|M3G_LOG_WARNINGS,
+ "Error: Out of memory!\n");
+ }
+
+# if defined(M3G_DEBUG)
+ M3G_LOG3(M3G_LOG_USER_ERRORS,
+ "Error %d at %s, line %d\n", (int) errorCode, filename, line);
+# else
+ M3G_LOG1(M3G_LOG_USER_ERRORS, "Error %d\n", (int) errorCode);
+# endif /* M3G_DEBUG */
+
+ m3g->error = errorCode;
+ if (m3g->func.error != NULL) {
+ (*m3g->func.error)(errorCode, (M3GInterface) m3g);
+ m3g->error = M3G_NO_ERROR;
+ }
+}
+
+#ifdef M3G_NATIVE_LOADER
+/*!
+ * \internal
+ * \brief Checks whether an error flag has been raised
+ */
+static M3GError m3gErrorRaised(const Interface *m3g)
+{
+ M3G_VALIDATE_INTERFACE(m3g);
+ return (M3GError)m3g->error;
+}
+
+/*!
+ * \internal
+ * \brief Sets a new error handler and returns the old one
+ */
+static m3gErrorHandler *m3gSetErrorHandler(Interface *m3g, m3gErrorHandler *errorHandler)
+{
+ m3gErrorHandler *current = m3g->func.error;
+ m3g->func.error = errorHandler;
+ return current;
+}
+#endif
+
+/*!
+ * \internal
+ * \brief Gets various constants from the GL driver
+ */
+
+#include <stdio.h>
+
+static void m3gConfigureGL(Interface *m3g)
+{
+# if defined(M3G_NGL_CONTEXT_API)
+ m3g->maxTextureDimension = M3G_MAX_TEXTURE_DIMENSION;
+ m3g->maxViewportWidth = M3G_MAX_VIEWPORT_WIDTH;
+ m3g->maxViewportHeight = M3G_MAX_VIEWPORT_HEIGHT;
+ m3g->maxViewportDim = M3G_MAX_VIEWPORT_DIMENSION;
+# else /* !M3G_NGL_CONTEXT_API */
+ const GLubyte *info;
+ int params[2];
+ int numConfigs;
+ EGLContext ctx;
+ EGLConfig config;
+ EGLSurface surf;
+ EGLint attrib[5];
+
+ m3gInitializeGL(m3g);
+
+ attrib[0] = EGL_SURFACE_TYPE;
+ attrib[1] = EGL_PBUFFER_BIT;
+ attrib[2] = EGL_NONE;
+
+ eglChooseConfig(eglGetDisplay(0),
+ attrib,
+ &config, 1,
+ &numConfigs);
+
+ M3G_ASSERT(numConfigs > 0);
+
+ ctx = eglCreateContext(eglGetDisplay(0),
+ config,
+ NULL,
+ NULL);
+
+ attrib[0] = EGL_WIDTH;
+ attrib[1] = 2;
+ attrib[2] = EGL_HEIGHT;
+ attrib[3] = 2;
+ attrib[4] = EGL_NONE;
+
+ surf = eglCreatePbufferSurface(eglGetDisplay(0),
+ config,
+ attrib);
+
+ eglMakeCurrent(eglGetDisplay(0),
+ surf, surf, ctx);
+
+
+ /* Check antialiasing support and workarounds
+ from the renderer string.
+ HW platforms like MBX has AA, Gerbera and NGL does not.
+ MBX needs workarounds for color mask and two sided lighting.
+ */
+
+ info = glGetString(GL_RENDERER);
+
+ if (strstr((const char *)info, "HW")) {
+ m3g->supportAntialiasing = M3G_TRUE;
+ }
+ else {
+ m3g->supportAntialiasing = M3G_FALSE;
+ }
+
+ if (strstr((const char *)info, "MBX")) {
+ m3g->colorMaskWorkaround = M3G_TRUE;
+ m3g->twoSidedLightingWorkaround = M3G_TRUE;
+ }
+ else {
+ m3g->colorMaskWorkaround = M3G_FALSE;
+ m3g->twoSidedLightingWorkaround = M3G_FALSE;
+ }
+
+ /* For testing purposes only */
+# if defined(M3G_FORCE_MBX_WORKAROUNDS)
+ m3g->colorMaskWorkaround = M3G_TRUE;
+ m3g->twoSidedLightingWorkaround = M3G_TRUE;
+# endif
+
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, params);
+
+ m3g->maxTextureDimension = params[0];
+
+ glGetIntegerv(GL_MAX_VIEWPORT_DIMS, params);
+
+ m3g->maxViewportWidth = params[0];
+ m3g->maxViewportHeight = params[1];
+ m3g->maxViewportDim = M3G_MIN(params[0], params[1]);
+
+ eglMakeCurrent(eglGetDisplay(0), NULL, NULL, NULL);
+ eglDestroySurface(eglGetDisplay(0), surf);
+ eglDestroyContext(eglGetDisplay(0), ctx);
+
+ m3gShutdownGL(m3g);
+
+#endif /* M3G_NGL_CONTEXT_API */
+}
+
+
+/*!
+ * \internal
+ * \brief Initializes the GL subsystem
+ */
+static void m3gInitializeGL(Interface *m3g)
+{
+# if defined(M3G_NGL_CONTEXT_API)
+# define glRefCount m3gs_glRefCount
+# else
+# define glRefCount m3g->glRefCount
+# endif
+
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_UNREF(m3g);
+
+ if (++glRefCount == 1) {
+ M3G_LOG(M3G_LOG_INTERFACE, "Initializing GL\n");
+
+# if defined(M3G_NGL_CONTEXT_API)
+
+ m3gs_nglMem = M3G_SYSTEM_ALLOC(NGL_MIN_WORKING_MEMORY_BYTES);
+ m3gs_nglTexMgr = nglCreateTextureManager();
+ if (!nglInit(m3gs_nglMem, NGL_MIN_WORKING_MEMORY_BYTES,
+ m3gs_nglTexMgr,
+ 0)) {
+ M3G_ASSERT(M3G_FALSE);
+ }
+ M3G_ASSERT_GL;
+
+# else /* !M3G_NGL_CONTEXT_API */
+
+ m3gInitializeEGL();
+
+# endif
+
+# if defined(M3G_NGL_TEXTURE_API)
+ {
+ int i;
+ GLuint texture;
+ for (i = 0; i < M3G_NUM_TEXTURE_UNITS; ++i) {
+ m3gs_nglTexUnit[i] = M3G_SYSTEM_ALLOC(NGL_TEXTURE_STRUCT_SIZE);
+ M3G_ASSERT(m3gs_nglTexUnit[i]);
+ texture = (GLuint) m3gs_nglTexUnit[i];
+ nglInitTextures(1, &texture);
+ glActiveTexture(GL_TEXTURE0 + i);
+ nglBindTextureInternal(GL_TEXTURE_2D, texture);
+ }
+ glActiveTexture(GL_TEXTURE0);
+ }
+# endif
+ }
+
+ M3G_ASSERT(glRefCount > 0);
+
+# undef glRefCount
+}
+
+/*!
+ * \internal
+ * \brief Shuts down the GL subsystem
+ */
+static void m3gShutdownGL(Interface *m3g)
+{
+# if defined(M3G_NGL_CONTEXT_API)
+# define glRefCount m3gs_glRefCount
+# else
+# define glRefCount m3g->glRefCount
+# endif
+
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_UNREF(m3g);
+ M3G_ASSERT(glRefCount > 0);
+ M3G_LOG(M3G_LOG_INTERFACE, "Shutting down GL...\n");
+
+ if (--glRefCount == 0) {
+# if defined(M3G_NGL_CONTEXT_API)
+
+ nglExit();
+ nglDeleteTextureManager(m3gs_nglTexMgr);
+ M3G_SYSTEM_DEALLOC(m3gs_nglMem);
+ m3gs_nglTexMgr = NULL;
+ m3gs_nglMem = NULL;
+
+# else /* !M3G_NGL_CONTEXT_API */
+
+ m3gTerminateEGL();
+
+# endif
+
+# if defined(M3G_NGL_TEXTURE_API)
+ {
+ int i;
+ for (i = 0; i < M3G_NUM_TEXTURE_UNITS; ++i) {
+ M3G_SYSTEM_DEALLOC(m3gs_nglTexUnit[i]);
+ m3gs_nglTexUnit[i] = NULL;
+ }
+ }
+# endif
+ }
+ else {
+ M3G_LOG1(M3G_LOG_INTERFACE, "Waiting for %d GL objects\n",
+ glRefCount);
+ }
+# undef glRefCount
+}
+
+/*!
+ * \internal
+ * \brief Make any run-time portability checks
+ */
+static M3Gbool m3gSystemCheck(void)
+{
+ /* Check that right shifts on signed values work as expected
+ * (extending the top bit to keep the sign) */
+ {
+ int magic = -0x7F7E80B1;
+ if ((magic >> 4) != -0x07F7E80C) {
+ return M3G_FALSE;
+ }
+ }
+
+ /* Check endianess if dependent code introduced */
+
+ return M3G_TRUE;
+}
+
+static M3Gbool m3gGetColorMaskWorkaround(Interface *m3g)
+{
+ return m3g->colorMaskWorkaround;
+}
+
+static M3Gbool m3gGetTwoSidedLightingWorkaround(Interface *m3g)
+{
+ return m3g->twoSidedLightingWorkaround;
+}
+
+/*!
+ * \internal
+ * \brief Increment a statistics counter
+ */
+#if defined(M3G_ENABLE_PROFILING)
+static void m3gIncStat(Interface *m3g, M3Gstatistic stat, M3Gint increment)
+{
+ m3g->statistics[stat] += increment;
+}
+#endif
+
+/*!
+ * \internal
+ * \brief Increment a statistics counter
+ */
+#if defined(M3G_ENABLE_PROFILING)
+static void m3gResetStat(Interface *m3g, M3Gstatistic stat)
+{
+ m3g->statistics[stat] = 0;
+}
+#endif
+
+/*!
+ * \internal
+ * \brief Output memory peak counters to the log
+ */
+#if defined(M3G_ENABLE_PROFILING)
+static void m3gLogMemoryPeakCounter(const Interface *m3g)
+{
+ M3G_LOG3(M3G_LOG_MEMORY_USAGE,
+ "Memory peaks: %d KB heap, %d KB obj, %d KB total\n",
+ m3g->statistics[M3G_STAT_MEMORY_MALLOC_PEAK] >> 10,
+ m3g->statistics[M3G_STAT_MEMORY_OBJECT_PEAK] >> 10,
+ m3g->statistics[M3G_STAT_MEMORY_PEAK] >> 10);
+}
+#endif
+
+/*!
+ * \internal
+ * \brief Update the peak memory counter
+ *
+ * This function should be called after each memory allocation
+ */
+#if defined(M3G_ENABLE_PROFILING)
+static void m3gUpdateMemoryPeakCounter(Interface *m3g)
+{
+ if (m3g->statistics[M3G_STAT_MEMORY_ALLOCATED] > m3g->statistics[M3G_STAT_MEMORY_PEAK]) {
+ m3g->statistics[M3G_STAT_MEMORY_PEAK] = m3g->statistics[M3G_STAT_MEMORY_ALLOCATED];
+ }
+ if (m3g->statistics[M3G_STAT_MEMORY_MALLOC_BYTES] > m3g->statistics[M3G_STAT_MEMORY_MALLOC_PEAK]) {
+ m3g->statistics[M3G_STAT_MEMORY_MALLOC_PEAK] = m3g->statistics[M3G_STAT_MEMORY_MALLOC_BYTES];
+ }
+ if (m3g->statistics[M3G_STAT_MEMORY_OBJECT_BYTES] > m3g->statistics[M3G_STAT_MEMORY_OBJECT_PEAK]) {
+ m3g->statistics[M3G_STAT_MEMORY_OBJECT_PEAK] = m3g->statistics[M3G_STAT_MEMORY_OBJECT_BYTES];
+ }
+
+ /* Output peaks in 100 KB increments to reduce amont of log clutter */
+
+ if (m3g->statistics[M3G_STAT_MEMORY_PEAK] - m3g->lastPeak > (100 << 10)) {
+ m3gLogMemoryPeakCounter(m3g);
+ m3g->lastPeak = m3g->statistics[M3G_STAT_MEMORY_PEAK];
+ }
+}
+#endif /* M3G_ENABLE_PROFILING */
+
+#if defined(M3G_ENABLE_PROFILING) && defined(M3G_TARGET_SYMBIAN)
+extern M3Gbool m3gProfileTriggered(void);
+#endif
+
+/*!
+ * \internal
+ * \brief Output profiling counters to the log
+ */
+#if defined(M3G_ENABLE_PROFILING) && (M3G_PROFILE_LOG_INTERVAL > 0)
+static void m3gLogProfileCounters(Interface *m3g)
+{
+ M3Gint profTime;
+# if defined(M3G_TARGET_SYMBIAN)
+ profTime = m3gProfileTriggered();
+ if (profTime > 0) {
+# else
+ profTime = M3G_PROFILE_LOG_INTERVAL;
+ if (++m3g->profileInterval >= M3G_PROFILE_LOG_INTERVAL) {
+# endif
+ M3Gint v, i;
+
+ M3G_LOG1(M3G_LOG_PROFILE, "Profile %d:", profTime);
+
+ for (i = 0; i < M3G_STAT_MAX; ++i) {
+ v = m3gGetStatistic(m3g, i);
+ M3G_LOG1(M3G_LOG_PROFILE, " %d", v);
+ }
+ M3G_LOG(M3G_LOG_PROFILE, "\n");
+ m3g->profileInterval = 0;
+ }
+}
+#endif /* M3G_ENABLE_PROFILING && M3G_PROFILE_LOG_INTERVAL > 0 */
+
+/*----------------------------------------------------------------------
+ * Public API implementation
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \brief Creates a new M3G interface
+ *
+ */
+M3G_API M3GInterface m3gCreateInterface(
+ /*@in@*//*@temp@*/ const M3Gparams *params)
+{
+ /* This is likely to get executed exactly once during the
+ * execution of an application, so before doing anything else,
+ * execute the run-time sanity checks */
+
+ if (!m3gSystemCheck()) {
+ M3G_ASSERT(M3G_FALSE);
+ return NULL;
+ }
+
+ /* Allow for deletion of existing log files */
+
+# if defined(M3G_LOGLEVEL)
+ m3gBeginLog();
+# endif
+
+ /* To actually create the interface, first check the supplied
+ * function pointers */
+
+ if (params == NULL
+ || params->mallocFunc == NULL
+ || params->freeFunc == NULL) {
+ return NULL;
+ }
+ if (params->objAllocFunc != NULL
+ && (params->objResolveFunc == NULL
+ || params->objFreeFunc == NULL)) {
+ return NULL;
+ }
+
+ /* Allocate the interface using the provided malloc function and
+ * initialize. Note that this is slightly different from all other
+ * constructors due to the memory instrumentation being done
+ * directly rather than via m3gAlloc which isn't usable yet. */
+ {
+ Interface *m3g = (*params->mallocFunc)(INSTRUMENTATED_SIZE(sizeof(Interface)));
+ if (m3g == NULL) {
+ M3G_LOG(M3G_LOG_FATAL_ERRORS, "Interface creation failed\n");
+ return NULL;
+ }
+ M3G_LOG1(M3G_LOG_INTERFACE, "New interface 0x%08X\n", (unsigned) m3g);
+
+ m3g = (Interface *) PAYLOAD_BLOCK(m3g);
+# if defined(M3G_DEBUG_HEAP_TRACKING)
+ instrumentateBlock(m3g, sizeof(*m3g), __FILE__, __LINE__);
+# else
+ instrumentateBlock(m3g, sizeof(*m3g));
+# endif
+ m3gZero(m3g, sizeof(*m3g));
+
+ m3g->func.malloc = params->mallocFunc;
+ m3g->func.free = params->freeFunc;
+
+ if (params->objAllocFunc) {
+ m3g->func.objAlloc = params->objAllocFunc;
+ m3g->func.objFree = params->objFreeFunc;
+ m3g->func.objResolve = params->objResolveFunc;
+ }
+ else {
+ m3g->func.objAlloc = (m3gObjectAllocator*)(m3g->func.malloc);
+ m3g->func.objFree = (m3gObjectDeallocator*)(m3g->func.free);
+ m3g->func.objResolve = defaultResolver;
+ }
+
+ m3g->func.error = params->errorFunc;
+ m3g->func.getFrameBuffer = params->beginRenderFunc;
+ m3g->func.releaseFrameBuffer = params->endRenderFunc;
+
+ m3g->userContext = params->userContext;
+
+ /* Initialize memory allocation failure debugging */
+# if defined(M3G_DEBUG_OUT_OF_MEMORY)
+ {
+ const char *str;
+
+# define M3G_GETENV(name, field) \
+ str = getenv(name); \
+ if (str) { \
+ m3g-> ## field = atoi(str); \
+ }
+
+ M3G_GETENV("M3G_DEBUG_MALLOC_LIMIT", mallocLimit);
+ M3G_GETENV("M3G_DEBUG_OBJALLOC_LIMIT", objAllocLimit);
+ M3G_GETENV("M3G_DEBUG_MALLOC_FAILRATE", mallocFailRate);
+ M3G_GETENV("M3G_DEBUG_OBJALLOC_FAILRATE", objAllocFailRate);
+
+# undef M3G_GETENV
+ }
+# endif
+
+# if !defined(M3G_NGL_CONTEXT_API)
+ /* Before messing with EGL state, check if EGL is already
+ * initialized by the calling application. */
+
+ if (eglQueryString(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_VERSION)) {
+ ++m3g->glRefCount;
+ }
+# endif /*!M3G_NGL_CONTEXT_API*/
+
+ /* Dig some constants from the GL implementation */
+
+ m3gConfigureGL(m3g);
+
+ /* All done! Now we can allocate the more trival stuff */
+
+ m3g->tcache = m3gCreateTransformCache(m3g);
+
+ M3G_LOG1(M3G_LOG_INTERFACE,
+ "Interface 0x%08X initialized\n", (unsigned) m3g);
+ return (M3GInterface) m3g;
+ }
+}
+
+/*!
+ * \brief Deletes an M3G interface and all associated objects
+ */
+M3G_API void m3gDeleteInterface(M3GInterface interface)
+{
+ Interface *m3g = (Interface *)interface;
+ M3G_VALIDATE_INTERFACE(m3g);
+ M3G_LOG1(M3G_LOG_INTERFACE,
+ "Shutting down interface 0x%08X...\n", (unsigned) m3g);
+
+ /* Check if we still have objects lingering (this may happen when
+ * Java GC deletes the interface first, for instance), and just
+ * mark the interface for deletion in that case */
+
+ if (m3g->objCount > 0) {
+ M3G_ASSERT(!interface->shutdown);
+ M3G_LOG1(M3G_LOG_INTERFACE, "Waiting for %d objects\n",
+ interface->objCount);
+ interface->shutdown = M3G_TRUE;
+ return;
+ }
+
+# if !defined(M3G_NGL_TEXTURE_API)
+ /* Free the list of dead GL objects (those will have been deleted
+ * along with the owning contexts by now) */
+
+ m3gDestroyArray(&m3g->deadGLObjects, m3g);
+# endif
+
+ /* Delete temp buffers and caches */
+
+ M3G_ASSERT(!m3g->tempLocked);
+ m3gFree(m3g, m3g->tempBuffer);
+
+ m3gDeleteTransformCache(m3g->tcache);
+
+ /* Check for any leaked memory */
+# if defined(M3G_DEBUG)
+ if (m3g->mallocCount != 0 || m3g->objAllocCount != 0) {
+ M3G_LOG(M3G_LOG_FATAL_ERRORS, "Memory leak detected!\n");
+ if (m3g->mallocCount != 0) {
+ M3G_LOG1(M3G_LOG_FATAL_ERRORS,
+ "\t%d memory blocks\n", m3g->mallocCount);
+# if defined(M3G_DEBUG_HEAP_TRACKING) && defined(M3G_LOGLEVEL)
+ M3G_LOG(M3G_LOG_FATAL_ERRORS, "Dumping blocks...\n");
+ dumpBlocks(m3g->blockList);
+# endif
+ }
+ if (m3g->objAllocCount != 0) {
+ M3G_LOG1(M3G_LOG_FATAL_ERRORS,
+ "\t%d memory objects\n", m3g->objAllocCount);
+ }
+ }
+# endif /* M3G_DEBUG */
+
+ /* Cleanup profiling resources */
+ m3gCleanupProfile();
+ m3gLogMemoryPeakCounter(m3g);
+
+ /* Delete self */
+ {
+ m3gFreeFunc *freeFunc = m3g->func.free;
+ destroyBlock(m3g);
+ (*freeFunc)(PHYSICAL_BLOCK(m3g));
+ }
+
+ M3G_LOG1(M3G_LOG_INTERFACE,
+ "Interface 0x%08X destroyed\n", (unsigned) m3g);
+
+ /* Allow for log cleanup */
+
+# if defined(M3G_LOGLEVEL)
+ m3gEndLog();
+# endif
+}
+
+/*!
+ * \brief Returns the latest error that occurred on an M3G interface
+ *
+ * Returns the latest error for \c interface, and resets the error
+ * status to M3G_NO_ERROR.
+ *
+ * @param interface handle of the interface to query for errors
+ */
+M3G_API M3Genum m3gGetError(M3GInterface interface)
+{
+ Interface *m3g = (Interface *)interface;
+ M3G_VALIDATE_INTERFACE(m3g);
+ {
+ M3Genum error = m3g->error;
+ m3g->error = M3G_NO_ERROR;
+ return error;
+ }
+}
+
+/*!
+ * \brief Returns the user context data pointer associated with an M3G interface
+ *
+ * User context data can be associated with an interface via the
+ * M3GParams struct in m3gCreateInterface.
+ *
+ * @param interface handle of the interface
+ * @return pointer to the user context
+ */
+M3G_API void *m3gGetUserContext(M3GInterface interface)
+{
+ Interface *m3g = (Interface *)interface;
+ M3G_VALIDATE_INTERFACE(m3g);
+ return m3g->userContext;
+}
+
+/*!
+ * \brief Returns if antialiasing is supported
+ *
+ * User context data can be associated with an interface via the
+ * M3GParams struct in m3gCreateInterface.
+ *
+ * @param interface handle of the interface
+ * @return pointer to the user context
+ */
+M3G_API M3Gbool m3gIsAntialiasingSupported(M3GInterface interface)
+{
+ Interface *m3g = (Interface *)interface;
+ M3G_VALIDATE_INTERFACE(m3g);
+ return m3g->supportAntialiasing;
+}
+
+/*!
+ * \brief Free memory by removing all detached objects
+ *
+ * Garbage collection will run automatically during normal operation,
+ * so there is no need to call this function. However, it allows the
+ * application to signal a suitable time to invest more time in
+ * garbage collection, potentially improving performance later on.
+ */
+M3G_API void m3gGarbageCollect(M3GInterface interface)
+{
+ Interface *m3g = (Interface *) interface;
+ M3G_VALIDATE_INTERFACE(m3g);
+ m3gGarbageCollectAll(m3g);
+}
+
+/*!
+ * \brief Returns the transformation cache for this interface
+ */
+static TCache *m3gGetTransformCache(Interface *m3g)
+{
+ return m3g->tcache;
+}
+
+/*!
+ * \brief Returns the value of a given statistic
+ *
+ * If the statistic is a counter (such as number of rendering calls),
+ * its value is also cleared.
+ *
+ * \note Dependent on the M3G_ENABLE_PROFILING compile-time flag; if
+ * undefined, all statistic queries return zero
+ */
+#if defined(M3G_ENABLE_PROFILING)
+/*@access M3GInterface@*/
+M3G_API M3Gint m3gGetStatistic(M3GInterface hInterface, M3Gstatistic stat)
+{
+ Interface *m3g = (Interface*) hInterface;
+ M3G_VALIDATE_INTERFACE(m3g);
+
+ if (m3gInRange(stat, 0, M3G_STAT_MAX-1)) {
+ M3Gint value = m3g->statistics[stat];
+
+ if (stat < M3G_STAT_CUMULATIVE) {
+ m3g->statistics[stat] = 0;
+ }
+
+ return value;
+ }
+ else {
+ m3gRaiseError(m3g, M3G_INVALID_ENUM);
+ return -1;
+ }
+}
+#else
+M3G_API M3Gint m3gGetStatistic(M3GInterface hInterface, M3Gstatistic stat)
+{
+ M3G_UNREF(hInterface);
+ M3G_UNREF(stat);
+ return 0;
+}
+#endif /*M3G_ENABLE_PROFILING*/
+
+
+#undef INSTRUMENTATED_SIZE
+#undef PAYLOAD_BLOCK
+#undef PHYSICAL_BLOCK
+