diff -r 000000000000 -r 5d03bc08d59c m3g/m3gcore11/src/m3g_interface.c --- /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 /* 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 + +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 +