[BACK]Return to pfdLoadFile.c CVS log [TXT][DIR] Up to [Development] / performer / src / lib / libpfdu

File: [Development] / performer / src / lib / libpfdu / pfdLoadFile.c (download)

Revision 1.1, Tue Nov 21 21:39:35 2000 UTC (16 years, 10 months ago) by flynnt
Branch: MAIN
CVS Tags: HEAD

Initial check-in based on OpenGL Performer 2.4 tree.
-flynnt

/*
 * pfdLoadFile.c
 *
 * $Revision: 1.1 $
 * $Date: 2000/11/21 21:39:35 $
 *
 * Load a database file into an IRIS Performer in-memory data 
 * structure using the filename's extension to intuit the file 
 * format. The file is sought in the directories named in the
 * active performer file search path (see pfFilePath for more
 * details). 
 *
 *
 * Copyright 1995, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * UNPUBLISHED -- Rights reserved under the copyright laws of the United
 * States.   Use of a copyright notice is precautionary only and does not
 * imply publication or disclosure.
 *
 * U.S. GOVERNMENT RESTRICTED RIGHTS LEGEND:
 * Use, duplication or disclosure by the Government is subject to restrictions
 * as set forth in FAR 52.227.19(c)(2) or subparagraph (c)(1)(ii) of the Rights
 * in Technical Data and Computer Software clause at DFARS 252.227-7013 and/or
 * in similar or successor clauses in the FAR, or the DOD or NASA FAR
 * Supplement.  Contractor/manufacturer is Silicon Graphics, Inc.,
 * 2011 N. Shoreline Blvd. Mountain View, CA 94039-7311.
 *
 * THE CONTENT OF THIS WORK CONTAINS CONFIDENTIAL AND PROPRIETARY
 * INFORMATION OF SILICON GRAPHICS, INC. ANY DUPLICATION, MODIFICATION,
 * DISTRIBUTION, OR DISCLOSURE IN ANY FORM, IN WHOLE, OR IN PART, IS STRICTLY
 * PROHIBITED WITHOUT THE PRIOR EXPRESS WRITTEN PERMISSION OF SILICON
 * GRAPHICS, INC.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#ifdef __linux__
#include <limits.h>   /* for PATH_MAX */
#endif
#include <Performer/pf.h>
#include <Performer/pfdu.h>
#include <Performer/pfutil.h>

typedef void		(*initFunc)(void);

typedef pfNode* 	(*loadFunc)(const char *fileName);
typedef int	 	(*storeFunc)(pfNode *, const char *fileName);

typedef void	 	(*loadNeededDSOsFunc)(const char *fileName);

typedef pfNode*	 	(*convertFromFunc)(void*);
typedef void*	 	(*convertToFunc)(pfNode*);

typedef void		(*setConverterModeFunc)(int mode, int val);
typedef int		(*getConverterModeFunc)(int mode);
typedef void		(*setConverterAttrFunc)(int which, void *attr);
typedef void*		(*getConverterAttrFunc)(int which);
typedef void		(*setConverterValFunc)(int which, float val);
typedef float		(*getConverterValFunc)(int which);

typedef struct
{
    void *			dso;
    initFunc			initConverter;
    loadFunc			loadFile;
    storeFunc			storeFile;
    loadNeededDSOsFunc		loadNeededDSOs;
    convertToFunc		convertTo;
    convertFromFunc		convertFrom;
    setConverterModeFunc	setMode;
    getConverterModeFunc	getMode;
    setConverterAttrFunc	setAttr;
    getConverterAttrFunc	getAttr;
    setConverterValFunc		setVal;
    getConverterValFunc		getVal;
    char *			name;
} ConverterDSO;

#define MAX_DSO_COUNT 128

static int		nDSOs = 0;
static ConverterDSO	DSO[MAX_DSO_COUNT];


/*
 * FUNCTION:
 *	pfdExitConverter() 
 *
 * DESCRIPTION:
 *	Discard any database loaders that have been dynamically
 *	linked into the current executable. The only reason to
 *	call this function is to save swap space.
 */

extern int
pfdExitConverter (const char *ext)
{
    int		src, dst;
    char	*tmp;
    
    /* look for final "." in filename */
    if ((tmp = strrchr(ext, '.')) != NULL)
	ext = tmp+1;
    
    
    /* release all file-loader DSOs */
    for (dst = 0, src = 0; src < nDSOs;)
    {
	if (ext == NULL || strcmp(ext, DSO[src].name) == 0)
	{
	    if (DSO[src].dso != NULL)
		dlclose(DSO[src].dso);
	    free(DSO[src].name);
	    
	}
	else
	{
	    if (dst < src)
		DSO[dst] = DSO[src];
	    dst++;
	}
	src++;
    }
    
    nDSOs -= (src - dst);
    /* return number of loaders released */
    return (src - dst);
}

