Appendix A. Building a Visual Simulation Application Using libpf

This appendix outlines the steps involved in using libpf, the visual simulation development library. The outline follows the development sequence of a skeleton application program that introduces the basic concepts involved in creating a visual simulation application with libpf. Each step at which more complex constructions are possible gives a cross reference to a later section where you can learn more about the topic.

For a more modular approach using a graphical viewer, see Appendix B, “Building a Visual Simulation Application Using libpfv”.


It takes only a few lines of code to set up an OpenGL Performer libpf application. Furthermore, once you have an application framework that you like you can use it again to create other libpf applications.

Certain configuration and control routines are required in all applications, while others depend on the features needed and the platform for which the application is designed. The basic requirements for simple programs are the same as for more complex programs, so you can learn the basic structure from a very simple framework application and then build on it to suit your needs.

Take a few moments to browse through the introductory program, simple.c, shown in Example A-1. If you want to compile this program, see the section “Compiling and Linking OpenGL Performer Applications”.

Note: Sample code built upon the framework presented in simple.c is presented throughout the remainder of this guide, so familiarize yourself with the concepts presented here before reviewing more advanced subjects.

Example A-1 shows the basic framework of an OpenGL Performer application.

Example A-1. Structure of an OpenGL Performer Application

#include <stdlib.h>
#include <Performer/pf.h>
#include <Performer/pfutil.h>
#include <Performer/pfdu.h>
main (int argc, char *argv[])
 float t = 0.0f;
 pfScene *scene;
 pfNode *root;
 pfPipe *p;
 pfPipeWindow *pw;
 pfChannel *chan;
 pfSphere bsphere;
 if (argc < 2)
     “Usage: simple file.ext\n”);
/* Initialize Performer */
  * Select multiprocessing mode based on
  * number of processors
 pfMultiprocess( PFMP_DEFAULT ); 
 /* Load all loader DSO's before pfConfig() forks */
  * Initiate multi-processing mode set by pfMultiprocess 
  * FORKs for Performer processes, CULL and DRAW, etc.
  * happen here.
  * Append to Performer search path, PFPATH, files in 
  * /usr/share/Performer/data
 /* Read a single file, of any known type. */
 if ((root = pfdLoadFile(argv[1])) == NULL) 
 /* Attach loaded file to a new pfScene. */
 scene = pfNewScene();
 pfAddChild(scene, root);
 /* Create a pfLightSource and attach it to scene. */
 pfAddChild(scene, pfNewLSource());
 /* Configure and open graphics window */
 p = pfGetPipe(0);
 pw = pfNewPWin(p);
 pfPWinType(pw, PFPWIN_TYPE_X);
 pfPWinName(pw, “OpenGL Performer”);
 pfPWinOriginSize(pw, 0, 0, 500, 500);
 /* Open and configure the GL window. */
 /* Create and configure a pfChannel. */
 chan = pfNewChan(p); 
 pfChanScene(chan, scene);
 pfChanFOV(chan, 45.0f, 0.0f);
 /* determine extent of scene's geometry */
 pfGetNodeBSphere (root, &bsphere);
 pfChanNearFar(chan, 1.0f, 10.0f * bsphere.radius);
 /* Simulate for twenty seconds. */
 while (t < 20.0f)
   pfCoord view;
   float s, c;
   /* Compute new view position. */
   t = pfGetTime();
   pfSinCos(45.0f*t, &s, &c);
   pfSetVec3(view.hpr, 45.0f*t, -10.0f, 0);
   pfSetVec3(, 2.0f * bsphere.radius * s, 
     -2.0f * bsphere.radius *c, 
      0.5f * bsphere.radius);
   pfChanView(chan,, view.hpr);
   /* Initiate cull/draw for this frame. */
/* Terminate parallel processes and exit. */

