Chapter 3. OpenGL Performer Programming Interface
Prev | Part II. Programming with OpenGL Performer | Next

## Chapter 3. OpenGL Performer Programming Interface

This chapter describes the fundamental ideas behind the OpenGL Performer programming interface in the following sections:

- “General Naming Conventions”

- “Class API”

- “Base Clas ses”

## General Naming Conventions

The OpenGL Performer API uses naming conventions to help you understand what a given command will do and even predict the appropriate names of routines for desired functionality. Following similar naming practices in the software that you develop will make it easier for you and others on your team to understand and debug your code.

The API is largely object-oriented; it contains classes of objects comprised of methods that do the following:

- Configure their parent objects.

- Apply associated operations, based on the current configuration of the object.

Both C and C++ bindings are provided for OpenGL Performer. In addition, naming conventions provide a consistent and predictable API and indicate the kind of operations performed by a given command.

### Prefixes

The prefix of the command tells you in which library a C command or C++ class is found. All exposed OpenGL Performer C commands and C++ classes begin with "pf". The utility libraries use an additional prefix letter, such as "pfu" for the libpfutil general utility library, "pfi" for the libpfui input handling library, and "pfd" for the libpfdu database utility library. Libpr level commands still have the `pf' prefix as they are still in the main libpf library.

### Header Files

Each OpenGL Performer library contains a main header file in `/usr/include/Performer` that contains type and class definitions, the C API for that library, and global routines that are part of the C and C++ API. `Libpf` is broken into two distinct pieces: the low-level rendering layer, `libpr`, and the application layer, `libpf,` and each has their own main header file: `pr.h` and `pf.h`. Because `libpf` is considered to include `libpr`, `pf.h` includes `pr.h`. C++ class header files are found under `/usr/include/Performer`/{pf, pr, ...} on IRIX and Linux systems. On Windows, the header files are under `%PFROOT%/Include/Performer`. Each class has its own C++ header file and that header must be included to use that class.

    #include <Performer/pf.h>
    #include <Performer/pf/pfGroup.h>
    .....
    pfGroup *group;

### Naming in C and C++

All C++ class method names have an expanded C counterpart. Typically, the C routine will include the class name in the routine, whereas the C++ method will not.

    C: pfGetPipeScreen();
    C++: pipe->getScreen();

For some very general routines on the most abstract classes, the class name is omitted. This is the case with the child API on pfNodes:

    C: pfAddChild(node,child);
    C++: node->addChild(child);

Command and type names are mixed case; the first letter of a new word in a name is capitalized. C++ method names always start with a lowercase letter.

    pfTexture *texture;
    texture->loadFile();

### Abbreviations

Type names do not use abbreviations. The C API acting on that type will often use abbreviations for the type names, as does the associated tokens and enums.

In procedure names, a name is always abbreviated or never abbreviated, and the same abbreviation is always be used and is in the pfNew* C command. For example, the pfTexture object uses "Tex" in its API, such as pfNewTex(). If a type name has multiple words, the abbreviation uses the first letter of the first words and then the first syllable of the last word.

    pfPipeWindow *pwin = pfNewPWin();
    pfPipeVideoChannel *pvchan = pfNewPVChan();
    pfTexLOD *tlod = pfNewTLOD();

### Macros, Tokens, and Enums

Macros, tokens, and enums all use full uppercase. Token names associated with a class and methods of a class start with the abbreviated name for that class, such as texture to “tex” in PFTEX_SHARPEN.

## Class API

The API of a given class, such as pfTexture, is comprised of:

- API to create an instance of the object

- API to set parameters on the object

- API to get those parameter settings

- API to perform actions on the configured object

### O bject Creation

Objects are always created with:

    C: pfThing *thing = pfNewThing();
    C++: pfThing *thing = new pfThing;

Libpf objects are automatically created out of the shared memory arena. Libpr objects take as an argument an arena pointer that, if NULL, causes allocation off the heap.

### Set Routines

A set routine has the form:

    C: pfThingParam(thing, ... ) (note no `Set' in the name)
    C++: thing->setParam()

Set routines are usually very fast and are not order dependent. Work required to process the settings happens once when the object is first used after settings have changed. If particularly expensive options must be done, there will be a pfConfigThing routine or method to explicitly force this work that must be called before the object is to be used.

### Get Routines

For every "set" there is a matching "get" routine to get back the value that was set.

    C: pfGetThingParam(thing, ... )
    C++: thing->getParam()

If the set/get is for a single value, that value is usually the return value of the routine. If there are multiple values together, the "get" routine will use pointers to result variables.