/* XXX - aliases are a hack until more general performer environment is available */
static pfList *ExtList = NULL;
static pfList *ExtAliasList = NULL;
static int AliasFirstTime = 1;

extern void
pfdInitAliases(void)
{
    AliasFirstTime = 0;
    ExtList = pfNewList(sizeof(const char *), 8, pfGetSharedArena());
    ExtAliasList = pfNewList(sizeof(const char *), 8, pfGetSharedArena());
    pfdAddExtAlias("ascii","phd");
    pfdAddExtAlias("env","iv");
    pfdAddExtAlias("vrml","wrl");
    pfdAddExtAlias("default","iv");
    pfdAddExtAlias("stl","stlb");
}

extern void
pfdAddExtAlias(const char *ext, const char *alias)
{
    char *tmp;
    if (AliasFirstTime)
	pfdInitAliases();
    
    /* look for final "." in filename */
    if ((tmp = strrchr(ext, '.')) != NULL)
	ext = tmp+1;
    
    pfAdd(ExtList, strdup(ext));
    pfAdd(ExtAliasList, strdup(alias));
}

extern const char *
pfdGetExtAlias(const char *ext)
{
    int i;
    char *tmp;

    if (AliasFirstTime)
	pfdInitAliases();

    /* look for final "." in filename */
    if ((tmp = strrchr(ext, '.')) != NULL)
	ext = tmp+1;

    for(i=0;i<pfGetNum(ExtList);i++)
	if (strcmp(pfGet(ExtList, i), ext) == 0)
	    return pfGet(ExtAliasList, i);
    return ext;
}

/*
 * For every dir in path, call sgidlopen(dir/filename).
 * Return the first one that succeeds.
 * Do NOT call this with an absolute pathname;
 * it will do something nonsensical.
 */
static void *
dlopen_version_path(const char *path,
		    const char *filename, int mode,
		    const char *version,
		    int flags, int verbose,
		    char fullname[PATH_MAX])
{
    void *handle;
    const char *dir;

    fullname[0] = '\0';

    for (dir = path; *dir; dir = path) {
	while (*path != '\0' && *path != ':')
	    path++;

	if (path - dir > 0 &&
	    path - dir + 1 + strlen(filename) + 1 <= PATH_MAX)
	{
	    (void) sprintf(fullname, "%.*s/%s", path - dir, dir, filename);

	    if (verbose)
		pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
			 "pfdFindConverterDSO() - trying \"%s\" version \"%s\"",
			 fullname, version);

#ifdef __linux__
	    handle = dlopen (fullname, mode);
            if (handle != NULL) 
		return handle;
#else
	    handle = sgidlopen_version(fullname, mode, version, flags);
	    if (handle != NULL) 
	    {
		/*
		 * The following is necessary due to an RLD bug
		 * that causes it to accept versionless DSOs...
		 * It happens that we actually want to accept them,
		 * but only for Performer2.0.
		 */
		char *got_version = sgigetdsoversion(filename);
		if (got_version == NULL || got_version[0] == '\0')
		{
		    pfNotify(PFNFY_WARN, PFNFY_PRINT,
			"Using loader DSO %s", fullname);
		    pfNotify(PFNFY_WARN, PFNFY_MORE,
			"   which has no version number;");
		    pfNotify(PFNFY_WARN, PFNFY_MORE,
		        "   please give it one by linking with ");
		    pfNotify(PFNFY_WARN, PFNFY_MORE,
			"   \"-set_version %s\"", version);
		}

		pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
			 "pfdFindConverterDSO() - using DSO \"%s\"", fullname);

		return handle;
	    }
#endif
	    if (verbose)
		pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
			 "  dlopen said: %s\n", dlerror());
	}

	if (*path == ':')
	    path++;
    }
    return NULL;
}