If you want to compile simple.c and try it, use the copy in /usr/share/Performer/src/pguide/libpf/C (for IRIX and Linux systems) and %PFROOT%\Src\pguide\lippf\C (for Windows systems). File Makefile in that directory provides all the necessary compilation options. (For more information about OpenGL Performer compiler options, see section “Compiling and Linking OpenGL Performer Applications”.) Once you have compiled the code, try executing it with some of the sample data files in /usr/share/Performer/data (for IRIX and Linux systems) and %PFROOT%\Data (for Windows systems), such as blimp.flt or sampler.nff.

The following describes the steps involved in a simple OpenGL Performer application. Refer to the sample code in Example A-1 as you read these steps.

  1. Include the necessary system header files.

    #include <stdlib.h>

  2. Include the relevant OpenGL Performer header files.

    #include <Performer/pf.h>
    #include <Performer/pfutil.h>
    #include <Performer/pfdu.h>

  3. Declare variables for the required elements.


    Scene graph to be rendered on a channel


    Graphics pipeline to perform the rendering


    View to be rendered on the designated pipe

    You can configure OpenGL Performer to use multiple scenes, multiple pipes (if your system has them), and multiple channels per pipe. (See “Using Multiple Channels” in Chapter 5.)

  4. Initialize OpenGL Performer.


    This sets up the shared memory arena used for multiprocessing, initializes the high-resolution clock, and resets OpenGL Performer's state.

  5. Configure OpenGL Performer.


    This configures the number of pipes and starts processes based on the selected multiprocessing model. The code in Example A-1 uses the defaults: a single pipe and a multiprocessing model that is tailored to the number of processors on the machine.

  6. Load or create the database.

    root = pfdLoadFile(argv[1])

    pfdLoadFile() loads a database from the disk using whichever file importer seems appropriate (based on the three-letter extension at the end of the given filename). There are other ways to set up scenes, too; for example, you can call a specific importing routine in place of pfdLoadFile() if you want to load only databases of a particular format, or you can create geometric objects directly using libpr and place them in a database hierarchy. See Chapter 17, “Math Routines ,” in the OpenGL Performer Programmer's Guide for information on constructing pfGeoSets, and Chapter 6, “Creating Scene Graphs”, for information on creating a scene graph.

  7. Create a new scene for the channel to draw.

    scene = pfNewScene();

  8. Add the root of the database that you loaded or created in step 6 to the scene.

    pfAddChild(scene, root);

  9. Initialize a graphics-rendering pipeline with a custom window.

    p = pfGetPipe(0);
    pw = pfNewPWin(p);
    pfPWinType(pw, PFPWIN_TYPE_X);
    pfPWinName(pw, “OpenGL Performer”);
    pfPWinOriginSize(pw, 0, 0, 500, 500);
    /* set up configuration callback OpenPipeWin() */
    pfPWinConfigFunc(pw, OpenPipeWin);
    /* Open and configure the graphics window. */

    This sets up an optional callback to open a graphics library window for custom initialization, in this case, OpenPipeWin(). In the simple.c example, no window configuration callback was used.

  10. Specify the frame rate and the synchronization method.


    Because neither a frame rate nor a synchronization method is specified in simple.c, the application “free runs” without frame-rate control, which is the default. See “Frame Rate and Synchronization” and Chapter 10, “Controlling Frame Rate” for more information on controlling frame rates.

  11. Create a channel on the specified pipe.

    chan = pfNewChan(p);

    A channel is a viewport into a pipe. Because simple.c doesn't configure any screen dimensions for the channel, it renders to the full window of the pipe.

  12. Configure the channel: set the viewpoint, field of view (FOV), and near and far clipping planes (based on the size of the scene).

    pfChanScene(chan, scene);
    pfChanFOV(chan, 45.0f, 0.0f);
    pfGetNodeBSphere (root, &bsphere);
    pfChanNearFar(chan, 1.0f, 10.0f * bsphere.radius);

    When you pass in zero as a field of view—in this case, the vertical FOV—OpenGL Performer matches the FOV to the aspect ratio of the channel.

  13. Render the scene repeatedly until the specified time has elapsed.

    • Set up the viewpoint for the next frame:

      pfChanView(chan,, view.hpr);

    • Initiate the next CULL/DRAW cycle to render the frame:


  14. When the time limit is reached, exit OpenGL Performer.