#### Getting Current In-Use Values

Get routines return values that have been previously set by the user, or default values if no settings have been made. Sometimes a value other than the user-specified value is currently in use and that is the value that you would like to get. For these cases, there is a separate "getCur" routine to get the current value in use.

    C:   pfGetCurThingParam()
    C++: thing->getcurParam()

These "cur" routines may only be able to give reasonable values in the process in which associated operations are happening. For example, to get the current texture (pfGetCurTex()), you need to be in the DRAW process, because that is the only process that has a current texture.

### Action Routines

An action routine has the following form:

    C: pfVerbThing(), such as pfApplyTex()
    C++: thing->verb(), such as tex->apply()

Action routines can have parameter scope and apply only to that parameter. These routines have the following forms:

    C: pfVerbThingParam(), such as pfApplyTexMinLOD()
    C++: thing->verbParam(), such as tex->applyMinLOD()

The APPLY and DRAW action routines do graphics operations and so must happen either in the DRAW process or in display list mode.

    C: pfApplyGState()
    pfDrawGSet()
    C++: gstate->apply()
    gset->draw()

### Enable and Disable of Modes

You can enable land disable with pfEnable() and pfDisable(), respectively.

pfEnable() and pfDisable() take PFEN_* tokens, naming the graphics state operation to enable or disable. pfGetEnable() is used to query enable status, and will return 1 or 0 if the given mode is enabled or disabled, respectively.

    ex: pfEnable(PFEN_TEXTURE), pfDisable(PFEN_TEXTURE),
    pfGetEnable(PFEN_TEXTURE);

### Mode, Attribute, or Value

Class instances are configured by having their internal fields set. These fields may be simple modes or complex attribute structures. Mode values are ints or tokens, attributes are typically pointers to objects, and values are floats.

    pfGStateMode(gstate, PFSTATE_DECAL, PFDECAL_LAYER)
    pfGStateAttr(gstate, PFSTATE_TEXTURE, texPtr)
    pfGStateVal(gstate, PFSTATE_ALPHAREF, 0.5)

## Base Clas ses

OpenGL Performer provides an object-oriented programming interface to most of its data structures. Only OpenGL Performer functions can change the values of elements of these data structures; for example, you must call pfMtlColor() to set the color of a pfMaterial structure rather than modifying the structure directly.

For a more transparent type of memory, OpenGL Performer provides pfMemory. All object classes are derived from pfMemory. pfMemory instances must be explicitly allocated with the new operator and cannot be allocated statically, on the stack, or included directly in other object definitions. pfMemory is managed memory; it includes special fields, such as size, arena, and ref count, that are initialized by the pfMemory new() function.

Some very simple and unmanaged data types are not encapsulated for speed and easy access. Examples include pfMatrix, pfSphere and pfVec3. These data types are referred to as public structures and are inherited from pfStruct.

Unlike pfMemory, pfStructs can be:

- Allocated statically.

- Allocated on the stack.

- Included directly in other structure and object definitions.

pfStructs allocated off the stack or allocated statically are not in the shared memory arena and thus are not safe for multiprocessed use. Also, pfStructs allocated off the stack in a procedure do not exist after the procedure exits so they should not be given to persistent objects, such as a pfVec3 array of vertices for a pfGeoSet.

To allow some functions to apply to multiple data types, OpenGL Performer uses the concept of class inheritance. Class inheritance takes advantage of the fact that different data types (classes) often share attributes. For example, a pfGroup is a node that can have children. A pfDCS (Dynamic Coordinate System) has the same basic structure as a pfGroup, but also defines a transformation to apply to its children—in other words, the pfDCS data type inherits the attributes of the pfGroup and adds new attributes of its own. This means that all functions that accept a pfGroup* argument will alternatively accept a pfDCS* argument.

For example, pfAddChild() takes a pfGroup* argument, but the following appends child to the list of children belonging to dcs.

    pfDCS *dcs = pfNewDCS();
    pfAddChild(dcs, child);

Because the C language does not directly express the notion of classes and inheritance, arguments to functions must be cast before being passed, for example:

    pfAddChild((pfGroup*)dcs, (pfNode*)child);

In the example above, no such casting is required because OpenGL Performer provides macros that perform the casting when compiling with ANSI C, for example:

    #define pfAddChild(g, c) pfAddchild((pfGroup*)g, (pfNode*)c)

| Note: Using automatic casting eliminates type checking—the macros cast anything to the desired type. If you make a mistake and pass an unintended data type to a casting macro, the results may be unexpected.

No such trickery is required when using the C++ API. Full type checking is always available at compile time.