static ConverterDSO *
_pfdFindConverterDSO(const char *fileName, int warnIfNotFound)
{
    ConverterDSO 	*cDSO		= NULL;
    const char	*s		= NULL;
    char        *newPath	= NULL;
    char 	*libraryPath	= NULL;
    char 	*pfLibraryPath	= NULL;
    char 	*rootPath	= NULL;
    char	*version	= NULL;
    char	*versionuse	= NULL;
    char	versionpart[80];
    int 	i;
    char	initFuncName[PF_MAXSTRING];
    char	loadFuncName[PF_MAXSTRING];
    char	storeFuncName[PF_MAXSTRING];
    char	loadNeededDSOsFuncName[PF_MAXSTRING];
    char	convertToFuncName[PF_MAXSTRING];
    char	convertFromFuncName[PF_MAXSTRING];
    char	setConverterModeFuncName[PF_MAXSTRING];
    char	getConverterModeFuncName[PF_MAXSTRING];
    char	setConverterAttrFuncName[PF_MAXSTRING];
    char	getConverterAttrFuncName[PF_MAXSTRING];
    char	setConverterValFuncName[PF_MAXSTRING];
    char	getConverterValFuncName[PF_MAXSTRING];
    char	dsoName[PF_MAXSTRING];
    char	dsoPath[PATH_MAX];
    char	*glStyle;
    int		verbose = FALSE;
    const char	*ext, *dsoext;
    
    /* get the loader type from the file's extension */
    
    /* look for final "." in filename */
    if ((ext = strrchr(fileName, '.')) == NULL)
	/* no dot, assume it's just the extension */
	ext = fileName;
    else 
  	/* advance "ext" past the period character */
	++ext;
    
    if (strlen(ext) < 1)
      pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdFindConverterDSO() - Trying default file type extension. "
		 "Conversion may not be possible!");
    
    ext = pfdGetExtAlias(ext);
    
    /* is the loader for this file type already available ? */
    for (i = 0; i < nDSOs; i++)
	if (DSO[i].name && (strcmp(ext, DSO[i].name) == 0))
	    return &DSO[i];
    
    sprintf(initFuncName,"pfdInitConverter_%s", ext);
    sprintf(loadFuncName,"pfdLoadFile_%s", ext);
    sprintf(storeFuncName,"pfdStoreFile_%s", ext);
    sprintf(loadNeededDSOsFuncName,"pfdLoadNeededDSOs_%s", ext);
    sprintf(convertToFuncName,"pfdConvertTo_%s", ext);
    sprintf(convertFromFuncName,"pfdConvertFrom_%s", ext);
    sprintf(setConverterModeFuncName,"pfdConverterMode_%s", ext);
    sprintf(getConverterModeFuncName,"pfdGetConverterMode_%s", ext);
    sprintf(setConverterAttrFuncName,"pfdConverterAttr_%s", ext);
    sprintf(getConverterAttrFuncName,"pfdGetConverterAttr_%s", ext);
    sprintf(setConverterValFuncName,"pfdConverterVal_%s", ext);
    sprintf(getConverterValFuncName,"pfdGetConverterVal_%s", ext);
    
    /* is the loader for this file type linked into the executable ? */
    cDSO = &DSO[PF_MIN2(nDSOs, MAX_DSO_COUNT-1)];
    
    cDSO->dso = dlopen(NULL, RTLD_LAZY);
    if (cDSO->dso != NULL) {
      cDSO->initConverter  = (initFunc)	dlsym(cDSO->dso, initFuncName);
      cDSO->loadFile  = (loadFunc)		dlsym(cDSO->dso, loadFuncName);
      cDSO->storeFile  = (storeFunc)		dlsym(cDSO->dso, storeFuncName);
      cDSO->loadNeededDSOs = (loadNeededDSOsFunc)dlsym(cDSO->dso, loadNeededDSOsFuncName);
      cDSO->convertTo  = (convertToFunc)	dlsym(cDSO->dso, convertToFuncName);
      cDSO->convertFrom  = (convertFromFunc)	dlsym(cDSO->dso, convertFromFuncName);
      cDSO->setMode = (setConverterModeFunc)	dlsym(cDSO->dso, setConverterModeFuncName);
      cDSO->getMode = (getConverterModeFunc)	dlsym(cDSO->dso, getConverterModeFuncName);
      cDSO->setAttr = (setConverterAttrFunc)	dlsym(cDSO->dso, setConverterAttrFuncName);
      cDSO->getAttr = (getConverterAttrFunc)	dlsym(cDSO->dso, getConverterAttrFuncName);
      cDSO->setVal = (setConverterValFunc)	dlsym(cDSO->dso, setConverterValFuncName);
      cDSO->getVal = (getConverterValFunc)	dlsym(cDSO->dso, getConverterValFuncName);
      
      /* if initConverter exists, call it so the loader can execute appropriate setup */
      if (cDSO->initConverter != NULL) {
	(*cDSO->initConverter)();
      }
      
      if (cDSO->loadFile != NULL)
	{
	  /* if loadFile was not null, we actually do have the converter loaded in, so
	     increment converter count and return the dso. */
	  nDSOs++;
	  cDSO->name = strdup(ext);
	  return cDSO;
	}
    }
    
    /* at this point, loader is not linked into executable */

    if (!strcmp(ext, "pfa"))
    {
	/*
	 * Special case for pfa-- the pfa functions are included in
	 * the pfb DSO, but pfa is *not* an alias for pfb. So we have the
	 * function names right (pfa) but we want to look for the DSO
	 * using pfb instead.
	 */
	dsoext = "pfb";
    }
    else
	dsoext = ext;
    
    /* Must find a dso for this extension */
    
    /* get file search paths for use in locating loader */
    if (
#if defined(N64)
	(s = getenv("PFLD_LIBRARY64_PATH")) != NULL ||
#elif defined(N32)
	(s = getenv("PFLD_LIBRARYN32_PATH")) != NULL ||
#endif /* N32 */
	(s = getenv("PFLD_LIBRARY_PATH")) != NULL)
    {
	verbose = TRUE;
	pfLibraryPath = strdup(s);
    }
    else
	pfLibraryPath = strdup("");
    
    /* get file search paths for use in locating loader */
    if (
#if defined(N64)
	(s = getenv("LD_LIBRARY64_PATH")) != NULL ||
#elif defined(N32)
	(s = getenv("LD_LIBRARYN32_PATH")) != NULL ||
#endif /* N32 */
        (s = getenv("LD_LIBRARY_PATH")) != NULL)
	libraryPath = strdup(s);
    else
	libraryPath = strdup("");
    
    if ((s = getenv("PFHOME")) != NULL)
	rootPath = strdup(s);
    else
	rootPath = strdup("");
    
    /* form new search path for locating loader */
    newPath = malloc(1024 + strlen(libraryPath) + 2*strlen(rootPath));