The remainder of this appendix discusses portions of the outline in detail. You may find it helpful to continue to refer to simple.c while you read the following sections.

Setting Up the Basic Elements

This section describes how to set up the basic requirements of an OpenGL Performer libpf application.

Using OpenGL Performer Header Files

The header files for the OpenGL Performer libraries are in the /usr/include/Performer directory (for IRIX and Linux systems) and %PFROOT%\Include\Performer (for Windows systems). They include pf.h and pr.h (header files for libpf and libpr, respectively), and opengl.h and other header files for use with the other OpenGL Performer libraries.

The header files contain useful macros as well as function declarations, including macros for transparently casting a variable from one data type to another. (ANSI C requires that expressions used as function arguments be cast to match function prototypes.) Some routines therefore accept more than one type of argument, with automatic casting between usable types. For example, a routine accepting a pfGroup as an argument can also take a pfSwitch. In the code below, switch is automatically cast to a pfGroup* and geode is automatically cast to a pfNode* by a macro within pf.h:

pfGeode *geode;
pfSwitch *switch;
pfAddChild(switch, geode);

Initializing and Configuring OpenGL Performer

Before you can set up a pipe, you have to set up any areas of shared memory that you intend to use, and you have to determine how many processors to use (and in what configuration).

Initializing Shared Memory

OpenGL Performer uses shared memory to share data among the application, the visibility cull traversal, and the draw traversal, all of which can run in parallel on different processors. pfInit() sets up the shared memory arena from which libpf objects are allocated. The shared memory arena uses either swap space or a file in the directory specified by the environment variable PFTMPDIR. For more information on shared memory arenas, see Chapter 5, “Frame and Load Control ,” in the OpenGL Performer Programmer's Guide.

Initializing Processes

pfConfig() starts up multiple processes, which allow visibility culling and drawing to run in parallel with the application process. The number of processes created depends on the process model (specified by a call to pfMultiprocess()), the number of processors, and the number of pipes (one by default; call pfMultipipe() to specify more than one pipe). The order of the calls is important—pfMultiprocess() and pfMultipipe() are effective only if called between pfInit() and pfConfig().

The default is a single pipe running with one, two (separate draw process), or three (separate cull and draw processes) processes, depending on the number of processors on the machine. When you run the application from the root login account, pfConfig() also sets nondegradable priorities for the processes to improve the consistency of the run-time behavior.

For information on controlling multiple pipes, see “Multiple Pipes” in Chapter 5. For information on multiprocessing, see Chapter 5, Frame and Load Control,” and Chapter 20, “Programming with C++ ,” in the OpenGL Performer Programmer's Guide.

In addition to setting up shared memory, pfInit() initializes a high-resolution clock by calling pfInitClock(). Depending on the hardware, this may start up a process to service the clock. The clock process consumes few system resources because it sleeps most of the time.

Setti ng Up a Pipe

A pfPipe variable (also called a pipe) represents an OpenGL Performer software graphics pipeline. You gain access to a pipe using pfGetPipe(), for example,

p = pfGetPipe(0);

This statement sets p to point to the OpenGL Performer graphics pipeline numbered zero. The optional function pfStageConfigFunc() function for the PFPROC_DRAW stage sets up a callback initializes the drawing process for the pfPipe. pfWinConfigFunc() sets up a callback to do custom window initialization, as shown in Example A-2Example A-2.

Example A-2. pfStageConfigFunc() Callback

/* Set up pipe config func. */
pfStageConfigFunc(0, PFPROC_DRAW, ConfigPipe);
/* Set up window config func. */
pfPWinConfigFunc(pw, OpenPipeline);
/* Trigger pipe stage config func in draw process. */
pfConfigStage(0, PFPROC_DRAW);
/* Trigger window config func in draw process. */

The first argument to pfStageConfigFunc() is the pipe number and (-1) will select all pipes. The next argument is the stage or process in which you select the DRAW. The third argument is the configuration function.You perform custom window initialization in the Window configuration function, OpenPipeline(). If there is a custom window configuration function, it must open the window with pfOpenWin() as shown in Example A-3.

