Chapter 8. Geometry

All libpr geometry is defined by modular units of prmitives that employ a flexible specification method. The following two classes allow you to group these basic primitives:

This chapter describes these two classes and the following two topics:

pfGeoSet (Geometry Set)

A pfGeoSet is a collection of geometry that shares certain characteristics. All items in a pfGeoSet must be of the same primitive type (whether they are points, lines, or triangles) and share the same set of attribute bindings (you cannot specify colors-per-vertex for some items and colors-per-primitive for others in the same pfGeoSet). A pfGeoSet forms primitives out of lists of attributes that may be either indexed or nonindexed. An indexed pfGeoSet uses a list of unsigned short integers to index an attribute list. (See “Attributes” for information about attributes and bindings.)

Indexing provides a more general mechanism for specifying geometry than hard-wired attribute lists and also has the potential for substantial memory savings as a result of shared attributes. Nonindexed pfGeoSets are sometimes easier to construct, usually a bit faster to render, and may save memory (since no extra space is needed for index lists) in situations where vertex sharing is not possible. A pfGeoSet must be either completely indexed or completely nonindexed; it is not valid to have some attributes indexed and others nonindexed.


Note: libpf applications can include pfGeoSets in the scene graph with the pfGeode (Geometry Node).

Table 8-1 lists a subset of the routines that manipulate pfGeoSets.

Table 8-1. pfGeoSet Routines

Routine

Description

pfNewGSet()

Create a new pfGeoSet.

pfDelete()

Delete a pfGeoSet.

pfCopy()

Copy a pfGeoSet.

pfGSetGState()

Specify the pfGeoState to be used.

pfGSetGStateIndex()

Specify the pfGeoState index to be used.

pfGSetNumPrims()

Specify the number of primitive items.

pfGSetPrimType()

Specify the type of primitive.

pfGSetPrimLengths()

Set the lengths array for strip primitives.

pfGetGSetPrimLength()

Get the length for the specified strip primitive.

pfGSetAttr()

Set the attribute bindings.

pfGSetMultiAttr()

Set multi-value attributes (for example, multi-texture coordinates).

pfGSetDrawMode()

Specify draw mode (for example, flat shading or wireframe).

pfGSetLineWidth()

Set the line width for line primitives.

pfGSetPntSize()

Set the point size for point primitives.

pfGSetHlight()

Specify highlighting type for drawing.

pfDrawGSet()

Draw a pfGeoSet.

pfGSetBBox()

Specify a bounding box for the geometry.

pfGSetIsectMask()

Specify an intersection mask for pfGSetIsectSegs().

pfGSetIsectSegs()

Intersect line segments with pfGeoSet geometry.

pfQueryGSet()

Determine the number of triangles or vertices.

pfPrint()

Print the pfGeoSet contents.


Primitive Types

All primitives within a given pfGeoSet must be of the same type. To set the type of all primitives in a pfGeoSet named gset, call pfGSetPrimType(gset, type). Table 8-2 lists the primitive type tokens, the primitive types that they represent, and the number of vertices in a coordinate list for that type of primitive.

Table 8-2. Geometry Primitives

Token

Primitive Type

Number of Vertices

PFGS_POINTS

Points

numPrims

PFGS_LINES

Independent line segments

2 * numPrims

PFGS_LINESTRIPS

Strips of connected lines

Sum of lengths array

PFGS_FLAT_LINESTRIPS

Strips of flat-shaded lines

Sum of lengths array

PFGS_TRIS

Independent triangles

3 * numPrims

PFGS_TRISTRIPS

Strips of connected triangles

Sum of lengths array

PFGS_FLAT_TRISTRIPS

Strips of flat-shaded triangles

Sum of lengths array

PFGS_TRIFANS

Fan of conected triangles

Sum of lengths array

PFGS_FLAT_TRIFANS

Fan of flat-shaded triangles

Sum of lengths array

PFGS_QUADS

Independent quadrilaterals

4 * numPrims

PFGS_POLYS

Independent polygons

Sum of lengths array

The parameters in the last column denote the following:

numPrims 

The number of primitive items in the pfGeoSet, as set by pfGSetNumPrims().

lengths 

The array of strip lengths in the pfGeoSet, as set by pfGSetPrimLengths() (note that length is measured here in terms of number of vertices).

Connected primitive types (line strips, triangle strips, and polygons) require a separate array that specifies the number of vertices in each primitive. Length is defined as the number of vertices in a strip for STRIP primitives and is the number of vertices in a polygon for the POLYS primitive type. The number of line segments in a line strip is numVerts – 1, while the number of triangles in a triangle strip and polygon is numVerts – 2. Use pfGSetPrimLengths() to set the length array for strip primitives.

The number of primitives in a pfGeoSet is specified by pfGSetNumPrims(gset, num). For strip and polygon primitives, num is the number of strips or polygons in gset.

pfGeoSet Draw Mode

In addition to the primitive type, pfGSetDrawMode() further defines how a primitive is drawn. Triangles, triangle strips, quadrilaterals, and polygons can be specified as either filled or as wireframe, where only the outline of the primitive is drawn. Use the PFGS_WIREFRAME argument to enable or disable wireframe mode. Another argument, PFGS_FLATSHADE, specifies that primitives should be shaded. If flat shading is enabled, each primitive or element in a strip is shaded with a single color.

PFGS_COMPILE_GL
 

At the next draw for each pfState, compile gset's geometry into a GL display list and subsequently render the display list.

PFGS_DRAW_GLOBJ
 

Select the rendering of an already created display list but do not force a recompile.

PFGS_PACKED_ATTRS
 

Use the gset's packed attribute arrays, set with the PFGS_PACKED_ATTRS to pfGSetAttr, to render geometry with GL vertex arrays.

The pfGeoSets are normally processed in immediate mode, which means that pfDrawGSet() sends attributes from the user-supplied attribute arrays to the Graphics Pipeline for rendering. However, this kind of processing is subject to some overhead, particularly if the pfGeoSet contains few primitives. In some cases it may help to use GL display lists (this is different from the libpr display list type pfDispList) or compiled mode. In compiled mode, pfGeoSet attributes are copied from the attribute lists into a special data structure called a display list during a compilation stage. This data structure is highly optimized for efficient transfer to the graphics hardware. However, compiled mode has some major disadvantages:

  • Compilation is usually costly.

  • A GL display list must be recompiled whenever its pfGeoSet's attributes change.

  • The GL display list uses a significant amount of extra host memory.