#if defined(N64)
    sprintf(newPath, 
	    "%s"
	    ":."
	    ":%s"
	    ":%s/usr/lib64/libpfdb"
	    ":%s/usr/share/Performer/lib/libpfdb",
	    pfLibraryPath, libraryPath, rootPath, rootPath);
#elif defined(N32)
    sprintf(newPath, 
	    "%s"
	    ":."
	    ":%s"
	    ":%s/usr/lib32/libpfdb"
	    ":%s/usr/share/Performer/lib/libpfdb",
	    pfLibraryPath, libraryPath, rootPath, rootPath);
#else /* is O32 */
    sprintf(newPath, 
	    "%s"
	    ":."
	    ":%s"
	    ":%s/usr/lib/libpfdb"
	    ":%s/libpfdb"
	    ":%s/usr/share/Performer/lib/libpfdb",
	    pfLibraryPath, libraryPath, rootPath, libraryPath, rootPath);
#endif /* O32 */

    free(pfLibraryPath);
    free(libraryPath);
    free(rootPath);

    /* print new search path */
    {
	char *body = strdup(newPath);
	char *head = body;
	char *tail = NULL;
	
	pfNotify(PFNFY_DEBUG, PFNFY_PRINT, "");
	pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
		 "pfdFindConverterDSO() - DSO search path is:");
	while (*head && (tail = strchr(head, ':')) != NULL)
	{
	    *tail = '\0';
	    pfNotify(PFNFY_DEBUG, PFNFY_MORE, "  \"%s:\"", head);
	    head = tail + 1;
	}
	pfNotify(PFNFY_DEBUG, PFNFY_MORE, "  \"%s\"", head);
	
	free(body);
    }
    
    /*
     * Get the DSO interface version of libpfdu.so
     */
#ifdef __linux__
    sprintf(dsoName, "libpf%s.so", dsoext);
    cDSO->dso = dlopen_version_path(newPath, dsoName, RTLD_LAZY, versionuse, 0, verbose, dsoPath);
#else
    version = sgigetdsoversion("libpfdu.so");
    if (version == NULL || version[0] == '\0')
    {
	version = "sgi5.0";
	pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
		"pfdFindConverterDSO() - can't get version of libpfdu.so, "
		"using %s", version);
    }
    else
    {
	pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
		"pfdFindConverterDSO() - version of libpfdu.so is %s",
		version);
    }

    /* 
     * munging the returned version string to extract sgiX.x from 
     * sgiX.x:sgiX.x:sgiX.x..  
     *
     * we cannot strtok 'version' as it is memory used by sgigetdsoversion,
     * so we copy it into versionpart.  
     *
     */

    strncpy(versionpart, version, 80);
    versionpart[79]='\0';
    versionuse=strtok(versionpart,":");
    
    /*
     * look for both optimized and debug versions of loader
     * accept non gl specifier as an OpenGL loader
     */
    
    /* look for OPT (no extension) version of DSO */
    sprintf(dsoName, "libpf%s.so", dsoext);
    cDSO->dso = dlopen_version_path(newPath, dsoName, RTLD_LAZY, versionuse, 0, verbose, dsoPath);
    if (cDSO->dso == NULL)
	pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
	    "pfdFindConverterDSO() - Did not find optimized DSO \"%s\"", dsoName);

    /* look for DBG ("-g" extension) version of DSO */
    if (cDSO->dso == NULL)
    {
    	sprintf(dsoName, "libpf%s-g.so", dsoext);
	cDSO->dso = dlopen_version_path(newPath, dsoName, RTLD_LAZY, versionuse, 0, verbose, dsoPath);
	if (cDSO->dso == NULL)
	    pfNotify(PFNFY_DEBUG, PFNFY_PRINT,
		"pfdFindConverterDSO() - Did not find debug DSO \"%s\"", dsoName);
    }