Example A-3. Sample OpenPipeline() Routine

void OpenPipeline(pfPipe *p)
    /* Open the window. */
    /* initialize custom graphics state */

Whether or not you specify a function with pfConfigPWin(), a custom window (as opposed to the default full screen window OpenGL Performer will otherwise create and open for you automatically) is not opened until a call to pfOpenWin() is made.

The call to pfInitGfx() sets up the initial graphics library state for OpenGL Performer and is called automatically by pfOpenWin(). You can also call this to re-initialize your window. The graphics library maintains state information for graphics hardware and software settings. These settings, or modes, determine how graphics are processed and rendered. Because OpenGL Performer takes over the processing and rendering duties of the system, the system must be set to a known state before it can reliably proceed.

OpenGL Performer maintains its own representation of the global graphics state. Therefore, changes that you make to the graphics state using graphics library commands can create inconsistencies. OpenGL Performer provides state management routines that let you manipulate both the graphics library state and the OpenGL Performer state. When you want to change graphics states, use these routines rather than their graphics library counterparts.

Frame Rate and Synchronization

The frame rate is the number of times per second the application intends to draw the scene. The period for a frame must be an integer multiple of the video vertical retrace period, which is typically 1/60th of a second. Thus, with a 60 Hz video rate, possible frame rates are 60 Hz, 30 Hz, 20 Hz, 15 Hz, and so on. simple.c does not specify a frame rate, so it attempts to free run at the default rate of 60 Hz.

The synchronization mode or phase defines how the system behaves if drawing takes more than the requested time. Free-running mode (the default) is useful for applications that do not require a fixed frame rate. pfSync() delays the application until the next appropriate frame boundary.

See Chapter 10, “Controlling Frame Rate”, to learn more about frame rates, phase, and synchronization modes.

Setting Up a Channel

A channel is a rendering viewport into a pipe. A pipe can have many channels within it, but by default a channel occupies the full window of a pipe. You can tell the channel to use a portion of the window using pfChanViewport():

pfChanViewport(chan, left, right, bottom, top);

Channels support the standard viewing concepts such as eyepoint, view direction, field of view, and near and far clipping planes.

For displays using multiple adjacent screens, you can slave channels together to a single viewpoint. You can also use channels to control scene management functions, such as the switching of level of detail models based on graphics stress and pixel size.

See Chapter 5, “Creating a Display with pfChannel” to learn more about setting up channels.

Creating and Loading a Scene Graph

Databases exist in a variety of formats. OpenGL Performer does not define a file format for databases; instead, it supports extensible run-time scene definitions of sufficient generality to support many database formats. Source code for several file importers is included with OpenGL Performer; the provided importers are described in Chapter 7, “Importing Databases” , in the OpenGL Performer Programmer's Guide.

Creating a Database

You can create a database with any modeler, or write your own modeler using libpr routines. If you use a modeler that has its own database format, you can develop a file importer for it by modifying one of the sample importers. See Chapter 7, “Importing Databases” , in the OpenGL Performer Programmer's Guide for more information about import routines.

If you write your own modeler using libpr routines, you do not have to convert the data structures for libpf to be able to use them. In this case, you create a database by using a series of calls to construct geometry in pfGeoSets or pfGeoArrays, by defining state and texture definitions in pfGeoStates, and by constructing a scene graph of pfNodes.

Setting the Search Path for Database Files

Database files are often scattered about a file system, making file-loading operations tedious. OpenGL Performer provides a general mechanism for defining multiple search paths.

When OpenGL Performer attempts to open a file, it first tries the name as specified. If that fails, it begins to search for the file using a search path, which specifies where to look for data.

You can specify a search path using pfFilePath(path), pfFilePathv(path0, path1, ..., pathn, NULL), or with the environment variable PFPATH. You can specify any number of directories using pfFilePath() and a maximum of 64 using pfFilePathv(). Colons separate path names on IRIX and Linux and semicolons on Windows. Since pfFilePathv() allows you to specify path names delimited by commas, it provides much more economy in coding compared to the use of pfFilePath(), where you must employ conditional code to accomodate cross-platform use. Directories are searched in the order given, beginning with those specified in PFPATH, followed by those specified by pfFilePath() or pfFilePathv().