In general, immediate mode will offer excellent performance with minimal memory usage and no restrictions on attribute volatility, which is a key aspect in may advanced applications. Despite this, experimentation may show databases or machines where compiled mode offers a performance benefit.

To enable or disable compiled mode, call pfGSetDrawMode() with the PFGS_COMPILE_GL token. When enabled, compilation is delayed until the next time the pfGeoSet is drawn with pfDrawGSet(). Subsequent calls to pfDrawGSet() will then send the compiled pfGeoSet to the graphics hardware.

To select a display list to render, without recompiling it, use pfGSetDrawMode() with the token PFGS_DRAW_GLOBJ.

Packed Attributes

Packed attributes is an optimized way of sending formatted data to the graphics pipeline under OpenGL that does not incur the same memory overead or recompilation burden as GL display lists. To render geometry with packed attributes, use the pfGSetDrawMode( PFGS_PACKED_ATTRS) method when using OpenGL. This pfGSetAttr list includes the currently bound PER_VERTEX vertex attribute data packed into a single nonindexed array. When specifying a packed attribute array, the optional vertex attributes, colors, normals, and texture coordinates, can be NULL. This array, like the other attribute arrays, is then shared betweenOpenGL Performer, the GL, and accessible by the user. Optionally, you can put your vertex coordinates in this packed array but in this case the vertices must be duplicated in the normal coordinate array because vertex coordinate data is used internally for other nondrawing operations such as intersections and computation of bounding geometry. Packed attribute arrays also allow OpenGL Performer to extend the vertex attribute types accepted by pfGeoSets. There are several base formats that expect all currently bound attributes of specified data type (unsigned byte, short, or float) to be in the attribute array. Attributes specified by the format but not bound to vertices are assumed to not be present and the present data is packed with the data for each vertex starting on a 32-bit word-aligned boundary. Then, there are several derived formats that let you put some attribute data in the packed array while leaving the rest in the normal individual coordinate attribute arrays. Table 8-3 shows the different base formats supported.

Table 8-3. pfGeoSet PACKED_ATTR Formats

Format

Description

PFGS_PA_C4UBN3ST2FV3F

Accepts all currently bound coordinate attributes; colors are unsigned bytes; normals are shorts. Vertices are duplicated in the packed attribute array.

PFGS_PA_C4UBN3ST2F

Vertices are in the normal coordinate array.

PFGS_PA_C4UBT2F

Normals and vertices are in the normal coordinate array.

PFGS_PA_C4UBN3ST2SV3F

All bound coordinate attributes are in the packed attribute array. Colors are unsigned bytes, normals are shorts, and texture coordinates are unsigned shorts.

PFGS_PA_C4UBN3ST3FV3F

Texture coordinates are 3D floats.

PFGS_PA_C4UBN3ST3SV3F

Texture coordinates are 2D shorts.

To create packed attributes, you can use the utility pfuTravCreatePackedAttrs(), which traverses a scene graph to create packed attributes for pfGeoSets and, optionally, pfDelete redundant attribute arrays. This utility packs the pfGeoSet attributes using pfuFillGSetPackedAttrs(). Examples of packed attribute usage can be seen in /usr/share/Performer/src/pguide/libpr/C/packedattrs.c and /usr/share/Performer/src/sample/C/perfly.c and /usr/share/Performer/src/sample/C++/perfly.C for IRIX and Linux and in %PFROOT%\Src\pguide\libpr\C\packedattrs.c, %PFROOT%\Src\sample\C\perfly.c, and %PFROOT%\Src\sample\C++\perfly.C for Microsoft Windows.

Primitive Connectivity

A pfGeoSet requires a coordinate array that specifies the world coordinate positions of primitive vertices. This array is either indexed or not, depending on whether a coordinate index list is supplied. If the index list is supplied, it is used to index the coordinate array; if not, the coordinate array is interpreted in a sequential order.

A pfGeoSet's primitive type dictates the connectivity from vertex to vertex to define geometry. Figure 8-1 shows a coordinate array consisting of four coordinates, A, B, C, and D, and the geometry resulting from different primitive types. This example uses index lists that index the coordinate array.


Note: Flat-shaded line strip and flat-shaded triangle strip primitives have the vertices listed in the same order as for the smooth-shaded varieties.

Figure 8-1. Primitives and Connectivity

Primitives and Connectivity

Attributes

The definition of a primitive is not complete without attributes. In addition to a primitive type and count, a pfGeoSet references four attribute arrays (see Figure 8-2):

  • Colors (red, green, blue, alpha)

  • Normals (Nx, Ny, Nz)

  • Texture coordinates (S, T)—multiple arrays for multitexture.

  • Vertex coordinates (X, Y, Z)

(A pfGeoState is also associated with each pfGeoSet; see Chapter 12, “Graphics State” for details.) The four components listed above can be specified with pfGSetAttr(). Multivalue attributes (texture coordinates) can be specified using pfGSetMultiAttr() or pfGSetAttr(). Using zero as the index parameter for pfGSetMultiAttr() is equivalent to calling pfGSetAttr(). Attributes may be set in two ways: by indexed specification—using a pointer to an array of components and a pointer to an array of indices; or by direct specification—providing a NULL pointer for the indices, which indicates that the indices are sequential from the initial value of zero. The choice of indexed or direct components applies to an entire pfGeoSet; that is, all of the supplied components within one pfGeoSet must use the same method. However, you can emulate partially indexed pfGeoSets by using indexed specification and making each nonindexed attribute's index list be a singly shared “identity mapping” index array whose elements are 0, 1, 2, 3,…, N–1, where N is the largest number of attributes in any referencing pfGeoSet. (You can share the same array for all such emulated pfGeoSets.) The direct method avoids one level of indirection and may have a performance advantage compared with indexed specification for some combinations of CPUs and graphics subsystems.


Note: Use pfMalloc() to allocate your arrays of attribute data. This allows OpenGL Performer to reference-count the arrays and delete them when appropriate. It also allows you to easily put your attribute data into shared memory for multiprocessing by specifying an arena such as pfGetSharedArena() to pfMalloc(). While perhaps convenient, it is very dangerous to specify pointers to static data for pfGeoSet attributes. Early versions of OpenGL Performer permitted this but it is strongly discouraged and may have undefined and unfortunate consequences.