### Inheritance Graph

The relations between classes can be arranged in a directed acyclic inheritance graph in which each child inherits all of its parent's attributes, as illustrated in Figure 3-1. OpenGL Performer does not use multiple inheritance, so each class has only one parent in the graph.

| Note: It is important to remember that an inheritance graph is different from a scene graph. The inheritance graph shows the inheritance of data elements and member functions among user-defined data types; the scene graph shows the relationship among instances of nodes in a hierarchical scene definition.

Figure 3-1. Partial Inheritance Graph of OpenGL Performer Data Types

OpenGL Performer objects are divided into two groups: those found in the `libpf` library and those found in the `libpr` library. These two groups of objects have some common attributes, but also differ in some respects.

While OpenGL Performer only uses single inheritance, some objects encapsulate others, hiding the encapsulated object but also providing a functional interface that mimics its original one. For example, a pfChannel has a pfFrustum, a pfFrameStats has a pfStats, a pfPipeWindow has a pfWindow, and a pfPipeVideoChannel has a pfVideoChannel. In these cases, the first object in each pair provides functions corresponding to those of the second. For example, pfFrustum has the following routine:

    pfMakeSimpleFrust(frust, 45.0f);

and pfChannel has a corresponding routine:

    pfMakeSimpleChan(channel, 45.0f);

### `Libpr` and `Libpf` Objects

All of the major classes in OpenGL Performer are derived from the pfObject class. This common, base class unifies the data types by providing common attributes and functions. `Libpf` objects are further derived from pfUpdatable. The pfUpdatable abstract class provides support for automatic multibuffering for multiprocessing. pfObjects have no special support for multiprocessing and so all processes share the same copy of the pfObject in the shared arena. libpr objects allocated from the heap are only visible in the process in which they are created or in child processes created after the object. Changes made to such an object in one process are not visible in any other process.

Explicit multibuffering of pfObjects is available through the pfFlux class. In general, `libpr` provides lightweight and low-level modular pieces of functionality that are then enhanced by more powerful `libpf` objects.

### User Data

The primary attribute defined by the pfObject class is the custom data a user can define on any pfObject called “ user data.” pfUserData attaches the user-supplied data pointer to the user data attribute. pfUserDataSlot attaches the user supplied data pointer to the given user data slot. Example 3-1 shows how to use user data.

Example 3-1. How to Use User Data

    typedef struct
        {
          float coeffFriction;
          float density;
          float *dataPoints;
        }
        myMaterial;

        myMaterial     *granite;

        granite = (myMaterial *)pfMalloc(sizeof(myMaterial), NULL);
        granite->coeffFriction = 0.5f;
        granite->density = 3.0f;
        granite->dataPoints = (float *)pfMalloc(sizeof(float)*8, NULL);
        graniteMtl = pfNewMtl(NULL);

        pfUserData(graniteMtl, granite);

### pfDelete() and Reference Counting