For example, the following function call tells OpenGL Performer to search for data first in the current directory, then in the data directory within the current directory, and then in data directories one and two levels above the current directory, and then in the installed OpenGL Performer data directory:


If you call pfFindFile() with the name of the file you want to locate, the complete pathname of the file is returned if the result of the search is successful.

Simulation Loop

After the pipes and channels are configured and the scene is loaded, the main simulation loop begins and manages scene updates, viewpoint updates, scene intersection inquiries, and image generation.

The loop has two principal control calls: pfSync() and pfFrame().

The order of operations is this:

  1. Call pfSync() to put the process to sleep until the next frame boundary. This step is typically used only when viewpoint information is being updated from a streaming input device, such as a head-tracker.

  2. Perform latency-critical operations such as setting the viewpoint or reading positional input/output devices.

  3. Call pfFrame() to initiate the next cull traversal.

  4. Perform any time-consuming calculations that are required.

  5. Return to step 1.

Time-consuming operations such as intersection inquiries and simulator dynamics computations that are performed in the main simulation loop should go after pfFrame(), but before pfSync(). If these calculations are done after pfSync() but before pfFrame(), the calculations can delay the start of the cull process, and thereby reduce the time available for the cull traversal on multiprocessor systems.


This appendix does not specifically discuss performance tuning (see Chapter 15, “Optimizing Performance”,), but every OpenGL Performer-based application should be written with performance in mind. You cannot easily build speed into an application as a last-minute addendum. During the design of your program (rather than after debugging it), you need to consider speed as you structure your database, as you decide what needs to happen in your main loop, and so on.

Compiling and Linking OpenGL Performer Applications

This section describes how to compile and link OpenGL Performer applications.

Required Libraries

The following libraries are required when linking an executable on the IRIX and Linux operating systems:


OpenGL Performer visual simulation development library.


OpenGL Performer high-performance rendering library—exists in OpenGL and is contained within the corresponding libpf.


OpenGL Performer database library— does file handling, and includes importers for a variety of data formats.


OpenGL Performer utilities library— includes the window-related functions.


Image library—required by libpr.


OpenGL utilities library—required by libpr with OpenGL.


OpenGL graphics library—is required by libpf and libpr.


X extensions—for video control.


 X extensions—needed by Silicon Graphics X extensions libraries.


OpenGL widget library, for using OpenGL with IRIS IM.


IRIS IM library; used for “Silicon Graphics look” windows.


X toolkit intrinsics library—used by IRIS IM for Motif.


 X utility library required by libpr.


X Window System³ library—required by libgl and libpr.


Math library—required by libpr.


Floating point exception library—required by libpr.


C++ library—required by libpf.

The corresponding line for an OpenGL application would be the following:

-lpfdu -lpfui -lpfutil -lpf -limage -lGLU -lGL -lXext -lXsgivc -lXmu -lX11 -lm -lfpe -lC

The following libraries are required when linking an executable on the Windows operating systems:









Multithreaded Optimized C library

The corresponding line for an OpenGL Performer application would be the following:

libpfdu-util.lib libpfui.lib libpf.lib glu32.lib opengl32.lib gdi32.lib user32.lib /NODEFAULTLIB:LIBC /NODEFAULTLIB:MSVCRT /NODEFAULTLIB:MSVCPRT msvcprt.lib msvcrt.lib

Dynamic Shared Objects (DSOs)

The standard libraries for OpenGL Performer are distributed as dynamic shared objects (DSOs). Compared with static libraries, DSOs produce smaller applications and allow sharing between multiple executables that are running simultaneously. However, if you build an application using a DSO, that DSO must be present on the target system at run time. The DSOs for OpenGL Performer 3.0 are in the performer_eoe subsystem on the OpenGL Performer CD-ROM. OpenGL Performer 3.0 on IRIX also includes DSOs from all previous versions in compatibility subsystems, so that old OpenGL Performer programs will still run on a system with OpenGL Performer 3.0 installed.