#endif /* __linux__ */

    free(newPath);

    /*
     * try to load the file using the loader DSO
     */
    if (cDSO->dso != NULL)
    {
	/*
	 *  We have needed to load the Converter DSO.  If pfConfig()
	 *  has already been called this could cause a problem.
	 */
	if (pfIsConfiged())
	{
	    pfNotify(PFNFY_NOTICE, PFNFY_PRINT,
		     "pfdFindConverterDSO(%s) called after pfConfig().",
		     fileName);
	    pfNotify(PFNFY_NOTICE, PFNFY_MORE,
		     "This might cause problems.");
	    pfNotify(PFNFY_NOTICE, PFNFY_MORE,
		     "Call pfdInitConverter(%s) before pfConfig() so that "
		     "all performer processes will have the converter dso "
		     "in their process space.", fileName);
	}

	nDSOs++;
	
	cDSO->initConverter  = (initFunc)		dlsym(cDSO->dso, initFuncName);
	cDSO->loadFile  = (loadFunc)		dlsym(cDSO->dso, loadFuncName);
	cDSO->storeFile  = (storeFunc)		dlsym(cDSO->dso, storeFuncName);
	cDSO->loadNeededDSOs = (loadNeededDSOsFunc)dlsym(cDSO->dso, loadNeededDSOsFuncName);
	cDSO->convertTo  = (convertToFunc)	dlsym(cDSO->dso, convertToFuncName);
	cDSO->convertFrom  = (convertFromFunc)	dlsym(cDSO->dso, convertFromFuncName);
	cDSO->setMode = (setConverterModeFunc)	dlsym(cDSO->dso, setConverterModeFuncName);
	cDSO->getMode = (getConverterModeFunc)	dlsym(cDSO->dso, getConverterModeFuncName);
	cDSO->setAttr = (setConverterAttrFunc)	dlsym(cDSO->dso, setConverterAttrFuncName);
	cDSO->getAttr = (getConverterAttrFunc)	dlsym(cDSO->dso, getConverterAttrFuncName);
	cDSO->setVal = (setConverterValFunc)	dlsym(cDSO->dso, setConverterValFuncName);
	cDSO->getVal = (getConverterValFunc)	dlsym(cDSO->dso, getConverterValFuncName);
	if (cDSO->initConverter != NULL)
	{
	    (*cDSO->initConverter)();
	}
	if (cDSO->loadFile != NULL)
	{
	    cDSO->name = strdup(ext);
	    return cDSO;
	}

	if (cDSO->loadFile == NULL)
	{
	    pfNotify(PFNFY_WARN, PFNFY_ASSERT,
		"pfdFindConverterDSO() - Function \"%s\" not defined in DSO \"%s\"", 
		    loadFuncName, dsoPath);
	}
	return cDSO;
    }

    if (!strcmp(ext, "iv"))
    {	
	/* 
	 * special case for Inventor loader.  If default loader fails 
	 * to load (probably because libInventor.so.3 was not present), 
	 * try the 2.0 version
	 */
	cDSO = _pfdFindConverterDSO("iv20", warnIfNotFound);
	if (cDSO != NULL)
	{
	    cDSO->name = "iv";
	    return cDSO;
	}
    }	

    pfNotify(warnIfNotFound ? PFNFY_WARN : PFNFY_DEBUG, PFNFY_PRINT,
	     "pfdFindConverterDSO() - Could not load DSO for extension \"%s\"", 
	     ext);

    return NULL;
}

extern ConverterDSO *
pfdFindConverterDSO(const char *fileName)
{
    return _pfdFindConverterDSO(fileName, 1);
}

extern void
pfdConverterMode(const char *ext, int mode, int value)
{
    ConverterDSO *dso = pfdFindConverterDSO(ext);
    if (dso && dso->setMode)
	(*dso->setMode)(mode, value);
}

extern int
pfdGetConverterMode(const char *ext, int mode)
{
    ConverterDSO *dso = pfdFindConverterDSO(ext);
    if (dso && dso->setMode)
	return (*dso->getMode)(mode);
    return -1;
}

extern void
pfdConverterAttr(const char *ext, int which, void *attr)
{
    ConverterDSO *dso = pfdFindConverterDSO(ext);
    if (dso && dso->setAttr)
	(*dso->setAttr)(which, attr);
}

extern void *
pfdGetConverterAttr(const char *ext, int which)
{
    ConverterDSO *dso = pfdFindConverterDSO(ext);
    if (dso && dso->getAttr)
	return (*dso->getAttr)(which);
    return NULL;
}

extern void
pfdConverterVal(const char *ext, int which, float val)
{
    ConverterDSO *dso = pfdFindConverterDSO(ext);
    if (dso && dso->setVal)
	(*dso->setVal)(which, val);
}