Attribute arrays can be created through pfFlux to support the multiprocessed generation of the vertex data for a dynamic object, such as ocean waves, or morphing geometry. pfFlux will automatically keep separate copies of data for separate proceses so that one process can generate data while another draws it. The pfFluxed buffer can be handed directly to pfGSetAttr() or pfGSetMultiAttr(). In fact, the entire pfGeoSet can be contained in a pfFlux. Index lists cannot be pfFluxed. See Chapter 19, “Dynamic Data”, for more information on pfFlux.

Figure 8-2. pfGeoSet Structure

pfGeoSet Structure


Note: When using multiple texture-coordinate arrays, pfGeoSet recognizes texture-coordinate arrays starting at the first array (index of 0) and ending immediately before the first index with a NULL array. In other words, specifying texture-coordinate arrays using pfGSetMultiAttr() for indices 0, 1, and 3 is equivalent to specifying texture-coordinate arrays for only indices 0 and 1. When using pfTexGen to automatically generate texture coordinates for some texture units, the application should not interleave texture units with texture coordinates and texture units with pfTexGen. Texture units with texture coordinates should come before texture units with pfTexGen. This is an implementation limitation and may be removed in future releases.


Attribute Bindings

Attribute bindings specify where in the definition of a primitive an attribute has effect. You can leave a given attribute unspecified; otherwise, its binding location is one of the following:

  • Overall (one value for the entire pfGeoSet)

  • Per primitive

  • Per vertex

Only certain binding types are supported for some attribute types.

Table 8-4 shows the attribute bindings that are valid for each type of attribute.

Table 8-4. Attribute Bindings

Binding Token

Color

Normal

Texture Coordinate

Coordinate

PFGS_OVERALL

Yes

Yes

No

No

PFGS_PER_PRIM

Yes

Yes

No

No

PFGS_PER_VERTEX

Yes

Yes

Yes

Yes

PFGS_OFF

Yes

Yes

Yes

No

Attribute lists, index lists, and binding types are all set by pfGSetAttr().

For FLAT primitives ( PFGS_FLAT_TRISTRIPS, PFGS_FLAT_TRIFANS, PFGS_FLAT_LINESTRIPS), the PFGS_PER_VERTEX binding for normals and colors has slightly different meaning. In these cases, per-vertex colors and normals should not be specified for the first vertex in each line strip or for the first two vertices in each triangle strip since FLAT primitives use the last vertex of each line segment or triangle to compute shading.

Indexed Arrays

A cube has six sides; together those sides have 24 vertices. In a vertex array, you could specify the primitives in the cube using 24 vertices. However, most of those vertices overlap. If more than one primitive can refer to the same vertex, the number of vertices can be streamlined to 8. The way to get more than one primitive to refer to the same vertex is to use an index; three vertices of three primitives use the same index which points to the same vertex information. Adding the index array adds an extra step in the determination of the attribute, as shown in Figure 8-3.

Figure 8-3. Indexing Arrays

Indexing Arrays

Indexing can save system memory, but rendering performance is often lost.

When to Index Attributes

The choice of using indexed or sequential attributes applies to all of the primitives in a pfGeoSet; that is, all of the primitives within one pfGeoSet must be referenced sequentially or by index; you cannot mix the two.

The governing principle for whether to index attributes is how many vertices in a geometry are shared. Consider the following two examples in Figure 8-4, where each dot marks a vertex.

Figure 8-4. Deciding Whether to Index Attributes

Deciding Whether to Index Attributes

In the triangle strip, each vertex is shared by two adjoining triangles. In the square, the same vertex is shared by eight triangles. Consider the task that is required to move these vertices when, for example, morphing the object. If the vertices were not indexed, in the square, the application would have to look up and alter eight triangles to change one vertex.

In the case of the square, it is much more efficient to index the attributes. On the other hand, if the attributes in the triangle strip were indexed, since each vertex is shared by only two triangles, the index look-up time would exceed the time it would take to simply update the vertices sequentially. In the case of the triangle strip, rendering is improved by handling the attributes sequentially.

The deciding factor governing whether to index attributes relates to the number of primitives that share the same attribute: if attributes are shared by many primitives, the attributes should be indexed; if attributes are not shared by many primitives, the attributes should be handled sequentially.

pfGeoSet Operations

There are many operations you can perform on pfGeoSets. pfDrawGSet() “draws “ the indicated pfGeoSet by sending commands and data to the Geometry Pipeline, unless OpenGL Performer's display-list mode is in effect. In display-list mode, rather than sending the data to the pipeline, the current pfDispList “captures” the pfDrawGSet() command. The given pfGeoSet is then drawn along with the rest of the pfDispList with the pfDrawDList() command.

When the PFGS_COMPILE_GL mode of a pfGeoSet is not active ( pfGSetDrawMode()), pfDrawGSet() uses rendering loops tuned for each primitive type and attribute binding combination to reduce CPU overhead in transferring the geometry data to the hardware pipeline. Otherwise, pfDrawGSet() sends a special, compiled data structure.

Table 8-1 lists other operations that you can perform on pfGeoSets. pfCopy() does a shallow copy, copying the source pfGeoSet's attribute arrays by reference and incrementing their reference counts. pfDelete() frees the memory of a pfGeoSet and its attribute arrays (if those arrays were allocated with pfMalloc() and provided their reference counts reach zero). pfPrint() is strictly a debugging utility and will print a pfGeoSet's contents to a specified destination. pfGSetIsectSegs() allows intersection testing of line segments against the geometry in a pfGeoSet; see “Intersecting with pfGeoSets” in Chapter 22 for more information on that function.

pfGeoArray (Geometry Array)

The pfGeoArray is a new OpenGL Performer data structure aimed at replacing the class pfGeoSet. Conceptually, pfGeoArrays are very similar to pfGeoSets, but they allow you to define new sets of attributes in addition to the standard vertex coordinates, normals, texture coordinates, and colors. These attributes can be used by vertex or fragment programs applied to the primitives (see “Using OpenGL Performer with GPUs” in Chapter 14). Also, pfGeoArrays are rendered using vertex arrays and vertex objects, making the rendering much more efficient. pfGeoArrays can be up to 10 times faster than pfGeoSets on Onyx4 or Prism systems.

Each pfGeoArray is a collection of geometry with one primitive type, such as points, lines, or triangles. Vertex coordinates, normals, colors, texture coordinates, and user-defined attributes are used to specify the primitives. There are two ways to specify the attributes. First, each attribute is specified per vertex, there is no concept of an attribute per primitive or an overall attribute. Second, you can use a single list of unsigned integers to index all attributes of a pfGeoArray.