Note: On Windows systems, a dynamic link library (DLL) is the functional equivalent of a DSO.

Debug and Static Libraries

OpenGL Performer is also shipped with libraries in different forms that might be useful to developers. The debug versions are primarily intended for bug reporting, because they contain more symbol table information than the optimized versions. The static versions are for use when distributing an application to customers who may not have performer_eoe installed. If you want to ensure that your customers will have all the libraries they need, you should use static linking.

Debug DSO, static optimized and static debug versions of the libraries can be found in optional subsystems and are installed under the directories /usr/lib/Performer/Debug,/usr/lib/Performer/Static, and /usr/lib/Performer/StaticDebug, respectively. The “-L” option to cc, CC or ld can be used to link with the static libraries. Use of the standard DSO or debug DSO is determined at run time through the environment variable LD_LIBRARY_PATH.

Note: See the OpenGL Performer Programmer's Guide  for information concerning file readers, which are normally accessed as DSOs at run time even when the main OpenGL Performer libraries have been statically linked. Also, when linking statically, you will have to be sure that you have all required libraries on your link line.

Static libraries are not available for Windows systems.

Using Compiler Flags

Note: This section is not applicable to Windows systems.

Much of the sample code in this guide, many of the sample applications, and most of the database-importing code are written in ANSI C. They should be compiled using the –ansi flag to the C compiler.

Using –cckr instead of –ansi affects OpenGL Performer in the following ways:

  1. Because –cckr does not support floating point constants denoted with the f suffix, all constants defined with #define are double-precision. The promotion of floating point expressions to double-precision can decrease performance for some numerically intensive applications.

  2. Because –cckr does not allow a macro to have the same name as a routine, the type-casting macros in pf.h are not available. Thus, when you pass a pointer to a derived type such as pfGroup or pfGeode to a routine that takes a generic type such as a pfNode, that argument must be cast to a pfNode explicitly, as shown in the following example:

    pfGeode *geode;
    pfSwitch *switch;
    pfAddChild((pfGroup *)switch, (pfNode *)geode);

MIPS-3, MIPS-4, and 64-Bit Compilation

If you are running version 6.2 or later of IRIX, you can compile and execute OpenGL Performer applications in 64-bit mode.

To do this, you need to have installed the optional 64-bit versions of the OpenGL Performer libraries. All that is required then is to use the “-64” switch to the compiler. This selects the compilation mode and causes libraries to be searched for in /usr/lib64 instead of /usr/lib.

The 64-bit version of OpenGL Performer is itself created using -mips3, so that you can compile an application using either the MIPS-3 or MIPS-4 instruction set. MIPS-3 executables can run on R4400-based machines such as Onyx and Indigo2 as well as on R8000-based machines such as Power Onyx and PowerIndigo2. MIPS-4 executables can only be run on R8000-based (and subsequent) machines.

Under IRIX 6.2 and later, if you want to use the extended MIPS-3 or MIPS-4 instruction set in a 32-bit application, install the optional “new 32-bit” (N32) version of OpenGL Performer and use the “-n32” option to the compiler. The old 32-bit, new 32-bit and 64-bit versions of OpenGL Performer can all be installed at the same time as each is installed in a separate directory, /usr/lib,/usr/lib32 and /usr/lib64, respectively.

The sample Makefiles in the source code distribution recognize the environment variable PFSTYLE and values of 32 for o32, N32 for n32, and 64 for 64-bit compilation.

Using OpenGL Performer From C++

OpenGL Performer provides C++ bindings for all functions as well as C bindings. Most of this guide does not include code examples in C++; however, all sample programs are provided in the OpenGL Performer distribution in both C and C++ versions. The structure of a C++ program is largely identical to that of a C program; for examples of OpenGL Performer programs using the C++ API, see the following directories for examples of C and C++ programs:

(IRIX and Linux)