extern float
pfdGetConverterVal(const char *ext, int which)
{
    ConverterDSO *dso = pfdFindConverterDSO(ext);
    if (dso && dso->getVal)
	return (*dso->getVal)(which);
    return 0.;
}

/*
 * FUNCTION:
 *	pfdPrintSceneGraphStats()
 *
 * DESCRIPTION:
 *	Print some simple statistics about the primitives in the
 *	scene graph, using pfNotify.
 *	The intent of this function is to modularize some code
 *	duplicated often in the loader functions themselves.
 */

extern void
pfdPrintSceneGraphStats (pfNode *node, double elapsedTime)
{
    double totalPrims;

    /* statistics information */
    static float ftmp[4];
    static uint qtmp[4] =
    {
        PFFSTATS_BUF_CUR | PFSTATSVAL_GFX_GEOM_TRIS,
        PFFSTATS_BUF_CUR | PFSTATSVAL_GFX_GEOM_LINES,
        PFFSTATS_BUF_CUR | PFSTATSVAL_GFX_GEOM_POINTS,
        NULL
    };

    /* only traverse if the scene graph loaded successfully */
    if (node != NULL)
    {
	int				numPrimTypes = 0;
        pfFrameStats            *fs = pfNewFStats();

        /* traverse scene graph and tabulate contents */
        pfuTravCountDB(node, fs);

        /* extract values from tabulated statistics */
        pfMQueryFStats(fs, qtmp, ftmp, 0);

        /* print information about scene graph contents */
        pfNotify(PFNFY_INFO, PFNFY_MORE, "  Scene-graph statistics:");
	if (ftmp[0] != 0)
	{
	    ++numPrimTypes;
	    pfNotify(PFNFY_INFO, PFNFY_MORE, "    Triangles:          %8d",
		(long)ftmp[0]);
	}
	if (ftmp[1] != 0)
	{
	    ++numPrimTypes;
	    pfNotify(PFNFY_INFO, PFNFY_MORE, "    Lines:              %8d",
		(long)ftmp[1]);
	}
	if (ftmp[2] != 0)
	{
	    ++numPrimTypes;
	    pfNotify(PFNFY_INFO, PFNFY_MORE, "    Points:             %8d",
		(long)ftmp[2]);
	}

	totalPrims = ftmp[0] + ftmp[1] + ftmp[2];
	if (numPrimTypes > 1)
	{
	    pfNotify(PFNFY_INFO, PFNFY_MORE, "    Total primitives:   %8d",
		(long)totalPrims);
	}

        /* print elapsed time and loading rate */
        if (elapsedTime > 0.0)
        {
            pfNotify(PFNFY_INFO, PFNFY_MORE,
		"    loading time:       %12.3f sec", elapsedTime);

            if (totalPrims > 0.0f)
                pfNotify(PFNFY_INFO, PFNFY_MORE, "    loading rate"
		    ":       %12.3f prims/sec", totalPrims/elapsedTime);
        }

	pfNotify(PFNFY_INFO, PFNFY_MORE, NULL);

        /* discard stats structure */
        pfDelete(fs);
    }
}


/*
 * FUNCTION:
 *	pfdInitConverter() 
 *
 * DESCRIPTION:
 *	If the loader for the specified extension is not already
 *	in the image, open the DSO now.  Useful for mapping loader
 *	routines into all processes before pfConfig().  Necessary
 *	if loaders contain functions (including destructors) that
 *	may be invoked in different Performer processes.
 */

extern int
pfdInitConverter(const char *_ext)
{
    char *ext;
    ConverterDSO *dso;

    if ((dso = pfdFindConverterDSO(_ext)) == NULL)
	return 0;

    if (dso && dso->loadNeededDSOs)
	(*dso->loadNeededDSOs)(_ext);

    /*
     * In the case of multiple extensions (e.g. foo.im.closest)
     * attempt to initialize the converter for all of the extensions
     * (but return 1 regardless of their success or failure,
     * since we already know the final one succeeded).
     */
    if ((ext = strrchr(_ext, '.')) != NULL)
    {
	char buf[PATH_MAX];
	strncpy(buf, _ext, ext-_ext);
	buf[ext-_ext] = '\0';
	while ((ext = strrchr(buf, '.')) != NULL)
	{
	    if (strrchr(ext, '/') != NULL)
		break;
	    dso = _pfdFindConverterDSO(buf, 0);
	    if (dso && dso->loadNeededDSOs)
		(*dso->loadNeededDSOs)(buf);
	    *ext = '\0';
	}
    }

    return 1;
}

/*
 * FUNCTION:
 *	pfdLoadFile() 
 *
 * DESCRIPTION:
 *	Load a database file into an IRIS Performer in-memory data 
 *	structure using the filename's extension to intuit the file 
 *	format. The file is sought in the directories named in the
 *	active performer file search path (see pfFilePath for more
 *	details). 
 */