Indexing provides a more general mechanism for specifying geometry than hardwired attribute lists and, in many cases, provides substantial memory savings due to shared attributes. Nonindexed pfGeoArrays are sometimes easier to construct and may exhibit better caching behavior. Indexing is often a desirable approach especially when your primitives are sharing many attributes (such as having the same normal for each face). Also, if you have a primitive with many triangle strips, it is better to create a single pfGeoArray containing indexed triangles than to have a set of short pfGeoArrays, each containing one triangle strip.

This section contains the following topics:

Creating pfGeoArrays

The function pfNewGArray() creates and returns a handle to a pfGeoArray. The parameter arena specifies a malloc() arena out of which the pfGeoArray is allocated or NULL for allocation off the process heap. pfGeoArrays can be deleted with pfDelete().

The call new(arena) allocates a pfGeoArray from the specified memory arena, or from the process heap if arena is NULL. The new() call allocates a pfGeoArray from the default memory arena (see the man page for pfGetSharedArena). Like other pfObjects, pfGeoArrays cannot be automatically created statically on the stack or in arrays. Delete pfGeoArrays with pfDelete() rather than with the delete operator.

pfGeoArray Attributes

The function pfGArrayAddAttr() adds a new attribute to the list of attributes of a pfGeoArray. This list is initially empty. An attribute is specified by its attribute type and the following parameters:

size 

Specifies the number of coordinates per vertex; It must be 2, 3, or 4.

type 

Specifies the type of each component in the attribute data. It is one of GL_DOUBLE, GL_FLOAT, GL_INT, GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_BYTE, and GL_BYTE.

stride 

Specifies the byte offset between consecutive vertex data. It is usually 0.

pointer 

Specifies a pointer to the attribute data.

You can modify the name, size, the data type, the stride, and the data pointer for any existing attribute using the following functions:

  • pfVAttrName()

  • pfVAttrPtr()

  • pfVAttrStride()

  • pfVAttrDataType()

  • pfVAttrSize()

You can also remove an attribute using the function pfGArrayRemoveAttr().

Multitexturing is supported by adding multiple PFGA_TEX_ARRAY vertex attributes and specifying different stages, as shown in the following example:

pfGeoArray *gArray = pfNewGArray();

pfGArrayMultiAttr(gArray, PFGA_TEX_ARRAY, 0, 2, GL_FLOAT, 0, baseCoords);
pfGArrayMultiAttr(gArray, PFGA_TEX_ARRAY, 1, 2, GL_FLOAT, 0, bumpCoords);

/* set name for the two sets of tex coords just assigned */
pfVArrayName( pfGArrayQueryAttrTypeStage(gArray, PFGA_TEX_ARRAY, 0), "base texture coords");
pfVArrayName( pfGArrayQueryAttrTypeStage(gArray, PFGA_TEX_ARRAY, 1), "bump texture coords");

Note that since the pfGeoArray attributes are rendered in the order they were added, it is possible to interleave the attributes with your own callbacks. To do so, create a special "callback" type with a function mask 0 (no callback data) or a function mask 0x1 (callback data is used).

It is possible to index the attributes, although (in contrast to pfGeoSets) a single index list is used for all attributes. The optional attribute index list is a list of unsigned short integers. The index list is specified using the function pfGArrayIndexArray().

If attribute and index lists are allocated from the pfMalloc routines, pfGArrayAddAttr() and pfGArrayAttrPtr() will correctly update the reference counts of the lists. Specifically, they will decrement the reference counts of the old lists and increment the reference counts of the new lists. It will not free any lists whose reference counts reach 0. When a pfGeoArray is deleted with pfDelete(), all pfMalloc'ed lists will have their reference counts decremented by one and will be freed if their count reaches 0.

When pfGeoArrays are copied with pfCopy(), all pfMalloc'ed lists of the source pfGeoArray will have their reference counts incremented by one and those pfMalloc'ed lists of the destination pfGeoArray will have their reference counts decremented by one. The function pfCopy() copies lists only by reference (only the pointer is copied) and will not free any lists whose reference counts reach 0.

