/* $Id$ */
#include <float.h>
#include "cr_dlm.h"
#include "cr_mem.h"
#include "dlm.h"
/**
* \mainpage Dlm
*
* \section DlmIntroduction Introduction
*
* Chromium consists of all the top-level files in the cr
* directory. The dlm module basically takes care of API dispatch,
* and OpenGL state management.
*
*/
/**
* Module globals: the current DLM state, bound either to each thread, or
* to a global.
*/
#ifdef CHROMIUM_THREADSAFE
CRtsd CRDLMTSDKey;
#else
CRDLMContextState *CRDLMCurrentState = NULL;
#endif
#define MIN(a,b) ((a)<(b)?(a):(b))
/*************************************************************************/
#ifdef CHROMIUM_THREADSAFE
/**
* This is the thread-specific destructor function for the
* data used in the DLM. It's very simple: if a thread exits
* that has DLM-specific data, the data represents the listState
* for the thread. All data and buffers associated with the list
* can be deleted, and the structure itself can be freed.
*
* Most Chromium threads don't have such things; but then,
* if a thread dies elsewhere in Chromium, huge buffers
* of information won't still be floating around in
* unrecoverable allocated areas, either.
*/
static void threadDestructor(void *tsd)
{
CRDLMContextState *listState = (CRDLMContextState *)tsd;
if (listState) {
if (listState->currentListInfo) {
crdlm_free_list(listState->currentListInfo);
}
crFree(listState);
}
}
#endif
/**
* This function creates and initializes a new display list
* manager. It returns a pointer to the manager, or NULL in
* the case of insufficient memory. The dispatch table pointer
* is passed in to allow the utilities to muck with the table
* to gain functional control when GL calls are made.
*/
CRDLM DLM_APIENTRY *crDLMNewDLM(unsigned int userConfigSize, const CRDLMConfig *userConfig)
{
CRDLM *dlm;
/* This is the default configuration. We'll overwrite it later
* with user-supplied configuration information.
*/
CRDLMConfig config = {
CRDLM_DEFAULT_BUFFERSIZE,
};
dlm = crAlloc(sizeof(*dlm));
if (!dlm) {
return NULL;
}
/* Start off by initializing all entries that require further
* memory allocation, so we can free up all the memory if there's
* a problem.
*/
if (!(dlm->displayLists = crAllocHashtable())) {
crFree(dlm);
return NULL;
}
/* The creator counts as the first user. */
dlm->userCount = 1;
#ifdef CHROMIUM_THREADSAFE
/* This mutex ensures that only one thread is changing the displayLists
* hash at a time. Note that we may also need a mutex to guarantee that
* the hash is not changed by one thread while another thread is
* traversing it; this issue has not yet been resolved.
*/
crInitMutex(&(dlm->dlMutex));
/* Although the thread-specific data (TSD) functions will initialize
* the thread key themselves when needed, those functions do not allow
* us to specify a thread destructor. Since a thread could potentially
* exit with considerable memory allocated (e.g. if a thread exits
* after it has issued NewList but before EndList, and while there
* are considerable content buffers allocated), I do the initialization
* myself, in order to be able to reclaim those resources if a thread
* exits.
*/
crInitTSDF(&(dlm->tsdKey), threadDestructor);
crInitTSD(&CRDLMTSDKey);
#endif
/* Copy over any appropriate configuration values */
if (userConfig != NULL) {
/* Copy over as much configuration information as is provided.
* Note that if the CRDLMConfig structure strictly grows, this
* allows forward compatability - routines compiled with
* older versions of the structure will only initialize that
* section of the structure that they know about.
*/
crMemcpy((void *)&config, (void *) userConfig,
MIN(userConfigSize, sizeof(config)));
}
dlm->bufferSize = config.bufferSize;
/* Return the pointer to the newly-allocated display list manager */
return dlm;
}
void DLM_APIENTRY crDLMUseDLM(CRDLM *dlm)
{
DLM_LOCK(dlm);
dlm->userCount++;
DLM_UNLOCK(dlm);
}
/**
* This routine is called when a context or thread is done with a DLM.
* It maintains an internal count of users, and will only actually destroy
* itself when no one is still using the DLM.
*/
void DLM_APIENTRY crDLMFreeDLM(CRDLM *dlm)
{
/* We're about to change the displayLists hash; lock it first */
DLM_LOCK(dlm)
/* Decrement the user count. If the user count has gone to
* 0, then free the rest of the DLM. Otherwise, other
* contexts or threads are still using this DLM; keep
* it around.
*/
dlm->userCount--;
if (dlm->userCount == 0) {
/* Free the set of display lists. As each one is freed, the
* crdlm_free_list function will be called to free up its
* internal resources. The crdlm_free_list() routine is
* cast to a (void *) to avoid warnings about being an
*/
crFreeHashtable(dlm->displayLists, crdlm_free_list);
dlm->displayLists = NULL;
/* Must unlock before freeing the mutex */
DLM_UNLOCK(dlm)
#ifdef CHROMIUM_THREADSAFE
/* We release the mutex here; we really should delete the
* thread data key, but there's no utility in Chromium to
* do this.
*
* Note that, should one thread release the entire DLM
* while other threads still believe they are using it,
* any other threads that have current display lists (i.e.
* have issued glNewList more recently than glEndList)
* will be unable to reclaim their (likely very large)
* content buffers, as there will be no way to reclaim
* the thread-specific data.
*
* On the other hand, if one thread really does release
* the DLM while other threads still believe they are
* using it, unreclaimed memory is the least of the
* application's problems...
*/
crFreeMutex(&(dlm->dlMutex));
/* We free the TSD key here as well. Note that this will
* strand any threads that still have thread-specific data
* tied to this key; but as stated above, if any threads
* still do have thread-specific data attached to this DLM,
* they're in big trouble anyway.
*/
crFreeTSD(&(dlm->tsdKey));
crFreeTSD(&CRDLMTSDKey);
#endif
/* Free the master record, and we're all done. */
crFree(dlm);
}
else {
/* We're keeping the DLM around for other users. Unlock it,
* but retain its memory and display lists.
*/
DLM_UNLOCK(dlm)
}
}
/**
* The actual run-time state of a DLM is bound to a context
* (because each context can be used by at most one thread at
* a time, and a thread can only use one context at a time,
* while multiple contexts can use the same DLM).
* This creates the structure required to hold the state, and
* returns it to the caller, who should store it with any other
* context-specific information.
*/
CRDLMContextState DLM_APIENTRY *crDLMNewContext(CRDLM *dlm)
{
CRDLMContextState *state;
/* Get a record for our own internal state structure */
state = (CRDLMContextState *)crAlloc(sizeof(CRDLMContextState));
if (!state) {
return NULL;
}
state->dlm = dlm;
state->currentListIdentifier = 0;
state->currentListInfo = NULL;
state->currentListMode = GL_FALSE;
state->listBase = 0;
state->replayState = CRDLM_IMMEDIATE;
/* Increment the use count of the DLM provided. This guarantees that
* the DLM won't be released until all the contexts have released it.
*/
crDLMUseDLM(dlm);
return state;
}
/**
* This routine should be called when a MakeCurrent changes the current
* context. It sets the thread data (or global data, in an unthreaded
* environment) appropriately; this in turn changes the behavior of
* the installed DLM API functions.
*/
void DLM_APIENTRY crDLMSetCurrentState(CRDLMContextState *state)
{
CRDLMContextState *currentState = CURRENT_STATE();
if (currentState != state) {
SET_CURRENT_STATE(state);
}
}
CRDLMContextState DLM_APIENTRY *crDLMGetCurrentState(void)
{
return CURRENT_STATE();
}
/**
* This routine, of course, is used to release a DLM context when it
* is no longer going to be used.
*/
void DLM_APIENTRY crDLMFreeContext(CRDLMContextState *state)
{
CRDLMContextState *listState = CURRENT_STATE();
/* If we're currently using this context, release it first */
if (listState == state) {
crDLMSetCurrentState(NULL);
}
/* Try to free the DLM. This will either decrement the use count,
* or will actually free the DLM, if we were the last user.
*/
crDLMFreeDLM(state->dlm);
state->dlm = NULL;
/* If any buffers still remain (e.g. because there was an open
* display list), remove those as well.
*/
if (state->currentListInfo) {
crdlm_free_list((void *)state->currentListInfo);
}
state->currentListInfo = NULL;
state->currentListIdentifier = 0;
/* Free the state record itself */
crFree(state);
}
/**
* This function can be used if the caller wishes to free up the
* potentially considerable resources used to store the display list
* content, without losing the rest of the display list management.
* For one example, consider an SPU that conditionally sends its
* input stream to multiple servers. It could broadcast all display
* lists to all servers, or it could only send display lists to servers
* that need them. After all servers have the display list, the SPU
* may wish to release the resources used to manage the content.
*/
CRDLMError DLM_APIENTRY crDLMDeleteListContent(CRDLM *dlm, unsigned long listIdentifier)
{
DLMListInfo *listInfo;
DLMInstanceList *instance;
listInfo = (DLMListInfo *) crHashtableSearch(dlm->displayLists, listIdentifier);
if (listInfo && (instance = listInfo->first)) {
while (instance) {
DLMInstanceList *nextInstance;
nextInstance = instance->next;
crFree(instance);
instance = nextInstance;
}
listInfo->first = listInfo->last = NULL;
}
return GL_NO_ERROR;
}
/* Return whether the current thread is involved in playback.
* This is useful for some routines to selectively choose their
* unpack state, for example (as replayed DLM functions must be
* unpacked with crStateNativePixelPacking instead of the
* normal unpack state, for example.
*/
CRDLMReplayState DLM_APIENTRY crDLMGetReplayState(void)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
return listState->replayState;
}
else {
return CRDLM_IMMEDIATE;
}
}
/**
*
* Playback/execute a list.
* dlm - the display list manager context
* listIdentifier - the display list ID (as specified by app) to playback
* dispatchTable - the GL dispatch table to jump through as we execute commands
*/
void DLM_APIENTRY crDLMReplayDLMList(CRDLM *dlm, unsigned long listIdentifier, SPUDispatchTable *dispatchTable)
{
DLMListInfo *listInfo;
listInfo = (DLMListInfo *)crHashtableSearch(dlm->displayLists, listIdentifier);
if (listInfo) {
DLMInstanceList *instance = listInfo->first;
while (instance) {
/* mutex, to make sure another thread doesn't change the list? */
/* For now, leave it alone. */
(*instance->execute)(instance, dispatchTable);
instance = instance->next;
}
}
}
/* Playback/execute a list in the current DLM */
void DLM_APIENTRY crDLMReplayList(unsigned long listIdentifier, SPUDispatchTable *dispatchTable)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
CRDLMReplayState oldReplayState = listState->replayState;
listState->replayState = CRDLM_REPLAY_ALL_FUNCTIONS;
crDLMReplayDLMList(listState->dlm, listIdentifier, dispatchTable);
listState->replayState = oldReplayState;
}
}
/*
* Playback/execute the state changing portions of a list.
* dlm - the display list manager context
* listIdentifier - the display list ID (as specified by app) to playback
* dispatchTable - the GL dispatch table to jump through as we execute commands
*/
void DLM_APIENTRY crDLMReplayDLMListState(CRDLM *dlm, unsigned long listIdentifier, SPUDispatchTable *dispatchTable)
{
DLMListInfo *listInfo;
listInfo = (DLMListInfo *)crHashtableSearch(dlm->displayLists, listIdentifier);
if (listInfo) {
DLMInstanceList *instance = listInfo->stateFirst;
while (instance) {
/* mutex, to make sure another thread doesn't change the list? */
/* For now, leave it alone. */
(*instance->execute)(instance, dispatchTable);
instance = instance->stateNext;
}
}
}
void DLM_APIENTRY crDLMReplayListState(unsigned long listIdentifier, SPUDispatchTable *dispatchTable)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
CRDLMReplayState oldReplayState = listState->replayState;
listState->replayState = CRDLM_REPLAY_STATE_FUNCTIONS;
crDLMReplayDLMListState(listState->dlm, listIdentifier, dispatchTable);
listState->replayState = oldReplayState;
}
}
/* This is a switch statement that lists every "type" value valid for a
* glCallLists() function call, with code for decoding the subsequent
* values correctly. It uses the current value of the EXPAND() macro,
* which must expand into an appropriate action to be taken.
* Its codification here allows for multiple uses.
*/
#define CALL_LISTS_SWITCH(type, defaultAction) \
switch (type) {\
EXPAND(GL_BYTE, GLbyte *, *p, p++)\
EXPAND(GL_UNSIGNED_BYTE, GLubyte *, *p, p++)\
EXPAND(GL_SHORT, GLshort *, *p, p++)\
EXPAND(GL_UNSIGNED_SHORT, GLushort *, *p, p++)\
EXPAND(GL_INT, GLint *, *p, p++)\
EXPAND(GL_FLOAT, GLfloat *, *p, p++)\
EXPAND(GL_2_BYTES, unsigned char *, 256*p[0] + p[1], p += 2)\
EXPAND(GL_3_BYTES, unsigned char *, 65536*p[0] + 256*p[1] + p[2], p += 3)\
EXPAND(GL_4_BYTES, unsigned char *, 16777216*p[0] + 65536*p[1] + 256*p[2] + p[3], p += 4)\
default:\
defaultAction;\
}
void DLM_APIENTRY crDLMReplayDLMLists(CRDLM *dlm, GLsizei n, GLenum type, const GLvoid * lists, SPUDispatchTable *dispatchTable)
{
unsigned long listId;
CRDLMContextState *listState = CURRENT_STATE();
#define EXPAND(TYPENAME, TYPE, REFERENCE, INCREMENT) \
case TYPENAME: {\
TYPE p = (TYPE)lists;\
while (n--) {\
listId = listState->listBase + (unsigned long) (REFERENCE);\
crDLMReplayDLMList(dlm, listId, dispatchTable);\
INCREMENT;\
}\
break;\
}
CALL_LISTS_SWITCH(type, break)
#undef EXPAND
}
void DLM_APIENTRY crDLMReplayLists(GLsizei n, GLenum type, const GLvoid * lists, SPUDispatchTable *dispatchTable)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
crDLMReplayDLMLists(listState->dlm, n, type, lists, dispatchTable);
}
}
void DLM_APIENTRY crDLMReplayDLMListsState(CRDLM *dlm, GLsizei n, GLenum type, const GLvoid * lists, SPUDispatchTable *dispatchTable)
{
unsigned long listId;
CRDLMContextState *listState = CURRENT_STATE();
#define EXPAND(TYPENAME, TYPE, REFERENCE, INCREMENT) \
case TYPENAME: {\
TYPE p = (TYPE)lists;\
while (n--) {\
listId = listState->listBase + (unsigned long) (REFERENCE);\
crDLMReplayDLMListState(dlm, listId, dispatchTable);\
INCREMENT;\
}\
break;\
}
CALL_LISTS_SWITCH(type, break)
#undef EXPAND
}
void DLM_APIENTRY crDLMReplayListsState(GLsizei n, GLenum type, const GLvoid * lists, SPUDispatchTable *dispatchTable)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
crDLMReplayDLMListsState(listState->dlm, n, type, lists, dispatchTable);
}
}
/* When we compiled the display list, we packed all pixel data
* tightly. When we execute the display list, we have to make
* sure that the client state reflects that the pixel data is
* tightly packed, or it will be interpreted incorrectly.
*/
void DLM_APIENTRY crDLMSetupClientState(SPUDispatchTable *dispatchTable)
{
dispatchTable->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
dispatchTable->PixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
dispatchTable->PixelStorei(GL_UNPACK_SKIP_ROWS, 0);
dispatchTable->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
}
void DLM_APIENTRY crDLMRestoreClientState(CRClientState *clientState, SPUDispatchTable *dispatchTable)
{
if (clientState) {
dispatchTable->PixelStorei(GL_UNPACK_ROW_LENGTH, clientState->unpack.rowLength);
dispatchTable->PixelStorei(GL_UNPACK_SKIP_PIXELS, clientState->unpack.skipPixels);
dispatchTable->PixelStorei(GL_UNPACK_SKIP_ROWS, clientState->unpack.skipRows);
dispatchTable->PixelStorei(GL_UNPACK_ALIGNMENT, clientState->unpack.alignment);
}
}
void DLM_APIENTRY crDLMSendDLMList(CRDLM *dlm, unsigned long listIdentifier,
SPUDispatchTable *dispatchTable)
{
dispatchTable->NewList(listIdentifier, GL_COMPILE);
crDLMReplayDLMList(dlm, listIdentifier, dispatchTable);
dispatchTable->EndList();
}
void DLM_APIENTRY crDLMSendList(unsigned long listIdentifier, SPUDispatchTable *dispatchTable)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
crDLMSendDLMList(listState->dlm, listIdentifier, dispatchTable);
}
}
struct sendListsCallbackParms {
CRDLM *dlm;
SPUDispatchTable *dispatchTable;
};
static void sendListsCallback(unsigned long key, void *data, void *dataPtr2)
{
struct sendListsCallbackParms *parms = (struct sendListsCallbackParms *)dataPtr2;
crDLMSendDLMList(parms->dlm, key, parms->dispatchTable);
}
void DLM_APIENTRY crDLMSendAllDLMLists(CRDLM *dlm, SPUDispatchTable *dispatchTable)
{
struct sendListsCallbackParms parms;
/* This is how we pass our parameter information to the callback routine -
* through a pointer to this local structure.
*/
parms.dlm = dlm;
parms.dispatchTable = dispatchTable;
crHashtableWalk(dlm->displayLists, sendListsCallback, (void *)&parms);
}
void DLM_APIENTRY crDLMSendAllLists(SPUDispatchTable *dispatchTable)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
crDLMSendAllDLMLists(listState->dlm, dispatchTable);
}
}
/** Another clever callback arrangement to get the desired data. */
struct getRefsCallbackParms {
int remainingOffset;
int remainingCount;
unsigned int *buffer;
int totalCount;
};
static void getRefsCallback(unsigned long key, void *data, void *dataPtr2)
{
struct getRefsCallbackParms *cbParms =
(struct getRefsCallbackParms *)dataPtr2;
/* Count the total number of references */
cbParms->totalCount++;
/* If we haven't yet reached the desired offset, decrement it */
if (cbParms->remainingOffset > 0) {
cbParms->remainingOffset--;
}
else if (cbParms->remainingCount > 0) {
/* Store data until we've stored all we can.
*/
*(cbParms->buffer++) = key;
cbParms->remainingCount--;
}
}
int DLM_APIENTRY crDLMGetReferences(CRDLM *dlm, unsigned long listIdentifier,
int firstIndex, int sizeofBuffer, unsigned int *buffer)
{
DLMListInfo *listInfo;
listInfo = (DLMListInfo *) crHashtableSearch(dlm->displayLists, listIdentifier);
if (listInfo) {
struct getRefsCallbackParms cbParms;
cbParms.remainingOffset = firstIndex;
cbParms.remainingCount = sizeofBuffer;
cbParms.buffer = buffer;
cbParms.totalCount = 0;
crHashtableWalk(listInfo->references, getRefsCallback, (void *)&cbParms);
return cbParms.totalCount;
}
else {
/* No list exists; it therefore has no references */
return 0;
}
}
CRDLMError DLM_APIENTRY crDLMGetDLMBounds(CRDLM *dlm, unsigned long listIdentifier, CRDLMBounds *bounds)
{
DLMListInfo *listInfo
= (DLMListInfo *) crHashtableSearch(dlm->displayLists, listIdentifier);
if (listInfo) {
*bounds = listInfo->bbox;
return GL_NO_ERROR;
}
else {
return GL_INVALID_OPERATION;
}
}
CRDLMError DLM_APIENTRY crDLMGetBounds(unsigned long listIdentifier, CRDLMBounds *bounds)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
return crDLMGetDLMBounds(listState->dlm, listIdentifier, bounds);
}
else {
return CRDLM_ERROR_STATE;
}
}
/**
* Set the bounding box for a display list.
*/
void DLM_APIENTRY crDLMSetDLMBounds(CRDLM *dlm, unsigned long listIdentifier,
double xmin, double ymin, double zmin,
double xmax, double ymax, double zmax)
{
DLMListInfo *listInfo
= (DLMListInfo *) crHashtableSearch(dlm->displayLists, listIdentifier);
if (!listInfo) {
/* allocate a list info now */
CRDLMContextState *listState = CURRENT_STATE();
listInfo = (DLMListInfo *) crCalloc(sizeof(DLMListInfo));
crHashtableReplace(listState->dlm->displayLists,
listIdentifier, listInfo, crdlm_free_list);
}
if (listInfo) {
listInfo->bbox.xmin = xmin;
listInfo->bbox.ymin = ymin;
listInfo->bbox.zmin = zmin;
listInfo->bbox.xmax = xmax;
listInfo->bbox.ymax = ymax;
listInfo->bbox.zmax = zmax;
}
}
void DLM_APIENTRY crDLMSetBounds(unsigned long listIdentifier,
double xmin, double ymin, double zmin,
double xmax, double ymax, double zmax)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
crDLMSetDLMBounds(listState->dlm, listIdentifier,
xmin, ymin, zmin, xmax, ymax, zmax);
}
}
/**
* Return GL_TRUE if the given list has a valid bounding box
*/
GLboolean DLM_APIENTRY crDLMListHasDLMBounds(CRDLM *dlm, unsigned long listIdentifier)
{
DLMListInfo *listInfo
= (DLMListInfo *) crHashtableSearch(dlm->displayLists, listIdentifier);
if (listInfo)
return listInfo->bbox.xmin != FLT_MAX;
else
return GL_FALSE;
}
GLboolean DLM_APIENTRY crDLMListHasBounds(unsigned long listIdentifier)
{
CRDLMContextState *listState = CURRENT_STATE();
if (listState) {
return crDLMListHasDLMBounds(listState->dlm, listIdentifier);
}
return 0;
}
/*
* Return id of list currently being compiled. Returns 0 of there's no
* current DLM state, or if no list is being compiled.
*/
GLuint DLM_APIENTRY crDLMGetCurrentList(void)
{
CRDLMContextState *listState = CURRENT_STATE();
return listState ? listState->currentListIdentifier : 0;
}
/*
* Return mode of list currently being compiled. Should be
* GL_FALSE if no list is being compiled, or GL_COMPILE if a
* list is being compiled but not executed, or GL_COMPILE_AND_EXECUTE
* if a list is being compiled and executed.
*/
GLenum DLM_APIENTRY crDLMGetCurrentMode(void)
{
CRDLMContextState *listState = CURRENT_STATE();
return listState ? listState->currentListMode : 0;
}
static CRDLMErrorCallback ErrorCallback = NULL;
void DLM_APIENTRY crDLMErrorFunction(CRDLMErrorCallback callback)
{
ErrorCallback = callback;
}
void crdlm_error(int line, const char *file, GLenum error, const char *info)
{
if (ErrorCallback)
(*ErrorCallback)(line, file, error, info);
}