extern pfNode *
pfdLoadFile(const char *fileName)
{
    ConverterDSO *dso  = 	NULL;
    pfNode *node = 	NULL;
    double startTime;
    double elapsedTime;
    const char *ext;

    /* look for final "." in filename */
    if ((ext = strrchr(fileName, '.')) == NULL)
	/* no dot, assume it's just the extension */
	ext = fileName;
    else 
  	/* advance "ext" past the period character */
	++ext;

    /* Get the ConverterDSO struct for this extension */
    dso = pfdFindConverterDSO(fileName);

    if (dso && dso->loadFile != NULL)
    {
#ifdef	VERBOSE_NOTIFICATION
	pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "pfdLoadFile");
	pfNotify(PFNFY_NOTICE, PFNFY_MORE,  "  Loader name:           pfdLoadFile_%s", ext);
	pfNotify(PFNFY_NOTICE, PFNFY_MORE,  "  File name:             %s", fileName);
#endif

	/* Call loader and collect elapsed time */
	startTime = pfGetTime();
	node = (*dso->loadFile)(fileName);	/* invoke loader */
	elapsedTime = pfGetTime() - startTime;

	/* Print some simple statistics on the scene graph */
	pfdPrintSceneGraphStats(node, elapsedTime);

    }
    else
	pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdLoadFile() - Unable to load file %s because of problem finding pfdLoadFile_%s",
		 fileName, ext);

    /* reset builder geometry and graphics state */
    pfdResetBldrGeometry();
    pfdResetBldrState();

    /* return pointer to in-memory hierarchy */
    return node;
}


/*
 * FUNCTION:
 *	pfdStoreFile() 
 *
 * DESCRIPTION:
 *	Store a database file into an IRIS Performer in-memory data 
 *	structure using the filename's extension to intuit the file 
 *	format. The file is sought in the directories named in the
 *	active performer file search path (see pfFilePath for more
 *	details). 
 */

extern int
pfdStoreFile(pfNode *root, const char *fileName)
{
    ConverterDSO *dso  = 	NULL;
    const char *ext;

    /* look for final "." in filename */
    if ((ext = strrchr(fileName, '.')) == NULL)
	/* no dot, assume it's just the extension */
	ext = fileName;
    else 
  	/* advance "ext" past the period character */
	++ext;

    /* Get the ConverterDSO struct for this extension */
    dso = pfdFindConverterDSO(fileName);

    if (dso == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdStoreFile() - No converter available for %s\n", fileName);
	return FALSE;
    }
	
    if (dso->storeFile == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdStoreFile() - Converter for %s does not provide pfdStoreFile_%s\n", ext, ext);
	return FALSE;
    }

    return dso->storeFile(root, fileName);
}


/*
 * FUNCTION:
 *	pfdConvertFrom() 
 *
 * DESCRIPTION:
 *	Load a database file into an IRIS Performer in-memory data 
 *	structure from another in-memory representation
 */

extern pfNode *
pfdConvertFrom(void *root, const char *ext)
{
    ConverterDSO *dso  = 	NULL;
    char *tmp;

    /* look for final "." in filename */
    if ((tmp = strrchr(ext, '.')) != NULL)
	ext = tmp+1;

    /* Get the ConverterDSO struct for this extension */
    dso = pfdFindConverterDSO(ext);

    if (dso == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdConvertFrom() - No converter available for %s\n", ext);
	return NULL;
    }
	
    if (dso->convertFrom == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdConvertFrom() - Converter for %s does not provide a pfdConvertFrom_%s\n", ext, ext);
	return NULL;
    }
 
    /* reset builder geometry and graphics state */
    pfdResetBldrGeometry();
    pfdResetBldrState();

    return dso->convertFrom(root);
}

/*
 * FUNCTION:
 *	pfdConvertTo() 
 *
 * DESCRIPTION:
 *	Load a database file into an IRIS Performer in-memory data 
 *	structure to another in-memory representation
 */

extern void*
pfdConvertTo(pfNode *root, const char *ext)
{
    ConverterDSO *dso  = 	NULL;
    char *tmp;

    /* look for final "." in filename */
    if ((tmp = strrchr(ext, '.')) != NULL)
	ext = tmp+1;

    /* Get the ConverterDSO struct for this extension */
    dso = pfdFindConverterDSO(ext);

    if (dso == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdConvertTo() - No converter available for %s\n", ext);
	return NULL;
    }
	
    if (dso->convertTo == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_PRINT,
		 "pfdConvertTo() - Converter for %s does not provide a pfdConvertTo_%s\n", ext, ext);
	return NULL;
    }

    return dso->convertTo(root);
}


/*
 *  User callback function registry.
 */