You can place types of data objects in OpenGL Performer can be placed in a hierarchical scene graph, using instancing (see OpenGL Performer Programmer's Guide) when an object is referenced multiple times. Scene graphs can become quite complex, which can cause problems if you are not careful. Deleting objects can be a particularly dangerous operation, for example, if you delete an object that another object still references.

R eference counting provides a bookkeeping mechanism that makes object deletion safe: an object is never deleted if its reference count is greater than zero.

All `libpr` objects (such as pfGeoState and pfMaterial) have a reference count that specifies how many other objects refer to it. A reference is made whenever an object is attached to another using the OpenGL Performer routines shown in Table 3-1.

Table 3-1. Routines that Modify `libpr` Object Reference Counts

Routine | Action
--- | ---
pfGSetGState () | Attaches a pfGeoState to a pfGeoSet
pfGStateAttr () | Attaches a state structure (such as a pfMaterial) to a pfGeoState
pfGSetHlight () | Attaches a pfHighlight to a pfGeoSet
pfTexDetail () | Attaches a detail pfTexture to a base pfTexture
pfGSetAttr () | Attaches attribute and index arrays to a pfGeoSet
pfTexImage () | Attaches an image array to a pfTexture
pfAddGSet (), pfReplaceGSet (), pfInsertGSet () | Modifies pfGeoSet/pfGeode association

When object A is attached to object B, the reference count of A is incremented. Additionally, if A replaces a previously referenced object C, then the reference count of C is decremented. Example 3-2 demonstrates how reference counts are incremented and decremented.

Example 3-2. Objects and Reference Counts

    pfGeoState *gstateA, *gstateC;
    pfGeoSet *gsetB;

    /* Attach gstateC to gsetB. Reference count of gstateC
     * is incremented. */
    pfGSetGState(gsetB, gstateC);

    /* Attach gstateA to gsetB, replacing gstateC. Reference
     * count of gstateC is decremented and that of gstateA
     * is incremented. */
    pfGSetGState(gsetB, gstateA);

This automatic reference counting by OpenGL Performer routines would normally be sufficient for your needs. However, the routines pfRef(), pfUnref(), and pfGetRef() allow you to increment, decrement, and retrieve the reference count of a `libpr` object if you want to do so. (These routines also work with objects allocated by pfMalloc(); see the OpenGL Performer Programmer's Guide for more information).

You can delete an object whose reference count is equal to with pfDelete(). pfDelete() works for all `libpr` objects and all pfNodes but not for other `libpf` objects like pfPipe and pfChannel. pfDelete() first checks the reference count of an object. If the reference count is non-positive, pfDelete() decrements the reference count of all objects that the current object references, and then it deletes the current object. pfDelete() does not stop here but continues down all reference chains, deleting objects until it finds one whose count is greater than zero. Once all reference chains have been explored, pfDelete returns a boolean indicating whether it successfully deleted the first object or not. Example 3-3 illustrates the use of pfDelete() with `libpr`.

Example 3-3. Using pfDelete() with `libpr` Objects

    pfGeoState *gstate0, *gstate1;
    pfMaterial *mtl;
    pfGeoSet *gset;

    gstate0 = pfNewGState(arena); /* initial ref count is 0 */
    gset = pfNewGSet(arena); /* initial ref count is 0 */
    mtl = pfNewMtl(arena); /* initial ref count is 0 */

    /* Attach mtl to gstate0. Reference count of mtl is
     * incremented. */
    pfGStateAttr(gstate0, PFSTATE_FRONTMTL, mtl);

    /* Attach mtl to gstate1. Reference count of mtl is
     * incremented. */
    pfGStateAttr(gstate1, PFSTATE_FRONTMTL, mtl);

    /* Attach gstate0 to gset. Reference count of gstate0 is
     * incremented. */
    pfGSetGState(gset, gstate0);

    /* This deletes gset, gstate0, but not mtl since gstate1 is
     * still referencing it. */
    pfDelete(gset);

Example 3-4 illustrates the use of pfDelete() with `libpf`.

Example 3-4. Using pfDelete() with `libpf` Objects

    pfGroup *group;
    pfGeode *geode;
    pfGeoSet *gset;

    group = pfNewGroup(); /* initial parent count is 0 */
    geode = pfNewGeode(); /* initial parent count is 0 */
    gset = pfNewGSet(arena); /* initial ref count is 0 */

    /* Attach geode to group. Parent count of geode is
     * incremented. */
    pfAddChild(group, geode);

    /* Attach gset to geode. Reference count of gset is
     * incremented. */
    pfAddGSet(geode, gset);

    /* This has no effect since the parent count of geode is 1.*/
    pfDelete(geode);

    /* This deletes group, geode, and gset */
    pfDelete(group);

Some notes about reference counting and pfDelete():

- All reference count modifications are locked, so they guarantee mutual exclusion when multiprocessing.

- The counts of objects added to a pfDispList are not incremented due to performance considerations.

- In the multiprocessing environment of `libpf`, the successful deletion of a pfNode does not have immediate effect but is delayed one or more frames until all processes in all processing pipelines are done with the node. This accounts for the fact that pfDispLists do not reference-count their objects.

- pfUnrefDelete(obj) is shorthand for:

      if(pfUnref(obj) ==0)
          pfDelete(obj);

  This is true when pfUnrefGetRef is atomic.

- An object whose count reaches zero is not automatically deleted by OpenGL Performer. You must specifically request that an object be deleted with pfDelete() or pfUnrefDelete().

### Copying Objects with pfCopy()

pfCopy() is currently implemented for `libpr` (and pfMalloc()) objects only. Object references are copied and reference counts are modified appropriately, as illustrated in Example 3-5.

Example 3-5. Using pfCopy()

    pfGeoState *gstate0, *gstate1;
    pfMaterial *mtlA, *mtlB;

    gstate0 = pfNewGState(arena);
    gstate1 = pfNewGState(arena);
    mtlA = pfNewMtl(arena); /* initial ref count is 0 */
    mtlB = pfNewMtl(arena); /* initial ref count is 0 */

    /* Attach mtlA to gstate0. Reference count of mtlA is
     * incremented. */
    pfGStateAttr(gstate0, PFSTATE_FRONTMTL, mtlA);

    /* Attach mtlB to gstate1. Reference count of mtlB is
     * incremented. */
    pfGStateAttr(gstate1, PFSTATE_FRONTMTL, mtlB);

    /* gstate1 = gstate0. The reference counts of mtlA and mtlB
     * are 2 and 0 respectively. Note that mtlB is NOT deleted
     * even though its reference count is 0. */
    pfCopy(gstate1, gstate0);

The routine pfMalloc() and the related routines provide a consistent method to allocate memory, either from the user's heap (using the C library malloc() function) or from a shared memory arena (using the IRIX malloc() function).

### Printing Objec ts with pfPrint()

pfPrint() can print many different kinds of objects to a fill. For example, you can print nodes and GeoSets. To do so, you specify in the argument of the function the object to print, the level of verbosity, and the destination file. An additional argument, which, specifies different data according to the type of object being printed.

The different levels of verbosity include:

- PFPRINT_VB_OFF—no printing.

- PFPRINT_VB_ON—minimal printing (default).

- PFPRINT_VB_NOTICE—minimal printing (default).

- PFPRINT_VB_INFO—considerable printing.

- PFPRINT_VB_DEBUG—exhaustive printing.

If the object to print is a type of pfNode, which specifies whether the print traversal should only traverse the current node (PFTRAV_SELF) or the entire scene graph where the node specified in the argument is the root node (PFTRAV_SELF | PFTRAV_DESCEND). For example, to print an entire scene graph, in which scene is the root node, to the file fp, with default verbosity, use the following line of code.

    file = fopen (“scene.out”,”w”);
    pfPrint(scene, PFTRAV_SELF | PFTRAV_DESCEND, PFPRINT_VB_ON, fp); fclose(file);

If the object to print is a pfFrameStats, which should specify a bitmask of the frame statistics classes that you want printed. The values for the bitmask include:

- PFSTATS_ON enables the specified classes.

- PFSTATS_OFF disables the specified classes.

- PFSTATS_DEFAULT sets the specified classes to their default values.

- PFSTATS_SET sets the class enable mask to enmask.

For example, to print select classes of a pfFrameStats structure, stats, to stderr, use the following line of code.

pfPrint(stats, PFSTATS_ENGFX | PFFSTATS_ENDB | PFFSTATS_ENCULL, PFSTATS_ON, NULL);

If the object to print is a pfGeoSet, which is ignored and information about that pfGeoSet is printed according to the verbosity indicator. The output contains the types, names, and bounding volumes of the nodes and pfGeoSets in the hierarchy. For example, to print the contents of a pfGeoSet, gset, to stderr, use the following line of code:

    pfPrint(gset, NULL, PFPRINT_VB_DEBUG, NULL);

| Note: When the last argument, file, is set to NULL, the object is printed to stderr.

### Determining Ob ject Type

Sometimes you have a pointer to a pfObject but you do not know what it really is—is it a pfGeoSet, a pfChannel, or something else? pfGetType() returns a pfType that specifies the type of a pfObject. You can use pfType to determine the class ancestry of the object. Another set of routines, one for each class, returns the pfType corresponding to that class, For example, pfGetGroupClassType() returns the pfType corresponding to pfGroup.

pfIsOfType() tells whether an object is derived from a specified type, as opposed to being the exact type.

With these functions you can test for class type as shown in Example 3-6.

Example 3-6. General-Purpose Scene Graph Traverser

    void
    travGraph(pfNode *node)
    {
        if (pfIsOfType(node, pfGetDCSClassType()))
            doSomethingTransforming(node);

        /* If 'node' is derived from pfGroup then recursively
         * traverse its children */
        if (pfIsOfType(node, pfGetGroupClassType()))
            for (i = 0; i < pfGetNumChildren(node); i++)
                travGraph(pfGetChild(node, i));
    }

Because OpenGL Performer allows subclassing of built-in types when decisions are made based on the type of an object, it is usually better to use pfIsOfType() to test the type of an object rather than to test for the strict equality of the pfTypes. Otherwise the code will not have reasonable default behavior with file loaders or applications that use subclassing.

The pfType returned from pfGetType() is useful for programs but is not in a readable form for you. Calling pfGetTypeName() on a pfType returns a null-terminated ASCII string that identifies an object's type. For a pfDCS, for example, pfGetTypeName() returns the string“pfDCS.” The type returned by pfGetType() can then be compared to a class type using pfIsOfType(). Class types can be returned by methods such as pfGetGroupClassType().

Prev | Table of Contents | Next
Part II. Programming with OpenGL Performer |  | Chapter 4. Introduction to OpenGL Performer Concepts