Attribute data may be any of the following types of memory:

  • Data allocated with pfMalloc

    This is the usual and recommended memory type for pfGeoArray index and attribute arrays.

  • Static, malloc(), amalloc(), usmalloc(), and similar data (non-pfMalloc'ed data)

    This type of memory is not generally recommended since it does not support reference counting or other features provided by pfMalloc. In particular, do not use static data because it may result in segmentation violations.

  • pfFlux memory

    In a pipelined, multiprocessing environment, a pfFlux provides multiple data buffers, which allow frame-accurate data modifications to pfGeoArray attribute arrays like coordinates (facial animation) and texture coordinates (ocean waves, surf). The functions pfGArrayAddAttr() and pfGArrayAttrPtr() will accept a pfFlux* or pfFluxMemory* for the attribute list (index lists do not support pfFlux) and the pfGeoArray will select the appropriate buffer when rendered or intersected. See the man page for pfFlux for more details.

    Since pfGeoArrays are cached using vertex array objects, if you want to animate some attributes, you need to either disable caching using the function pfGArrayAllowCache() or call the function pfGArrayUpdateData() each time you change any of the attribute data.

  • pfCycleBuffer and pfCycleMemory

    Note that pfCycleBuffer has been obsoleted by pfFlux. See the man page for pfCycleBuffer for more details.

OpenGL Performer allows mixing pfMalloc'ed, pfFlux, and pfCycleBuffer attributes on a single pfGeoArray.

pfGeoArray Attribute Types

When a new pfGeoArray is created, it has no default attribute. When adding a new attribute, you must specify the type of the attribute— that is, whether it specifies one of the following:

  • A vertex coordinates (PFGA_COORD_ARRAY)

  • A normal vector (PFGA_NORMAL_ARRAY)

  • A color (PFGA_COLOR_ARRAY)

  • A texture coordinate (PFGA_TEX_ARRAY)

  • A generic user-defined attribute (PFGA_GENERIC_ARRAY)

Attribute types are identified by their type, their name (a string), and the associated texture stage (if applicable). A new attribute type can be added using the function pfGArrayAddAttrType(). The parameter type is one of tokens just cited. The name can be any arbitrary string and if one is not set, then depending on the array type, a default name will be used ("vertex", "normal", "color", "texture coord" or "generic"). The parameter stage defines the associated texture stage. In the case of attributes of type PFGA_GENERIC_ARRAY, the attributes are applied using the function glVertexAttribPointerARB().

The following example code adds two sets of texture coordinates and one set of vertices to a pfGeoArray:

pfVertexAttr *vAttr, tAttrs[2];
pfGeoArray *gArray = ...;

vAttr    = pfGArrayAddAttrType(gArray, PFGA_COORD_ARRAY, "vertices", 0);
tAttr[0] = pfGArrayAddAttrType(gArray, PFGA_TEX_ARRAY,  "texCoord0", 0);
tAttr[1] = pfGArrayAddAttrType(gArray, PFGA_TEX_ARRAY,  "texCoord1", 1);

pfGeoArray Primitive Types

A primitive is a single point, line segment, line strip, triangle, triangle strip, quad, or polygon depending on the primitive type. The primitive type dictates how the coordinate and coordinate index lists are interpreted to form geometry. The function pfGSetPrimType() specifies the type of primitives found in a pfGeoArray.

The following example shows how to set up a nonindexed, TRISTRIP pfGeoArray:

/* Set up a nonindexed, TRISTRIP pfGeoArray */

garray = pfNewGArray(NULL);
pfGSetPrimType(garray, PFGS_TRISTRIPS);
pfGSetNumPrims(garray, 1);
lengths[0] = 4;
pfGSetPrimLengths(gset, lengths);

coords = (pfVec3*) pfMalloc(sizeof(pfVec3) * 4, NULL);
colors = (pfVec4*) pfMalloc(sizeof(pfVec4) * 4, NULL);

pfGArraySetAttr(garray, PFGA_COORD_ARRAY, 3, GL_FLOAT, 0, coords);
pfGArraySetAttr(garray, PFGA_COLOR_ARRAY, 4, GL_FLOAT, 0, colors);

The function pfGetGSetClassType() returns the pfType* for the class pfShaderProgram. The pfType* returned by pfGetGSetClassType() is the same as the pfType* returned by invoking pfGetType(), the virtual function getType() on any instance of class pfShaderProgram. Because OpenGL Performer allows subclassing of built-in types when decisions are made based on the type of an object, use pfIsOfType() the member function isOfType() to test if an object is of a type derived from an OpenGL -Performer type rather than to test for strict equality of the pfType*s.

Example Code

The following example shows one way to create a pfGeoArray defining a hexahedron (cube).

static pfVec3 coords[] =
{
    {-1.0, -1.0,  1.0}, /* front */
    { 1.0, -1.0,  1.0},
    { 1.0,  1.0,  1.0},
    {-1.0,  1.0,  1.0},

    {-1.0, -1.0,  1.0}, /* left */
    {-1.0,  1.0,  1.0},
    {-1.0,  1.0, -1.0},
    {-1.0, -1.0, -1.0},

    {-1.0, -1.0, -1.0}, /* back */
    {-1.0,  1.0, -1.0},
    { 1.0,  1.0, -1.0},
    { 1.0, -1.0, -1.0},

    { 1.0, -1.0,  1.0}, /* right */
    { 1.0, -1.0, -1.0},
    { 1.0,  1.0, -1.0},
    { 1.0,  1.0,  1.0},

    {-1.0,  1.0,  1.0}, /* top */
    { 1.0,  1.0,  1.0},
    { 1.0,  1.0, -1.0},
    {-1.0,  1.0, -1.0},

    {-1.0, -1.0,  1.0}, /* bottom */
    {-1.0, -1.0, -1.0},
    { 1.0, -1.0, -1.0},
    { 1.0, -1.0,  1.0}
};
static pfVec3 norms[] =
{
    { 0.0,  0.0,  1.0},
    { 0.0,  0.0,  1.0},
    { 0.0,  0.0,  1.0},
    { 0.0,  0.0,  1.0},

    {-1.0,  0.0,  0.0},
    {-1.0,  0.0,  0.0},
    {-1.0,  0.0,  0.0},
    {-1.0,  0.0,  0.0},

    { 0.0,  0.0, -1.0},
    { 0.0,  0.0, -1.0},
    { 0.0,  0.0, -1.0},
    { 0.0,  0.0, -1.0},

    { 1.0,  0.0,  0.0},
    { 1.0,  0.0,  0.0},
    { 1.0,  0.0,  0.0},
    { 1.0,  0.0,  0.0},

    { 0.0,  1.0,  0.0},
    { 0.0,  1.0,  0.0},
    { 0.0,  1.0,  0.0},
    { 0.0,  1.0,  0.0},

    { 0.0, -1.0,  0.0},
    { 0.0, -1.0,  0.0},
    { 0.0, -1.0,  0.0},
    { 0.0, -1.0,  0.0}
};

/* Convert static data to pfMalloc'ed data */
static void*
memdup(void *mem, size_t bytes, void *arena)
{
    void *data = pfMalloc(bytes, arena);
    memcpy(data, mem, bytes);
    return data;
}

/* Set up a PFGS_QUADS pfGeoArray */

garray = pfNewGArray(NULL);
pfGSetPrimType(garray, PFGS_QUADS);
pfGSetNumPrims(garray, 6);

pfGArraySetAttr(garray, PFGA_COORD_ARRAY, 3, GL_FLOAT, 0, 
                memdup(coords, sizeof(coords), NULL));
pfGArraySetAttr(garray, PFGA_NORMAL_ARRAY, 3, GL_FLOAT, 0, 
                memdup(norms, sizeof(norms), NULL));


static pfVec3 coords[] =
{
    {-1.0, -1.0,  1.0}, /* front */
    { 1.0, -1.0,  1.0},
    { 1.0,  1.0,  1.0},
    {-1.0,  1.0,  1.0},

    {-1.0, -1.0,  1.0}, /* left */
    {-1.0,  1.0,  1.0},
    {-1.0,  1.0, -1.0},
    {-1.0, -1.0, -1.0},

    {-1.0, -1.0, -1.0}, /* back */
    {-1.0,  1.0, -1.0},
    { 1.0,  1.0, -1.0},
    { 1.0, -1.0, -1.0},

    { 1.0, -1.0,  1.0}, /* right */
    { 1.0, -1.0, -1.0},
    { 1.0,  1.0, -1.0},
    { 1.0,  1.0,  1.0},

    {-1.0,  1.0,  1.0}, /* top */
    { 1.0,  1.0,  1.0},
    { 1.0,  1.0, -1.0},
    {-1.0,  1.0, -1.0},

    {-1.0, -1.0,  1.0}, /* bottom */
    {-1.0, -1.0, -1.0},
    { 1.0, -1.0, -1.0},
    { 1.0, -1.0,  1.0}
};

static pfVec3 norms[] =
{
    { 0.0,  0.0,  1.0},
    { 0.0,  0.0,  1.0},
    { 0.0,  0.0,  1.0},
    { 0.0,  0.0,  1.0},

    {-1.0,  0.0,  0.0},
    {-1.0,  0.0,  0.0},
    {-1.0,  0.0,  0.0},
    {-1.0,  0.0,  0.0},

    { 0.0,  0.0, -1.0},
    { 0.0,  0.0, -1.0},
    { 0.0,  0.0, -1.0},
    { 0.0,  0.0, -1.0},
    { 1.0,  0.0,  0.0},
    { 1.0,  0.0,  0.0},
    { 1.0,  0.0,  0.0},
    { 1.0,  0.0,  0.0},

    { 0.0,  1.0,  0.0},
    { 0.0,  1.0,  0.0},
    { 0.0,  1.0,  0.0},
    { 0.0,  1.0,  0.0},

    { 0.0, -1.0,  0.0},
    { 0.0, -1.0,  0.0},
    { 0.0, -1.0,  0.0},
    { 0.0, -1.0,  0.0}
};

// Convert static data to pfMalloc'ed data
static void*
memdup(void *mem, size_t bytes, void *arena)
{
    void *data = pfMalloc(bytes, arena);
    memcpy(data, mem, bytes);
    return data;
}

/* Set up a PFGS_QUADS pfGeoArray */
garray = new pfGeoArray;
garray->setPrimType(PFGS_QUADS);
garray->setNumPrims(6);

garray->setAttr(PFGA_COORD_ARRAY, 3, GL_FLOAT, 0, 
                memdup(coords, sizeof(coords), NULL));
garray->setAttr(PFGA_NORMAL_ARRAY, 3, GL_FLOAT, 0, 
                memdup(norms, sizeof(norms), NULL));

With pfGeoArrays, unlike with pfGeoSets, you cannot index vertex coordinates and normals separately. This results in bigger memory requirements. The extra storage is worth the reduced rendering times, though.

Another example of creating pfGeoArrays can be found in following files:

(IRIX and Linux) /usr/share/Performer/src/pguide/libpfdu/pfdConvertToGeoArrays.C
/usr/share/Performer/src/pguide/libpr/C++/geoArray.C
(Microsoft Windows)
%PFROOT%\Src\pguide\libpfdu\pfdConvertToGeoArrays.cxx
%PFROOT%\Src\pguide\libpr\C++\geoArray.cxx
 

Converting pfGeoSets to pfGeoArrays

Since using pfGeoArrays can be much faster on some platforms, such as Onyx4 or Prism systems, you can convert your geometry from pfGeoSets to pfGeoArrays using the following two functions:

  • pfdConvertGeoSetToGeoArray()

  • pfdConvertNodeGeoSetsToGeoArrays()

The first function converts an individial pfGeoSet into a pfGeoArray. The second function traverses a pfNode and replaces all its pfGeoSets with pfGeoArrays. The parameter flags can be set to 0 or to PFD_CONVERT_TO_INDEXED_GEOARRAYS. In the second case the loader tries to avoid the use of lengths array and it converts strips to indexed lines or triangles.

Also, it is possible to use the pseudo loader libpfgeoa to convert the geometry from pfGeoSets to pfGeoArrays during loading. The pseudo loader is used as follows:

perfly file.ext.geoa

The pseudo loader calls pfdConvertNodeGeoSetsToGeoArrays() with the flag PFD_CONVERT_TO_INDEXED_GEOARRAYS set. You can overwrite this default by setting the environment variable PFD_CONVERT_TO_INDEXED_GEOARRAYS to 0.

Optimizing Geometry for Rendering

This section describes how you can use three functions to optimize your pfGeosets or pfGeoArrays for rendering. The following topics are described:

Function pfdMergeGraph()

The function pfdMergeGraph() gathers all pfGeoSets referenced in the each static subgraph rooted at a node. It then creates a new subgraph for each that has a minimum number of pfGeoSets by grouping pfGeoSets that share state and attribute data. The function only operates on static subgraphs and, thus, will not destroy pfLOD, pfSequence, or other dynamic structures in a hierarchy. The new graph, which is not spatialized, is returned to the caller. All functions described in this section can output geometry as either pfGeoSets or pfGeoArrays and as either indexed or nonindexed. The default behavior is to output nonindexed pfGeoArrays if any are present and nonindexed pfGeoSets, otherwise. This can be controlled by passing in the following as the flags parameters:

  • PFD_OUTPUT_GEOSETS

  • PFD_OUTPUT_GEOARRAYS

  • PFD_OUTPUT_INDEXED

These flags force the output format to be of the type and format indicated.

The function pfdMergeGraph() returns the root to a new scene graph and does not modify the graph that is rooted at node. The calling application must delete the previous graph if it is no longer needed.

Function pfdStripGraph()

The function pfdStripGraph() collects all pfGeoSets in the graph rooted by the node and modifies each in several possible ways in order to increase performance. This can include stripping (converting from separate primitives, such as triangles, to their stripped form, triangle strips) or unstripping geometry, merging stripped geometry, and reordering primitives. On certain hardware, such as Onyx4 or Prism systems, reordering primitives can improve performance by taking advantage of hardware vertex caches to decrease the number of transformed vertices. If you specify an integer length for cacheLength OpenGL Performer uses the given value as the length of the cache on the target hardware. If you do not know the cache length, which varies on some platforms, using a value of 0 triggers a more generic algorithm to provide better results on machines of varying cache lengths. Controlling the behavior of the operation can be done by passing in the OR result of the following tokens as the flags parameter:

PFD_STRIP_UNSTRIPPED_PRIMITIVES  

This token indicates that any primitives that are not stripped should be stripped.

PFD_STRIP_STRIPPED_PRIMITIVES  

This token indicates that any primitives that are already stripped should be restripped.

PFD_UNSTRIP_STRIPPED_PRIMITIVES  

This token indicates that any primitives that are already stripped should be unstripped or converted back to a separated form. If this token and PFD_STRIP_STRIPPED_PRIMITIVES are both passed, the input geometry is first unstripped and then restripped.

PFD_MERGE_TRISTRIPS  

This token indicates that triangle strips should be joined with degenerate triangles to construct longer strips. This will reduce the number of primitives in a pfGeoSet and may improve performance by reducing the number of draw calls needed to render the geometry.

PFD_REORDER_CACHE_REUSE  

This token indicates that the primitives should be reordered to improve cache reuse. On systems with a hardware vertex cache, this may improve performance and is typically the most effective reordering strategy.

PFD_REORDER_REDUCE_DEGENERATES  

This token indicates that the reordering of primitives should seek to reduce the number of mergings of degenerate triangles. This is not typically as effective as reordering to improve cache reuse.

The function pfdStripGraph() uses the same output flags as pfdMergeGraph().


Note: There is no default behavior for pfdStripGraph(); that is, if flags is set to 0, then the processing options just described are disabled. In this case, the result will be no change to the input geometry.


Function pfdSpatializeGraph()

The function pfdSpatializeGraph() operates similarily to pfdSpatialize() and pfdBreakup(). The function pfdSpatializeGraph() first breaks the geometry of the graph rooted at a node into smaller chunks to improve spatialization and, in effect, culling. This breakup turns each geode into a subgraph rooted by a pfGroup node with several pfGeode children. Next, each static subgraph is spatialized using pfSpatialize(). Similar to pfdMergeGraph(), this operation does not change the dynamic aspects of a scene graph. Graphs rooted by nodes such as pfSwitch and pfSequence will not be changed by the operation. The maxStripLength parameter controls the strip length threshold for the breakup. If this value is set to 0, then no maximum will be used. The octreeLevels parameter controls the target number of octree levels for the breakup and spatialization operations. However, depending upon the number of pfGeoSets in the graph, there may be slightly more or fewer levels in the actual output graph. Like pfdStripGraph(), the flags parameter is used to pass in the OR result of the following tokens to control the behavior of the operation:

PFD_BREAKUP_GEOSETS  

This token indicates that the breakup operation should be performed.

PFD_SPATIALIZE_GRAPH  

This token indicates that the spatialization operation should be performed.

As with pfdStripGraph(), pfdSpatializeGraph() has no default behavior and will perform no graph modification if no flags are passed in. Like both pfdStripGraph() and pfdMergeGraph(), the output can be controlled by the same tokens passed through the flags parameter, and pfdSpatializeGraph() returns the root to a new scene graph.

The Optimization Pipeline

Collectively, pfdMergeGraph(), pfdStripGraph(), and pfdSpatializeGraph() can be run as a pipeline on an input scene graph to reformat and optimize the graph for best performance on a given hardware. Additionally, the following compund tokens are available for setting the flags which can be passed to all three functions to modify behavior:

PFD_OPTIMIZE_AGGRESSIVE  

This token indicates that the operations should perform their most aggressive optimizations. This includes restripping all primitives, reordering to improve cache reuse, merging strips, and performing all spatialization steps. While this may not always be the best set of operations, it should provide an excellent starting point, especially for optimizing on newer hardware, such as Onyx4 or Prism systems.

PFD_OPTIMIZE_CONSERVATIVE  

This token is used for performing only the safest optimizations. These optimizations are unlikely to decrease performance and may be useful for optimizing across several different platforms and generations of hardware.

PFD_OPTIMIZE_INDEXED_TRIS  

On some hardware, indexed triangles may be the fastest geometry format. For these systems, the PFD_OPTIMIZE_INDEXED_TRIS token will use aggressive methods but output to an indexed, unstripped, but reordered format.

These tokens are combinations of the previous tokens and may be used with any of the previously mention tokens (most notably, the output tokens). All three compound tokens include PFD_OUTPUT_INDEXED; therefore, if that mode is not desired, an application could remove it by performing an AND of token and the negation of PFD_OUTPUT_INDEXED.

The following code sample illustrates using the optimization functions in a pipeline on an input graph and saving the output to a file:

root = pfdLoadFile(inputFile);

if (root == NULL)
{
 pfNotify(PFNFY_FATAL, PFNFY_USAGE,
     "Input graph was not found.  Quitting.");
}

// First, call merge graph
root = pfdMergeGraph(root, PFD_OPTIMIZE_AGGRESSIVE);

// Now strip each of the merged geoSets
// length of the cache is 12 vertices
root = pfdStripGraph(root, 12, PFD_OPTIMIZE_AGGRESSIVE);

// Finally, spatialize the graph
// Have 3 octree levels, and no maximum strip length
root = pfdSpatializeGraph(root, 3, 0, PFD_OPTIMIZE_AGGRESSIVE);

// Save new scene graph
pfdStoreFile(root, outputFile);

A sample application is located in the following file:

(IRIX and Linux)

/usr/share/Performer/src/pguide/libpf/C++/optimizeGraph.C

(Microsoft Windows)

%PFROOT%\Src\lib\pguide\libpf\C++\optimizeGraph.cxx

Using the libpfgopt Pseudo Loader

As an alternative to the functions described in this chapter, you can use the libpfgopt pseudo loader to optimize geometry while loading a database file. You can specify the filename in one of two formats to call the pseudo loader:

file.ext.gopt
file.ext.parameters.gopt

If the first format is specified, then the optimizer uses a default operation mode, which consists of aggressive optimization but with no breakup and with output to indexed geoarrays. This is equivalent to the following specification in the second format:

file.ext.aggressive,nobreakup,geoarray.gopt

In the second format, parameters is a comma separated list of keywords or keyword=value pairs. The following are valid keywords:

  • aggressive

  • conservative

  • indexedtris

  • join

  • nojoin

  • cachereuse

  • reducedegens

  • noreorder

  • breakup

  • nobreakup

  • spatialize

  • nospatialize

  • geoset

  • geoarray

  • indexed

  • unindexed

Each of these keywords enables or disables a related operation or mode of the optimization pipeline, which consists of pfdMergeGraph(), pfdStripGraph(), and pfdSpatializeGraph().

The following are valid keyword=value entries for parameters:

  • cachelength=int

  • octree=int

  • striplength=int

They set the related values of the corresponding functions to the specified value. For more information about all the keywords and their functionality, see the pfdOptimizeGraph man page.

If at least one parameter is specified, then there is no default operations by the optimizer, except those explicitly enabled. For example, for the following filename specification, the optimizer will not join triangle strips, breakup geosets, or spatialize the graph:

foo.pfb.cachereuse.gopt

Additionally, since some keywords have opposing behavior, the order they are specified matters. For example, if the file is specified in the following manner, then the resulting geometry will be nonindexed:

foo.pfb.indexed,unindexed.gopt

Rendering 3D Text

In addition to the pfGeoSet and pfGeoArray, libpr offers two other primitives which together are useful for rendering a specific type of geometry— 3D characters. See Chapter 3, “Nodes and Node Types” and the description for pfText nodes for an example of how to set up the 3D text within the context of libpf.

pfFont

The basic primitive supporting text rendering is the libpr pfFont primitive. A pfFont is essentially a collection of pfGeoSets in which each pfGeoSet represents one character of a particular font. pfFont also contain metric data, such as a per-character spacing, the 3D escapement offset used to increment a text `cursor' after the character has been drawn. Thus, pfFont maintains all of the information that is necessary to draw any and all valid characters of a font. However, note that pfFonts are passive and have little functionality on their own; for example, you cannot draw a pfFont—it simply provides the character set for the next higher-level text data object, the pfString.

Table 8-5 lists some routines that are used with a pfFont.

Table 8-5. pfFont Routines

Routine

Description

pfNewFont()

Create a new pfFont.

pfDelete()

Delete a pfFont.

pfFontCharGSet()

Set the pfGeoSet to be used for a specific character of this pfFont.

pfFontCharSpacing()

Set the 3D spacing to be used to update a text cursor after this character has been rendered.

pfFontMode()

Specify a particular mode for this pfFont.

Valid modes:

PFFONT_CHAR_SPACING—Specify whether to use fixed or variable spacings for all characters of a pfFont. Possible values are PFFONT_CHAR_SPACING_FIXED and PFFONT_CHAR_SPACING_VARIABLE, the latter being the default.

PFFONT_NUM_CHARS—Specify how many characters are in this font.

PFFONT_RETURN_CHAR—Specify the index of the character that is considered a `return' character and thus relevant to line justification.

pfFontAttr()

Specify a particular attribute of this pfFont.

Valid attributes:

PFFONT_NAME—Name of this font.

PFFONT_GSTATE—pfGeoState to be used when rendering this font.

PFFONT_BBOX—Bounding box that bounds each individual character.

PFFONT_SPACING—Set the overall character spacing if this is a fixed width font (also the spacing used if one has not been set for a particular character).


Example 8-1. Loading Characters into a pfFont

/* Setting up a pfFont */
pfFont *ReadFont(void)
{
    pfFont *fnt = pfNewFont(pfGetSharedArena());
    for(i=0;i<numCharacters;i++)
    {
        pfGeoSet* gset = getCharGSet(i);
        pfVec3* spacing = getCharSpacing(i);
        
        pfFontCharGSet(fnt, i, gset);
        pfFontCharSpacing(fnt, i, spacing);
    }
}


pfString

Simple rendering of 3D text can be done using a pfString. A pfString is an array of font indices stored as 8-bit bytes, 16-bit shorts, or 32-bit integers. Each element of the array contains an index to a particular character of a pfFont structure. A pfString can not be drawn until it has been associated with a pfFont object with a call to pfStringFont(). To render a pfString once it references a pfFont, call the function pfDrawString().

The pfString class supports the notion of `flattening' to trade off memory for faster processing time. This causes individual, noninstanced geometry to be used for each character, eliminating the cost of translating the text cursor between each character when drawing the pfString.

Example 8-2 illustrates how to set up and draw a pfString.

Example 8-2. Setting Up and Drawing a pfString

/* Create a string a rotate it for 2.5 seconds */
void
LoadAndDrawString(const char *text)
{
    pfFont *myfont = ReadMyFont();
    pfString *str = pfNewString(NULL);
    pfMatrix mat;
    float start,t;
    
    /* Use myfont as the 3-d font for this string */
    pfStringFont(str, fnt);
    
    /* Center String */
    pfStringMode(str, PFSTR_JUSTIFY, PFSTR_MIDDLE);
    
    /* Color String is Red */
    pfStringColor(str, 1.0f, 0.0f, 0.0f, 1.0f);
    
    /* Set the text of the string */
    pfStringString(str, text);
    
    /* Obtain a transform matrix to place this string */
    GetTheMatrixToPlaceTheString(mat);
    pfStringMat(str, &mat);
    
    /* optimize for draw time by flattening the transforms */
    pfFlattenString(str);
    
    /* Twirl text for 2.5 seconds */
    start = pfGetTime();
    do
    {
        pfVec4 clr;
        pfSetVec4(clr, 0.0f, 0.0f, 0.0f, 1.0f);
        
        /* Clear the screen to black */
        pfClear(PFCL_COLOR|PFCL_DEPTH, clr);
        
        t = (pfGetTime() - start)/2.5f;
        t = PF_MIN2(t, 1.0f);
        
        pfMakeRotMat(mat, t * 315.0f, 1.0f, 0.0f, 0.0f);
        pfPostRotMat(mat, mat, t * 720.0f, 0.0f, 1.0f, 0.0f);
        
        t *= t;
        pfPostTransMat(mat, mat, 0.0f, 
            150.0f * t + (1.0f - t) * 800.0f, 0.0f);
    
        pfPushMatrix();
        pfMultMatrix(mat);
        
        /* DRAW THE INPUT STRING */
        pfDrawString(str);
        
        pfPopMatrix();
        
        pfSwapWinBuffers(pfGetCurWin());
    } while(t < 2.5f);
}

Table 8-6 lists the key routines used to manage pfStrings.

Table 8-6. pfString Routines

Routine

Description

pfNewString()

Create a new pfString.

pfDelete()

Delete a pfString.

pfStringFont()

Set the pfFont to use when drawing this pfString.

pfStringString()

Set the character array that this pfString will represent or render.

pfDrawString()

Draw this pfString.

pfFlattenString()

Flatten all positional translations and the current specification matrix into individual pfGeoSets so that more memory is used, but no matrix transforms or translates have to be done between each character of the pfString.

pfStringColor()

Set the color of the pfString.

pfStringMode()

Specify a particular mode for this pfString.

Valid modes:

PFSTR_JUSTIFY — Sets the line justification and has the following possible values: PFSTR_FIRST or PFSTR_LEFT, PFSTR_MIDDLE or PFSTR_CENTER, and PFSTR_LAST or PFSTR_RIGHT.

PFSTR_CHAR_SIZE — Sets the number of bytes per character in the input string and has the following possible values: PFSTR_CHAR, PFSTR_SHORT, PFSTR_INT.

pfStringMat()

Specify a transform matrix that affects the entire character string when the pfString is drawn.

pfStringSpacingScale()

Specify a scale factor for the escapement translations that happen after each character is drawn. This routine is useful for changing the spacing between characters and even between lines.