typedef struct user_func_s {
    void *func;
    char *name;
    char *dso_name;
} user_func_t;

typedef struct ufr_s {
    user_func_t *funcs;
    int num;
    int size;
} ufr_t;

static ufr_t *ufr = NULL;


static void
_pfdInitUserFuncRegistry(void)
{
    ufr = (ufr_t*)pfMalloc(sizeof(ufr_t), pfGetSharedArena());
    ufr->funcs = NULL;
    ufr->num = 0;
    ufr->size = 0;
}


/*
 * FUNCTION:
 *	pfdRegisterUserFunc() 
 *
 * DESCRIPTION:
 *	Register a user callback function.
 */
int
pfdRegisterUserFunc(void *func, const char *name, const char *dso_name)
{
    int i;

    if (ufr == NULL)
	_pfdInitUserFuncRegistry();

    if (name == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_USAGE,
		 "pfdRegisterUserFunc:  Name must not be NULL.");
	return(-1);
    }

    for (i = 0; i < ufr->num; i++)
	if (strcmp(name, ufr->funcs[i].name) == 0)
	{
	    if (func != NULL)
	    {
		if (ufr->funcs[i].dso_name)
		    pfFree(ufr->funcs[i].dso_name);
		if (dso_name)
		{
		    ufr->funcs[i].dso_name =
			(char *)pfMalloc(strlen(dso_name)+1, pfGetArena(ufr));
		    strcpy(ufr->funcs[i].dso_name, dso_name);
		}
		else
		    ufr->funcs[i].dso_name = NULL;
		ufr->funcs[i].func = func;
	    }
	    else /* func == NULL */
	    {
		/*
		 *  NULL func means unregister the function name.
		 */
		pfFree(ufr->funcs[i].name);
		if (ufr->funcs[i].dso_name)
		    pfFree(ufr->funcs[i].dso_name);
		for (i++; i < ufr->num; i++)
		{
		    ufr->funcs[i-1].func = ufr->funcs[i].func;
		    ufr->funcs[i-1].name = ufr->funcs[i].name;
		    ufr->funcs[i-1].dso_name = ufr->funcs[i].dso_name;
		}
		ufr->num--;
	    }
	}

    if (ufr->num == ufr->size)
    {
	if (ufr->size == 0)
	{
	    ufr->size = 8;
	    ufr->funcs = (user_func_t*)pfMalloc(ufr->size * sizeof(user_func_t),
					        pfGetArena(ufr));
	}
	else
	{
	    ufr->size <<= 1;
	    ufr->funcs = (user_func_t*)pfRealloc(ufr->funcs,
						 ufr->size * sizeof(user_func_t));
	}
    }

    ufr->funcs[ufr->num].func = func;
    ufr->funcs[i].name = (char *)pfMalloc(strlen(name)+1, pfGetArena(ufr));
    strcpy(ufr->funcs[i].name, name);
    if (dso_name)
    {
	ufr->funcs[i].dso_name = (char *)pfMalloc(strlen(dso_name)+1,
						  pfGetArena(ufr));
	strcpy(ufr->funcs[i].dso_name, dso_name);
    }
    else
	ufr->funcs[ufr->num].dso_name = NULL;
    ufr->num++;

    return(0);
}


/*
 * FUNCTION:
 *	pfdGetRegisteredUserFunc() 
 *
 * DESCRIPTION:
 *	Return the name and dso_name of a registered user func.
 */
int
pfdGetRegisteredUserFunc(void *func, char **name, char **dso_name)
{
    int i;

    if (ufr == NULL)
	_pfdInitUserFuncRegistry();

    for (i = 0; i < ufr->num; i++)
	if (func == ufr->funcs[i].func)
	{
	    *name = ufr->funcs[i].name;
	    *dso_name = ufr->funcs[i].dso_name;
	    return TRUE;
	}

    *name = NULL;
    *dso_name = NULL;
    return FALSE;
}


/*
 * FUNCTION:
 *	pfdIsRegisteredUserFunc() 
 *
 * DESCRIPTION:
 *	Is func a registerd user func.
 */
int
pfdIsRegisteredUserFunc(void *func)
{
    int i;

    if (ufr == NULL)
	_pfdInitUserFuncRegistry();

    for (i = 0; i < ufr->num; i++)
	if (func == ufr->funcs[i].func)
	    return TRUE;

    return FALSE;
}


/*
 * FUNCTION:
 *	pfdFindRegisteredUserFunc() 
 *
 * DESCRIPTION:
 *	Find and return the user func with name.
 */
void *
pfdFindRegisteredUserFunc(char *name)
{
    int i;

    if (ufr == NULL)
	_pfdInitUserFuncRegistry();

    for (i = 0; i < ufr->num; i++)
	if (strcmp(name, ufr->funcs[i].name) == 0)
	    return ufr->funcs[i].func;

    return NULL;
}