Chapter 2. Setting Up the Display Environment

You can use the library libpf or libpfv as your base to build your application. For the most part, this chapter (and guide) shows how to do so with libpf. For a more modular approach using a graphical viewer, see Chapter 25, “Building a Visual Simulation Application Using libpfv”.

The library libpf is a visual-database processing and rendering system. The visual database has at its root a pfScene (as described in Chapter 3, “Nodes and Node Types” and Chapter 4, “Database Traversal”). The chain of events necessary to proceed from the scene graph to the display includes the following:

  1. A pfScene is viewed by a pfChannel.

  2. The pfChannel view of the pfScene is rendered by a pfPipe into a framebuffer.

  3. A pfPipeWindow manages the framebuffer.

  4. The images in the framebuffer are transmitted to a display system that is managed by a pfPipeVideoChannel.

Figure 2-1 shows this chain of events.

Figure 2-1. From Scene Graph to Visual Display

From Scene Graph to Visual Display

The following sections describe how to implement this chain of events using pfPipes, pfPipeWindows, and pfChannels directly or through the use of a configuration file:

Using Pipes

This section describes rendering pipelines (pfPipes) and their implementation in OpenGL Performer. Each rendering pipeline draws into one or more windows (pfPipeWindows) associated with a single geometry pipeline. A minimum of one rendering pipeline is required, although it is possible to have more than one.

The Functional Stages of a Pipeline

This rendering pipeline comprises three primary functional stages:

APP 

Simulation processing, which includes reading input from control devices, simulating the vehicle dynamics of moving models, updating the visual database, and interacting with other networked simulation stations.

CULL 

Traverses the visual database and determines which portions of it are potentially visible (a procedure known as culling), selects a level of detail (LOD) for each model, sorts objects and optimizes state management, and generates a display list of objects to be rendered.

DRAW 

Traverses the display list and issues graphics library commands to a Geometry Pipeline in order to create an image for subsequent display.

Figure 2-2 shows the process flow for a single-pipe system. The application constructs and modifies the scene definition (a pfScene) associated with a channel. The traversal process associated with that channel's pfPipe then traverses the scene graph, building an OpenGL Performer libpr display list. As shown in the figure, this display list is used as input to the draw process that performs the actual graphics library actions required to draw the image.

Figure 2-2. Single Graphics Pipeline

Single Graphics Pipeline

OpenGL Performer also provides additional processes for application processing tasks, such as database loading and intersection traversals, but these processes are optinal and are asynchronous to the software rendering pipeline(s).

An OpenGL Performer application renders images using one or more pfPipes. Each pfPipe represents an independent software-rendering pipeline. Most IRIS systems contain only one Geometry Pipeline; so, a single pfPipe is usually appropriate. This single pipeline is often associated with a window that occupies the entire display surface.

Alternative configurations include Onyx3 systems with InfiniteReality3 graphics (allowing up to 16 Geometry Pipelines). Applications can render into multiple windows, each of which is connected to a single Geometry Pipeline through a pfPipe rendering pipeline.

Figure 2-3 shows the process flow for a dual-pipe system. Notice both the differences and similarities between these two figures. Each pipeline (pfPipe) is independent in multiple-pipe configurations; the traversal and draw tasks are separate, as are the libpr display lists that link them. In contrast, these pfPipes are controlled by the same application process, and in many situations access the same shared scene definition.

Figure 2-3. Dual Graphics Pipeline

Dual Graphics Pipeline

Each of these stages can be combined into a single process or split into multiple processes (pfMultiprocess) for enhanced performance on multiple CPU systems. Multiprocessing and multiple pipes are advanced topics that are discussed in “Successful Multiprocessing with OpenGL Performer” in Chapter 5.

Creating and Configuring a pfPipe

pfPipes and their associated processes are created when you call pfConfig(). They exist for the duration of the application. After pfConfig(), the application can get handles to the created pfPipes using pfGetPipe(). The argument to pfGetPipe() indicates which pfPipe to return and is an integer between 0 and numPipes-1, inclusive. The pfPipe handle is then used for further configuration of the pfPipe.

pfMultipipe() specifies the number of pfPipes desired; the default is one. pfMultiprocess() specifies the multiprocessing mode used by all pfPipes. These two routines are discussed further in “Successful Multiprocessing with OpenGL Performer” in Chapter 5.

A key part of pfPipe initialization is the determination of the graphics hardware pipeline (or screen) and the creation of a window on that screen. The screen of a pfPipe can be set explicitly using pfPipeScreen(). Under single pipe operation, pfPipes can also inherit the screen of their first opened window. Under multipipe operation, the screen of all pfPipes must be determined before the pipes are configured by pfConfigStage() or the first call to pfFrame(). There may be other operations that require preset knowledge of the screen even under single pipes, such as custom configuration of video channels, discussed in “Creating and Configuring a pfChannel”.

Once the screen of a pfPipe has been set, it cannot be changed. All windows of a given pfPipe must be opened on the same screen. A graphics window is associated with a pfPipe through the pfPipeWindow mechanism. If you do not create a pfPipeWindow, OpenGL Performer will automatically create and open a full screen window with a default configuration for your pfPipe.

Once you create and initialize a pfPipe, you can query information about its configuration parameters. pfGetPipeScreen() returns the index number of the hardware pipeline for the pfPipe, starting from zero. On single-pipe systems the return value will be zero. If no screen has been set, the return value will be (-1). pfGetPipeSize() returns the full screen size, in pixels, of the rendering area associated with a pfPipe.

You may have application states associated with pfPipe stages and processes that need special initialization. For this purpose, you may provide a stage configuration callback for each pfPipe stage using pfStageConfigFunc(pipe, stageMask, configFunc) and specify the pfPipe, the stage bitmask (including one or more of PFPROC_APP, PFPROC_CULL, and PFPROC_DRAW), and your stage configuration callback routine. At any time, you may call the function pfConfigStage() from the application process to trigger the execution of your stage configuration callback in the process associated with that pfPipe's stage. The stage configuration callback will be invoked at the start of that stage within the current frame (the current frame in the application process, and subsequent frames through the cull and draw phases of the software rendering pipeline). Use a pfStageConfigFunc() callback function to configure OpenGL Performer processes not associated with pfPipes, such as the database process, PFPROC_DBASE, and the intersection process, PFPROC_ISECT. A common process initialization task for real-time applications is the selection and/or specification of a CPU on which to run.

Example of pfPipe Use

The sample source code shipped with OpenGL Performer includes several simple examples of pfPipe use in both C and C++. Specifically, look at the following examples under the C and C++ directories in /usr/share/Performer/src/pguide/libpf for IRIX and Linux and in %PFROOT%\Src\pguide\libpfc for Microsoft Windows, such as hello.c, simple.c, and multipipe.c.

Example 2-1 illustrates the basics of using pipes. The code in this example is adapted from OpenGL Performer sample programs.

Example 2-1. pfPipes in Action

main()
{
     int i;
 
     /* Initialize OpenGL Performer */
     pfInit();
    /* Set number of pfPipes desired -- THIS MUST BE DONE
     * BEFORE CALLING pfConfig().
     */
    pfMultipipe(NumPipes);
    /* set multiprocessing mode */
    pfMultiprocess(PFMP_DEFAULT);
    ...
    /* Configure OpenGL Performer and fork extra processes if 
     * configured for multiprocessing.
     */
    pfConfig();
     ...
 
    /* Optional custom mapping of pipes to screens. 
     * This is actually the reverse as the default.
     *//
     for (i=0; i < NumPipes; i++)
          pfPipeScreen(pfGetPipe(i), NumPipes-(i+1));
    {
        /* set up optional DRAW pipe stage config callback */
        pfStageConfigFunc(-1 /* selects all pipes */, 
                      PFPROC_DRAW /* stage bitmask */, 
                      ConfigPipeDraw /* config callback */);
        /* Config func should be done next pfFrame */
        pfConfigStage(i, PFPROC_DRAW);
    }
    InitChannels();
    ...
    /* trigger the configuration and opening of pfPipes
     * and pfWindows
     */
    pfFrame();
 
    /* Application's simulation loop */
    while(!SimDone())
    {
    ...
    }
}
 
/* CALLBACK FUNCTIONS FOR PIPE STAGE INITIALIZATION */
void
ConfigPipeDraw(int pipe, uint stage)
{
/* Application state for the draw process can be initialized
 * here. This is also a good place to do real-time
 * configuration for the drawing process, if there is one.
 * There is no graphics state or pfState at this point so no
 * rendering calls or pfApply*() calls can be made.
 */
		    pfPipe *p = pfGetPipe(pipe);
    pfNotify(PFNFY_INFO, PFNFY_PRINT,
       “Initializing stage 0x%x of pipe %d”, stage, pipe); 
}


Using Channels

This section describes how to use pfChannels. A pfChannel is a view of a scene. A pfChannel is a required element for an OpenGL Performer application because it establishes the visual frame of reference for what is rendered in the drawing process.

Creating and Configuring a pfChannel

When you create a new pfChannel, it is attached to a pfPipe for the duration of the application. The pfPipe renders the pfScene viewed by the pfChannel into a pfPipeWindow that is managed by that pipe. Use pfNewChan() to create a new pfChannel and assign it to a pfPipe. pfChannels are automatically assigned to the first pfPipeWindow of the pfPipe. In the sample program, the following statement creates a new channel and assigns it to pipe p.

chan = pfNewChan(p);

The pfChannel is automatically placed in the first pfPipeWindow of the pfPipe. A pfPipeWindow is created automatically if one is not explicitly created with pfNewPWin().

The simplest configuration uses one pipe, one channel, and one window. You can use multiple channels in a single pfPipeWindow on a pfPipe, thereby allowing channels to share hardware resources. Using multiple channels is an advanced topic that is discussed in the section of this chapter on “Using Multiple Channels”. For now, focus your attention on understanding the concepts of setting up and using a single channel.

The primary function of a pfChannel is to define the view of a scene. A view is fully characterized by a viewport, a viewing frustum, and a viewpoint. The following sections describe how to set up the scene and view for a pfChannel.

Setting Up a Scene

A pfChannel draws the pfScene set by pfChanScene(). A channel can draw only one scene per frame but can change scenes from frame to frame. Other pfChannel attributes such as LOD modifications, described in “pfLOD Nodes” in Chapter 3, affect the scene.

A pfChannel also renders an environmental model known as pfEarthSky. A pfEarthSky defines the method for clearing the channel viewport before rendering the pfScene and also provides environmental effects, including ground and sky geometry and fog and haze. A pfEarthSky is attached to a pfChannel by pfChanESky().

Setting Up a Viewport

A pfChannel is rendered by a pfPipe into its pfPipeWindow. The screen area that displays a pfChannel's view is determined by the origin and size of the window and the channel viewport specified by pfChanViewport. The channel viewport is relative to the lower left corner of the window and ranges from 0 to 1. By default, a pfChannel viewport covers the entire window.

Suppose that you want to establish a viewport that is one-quarter of the size of the window, located in the lower left corner of the window. Use pfChanViewport(chan, 0.0, 0.25, 0.0, 0.25) to set up the one-quarter window viewport for the channel chan.

You can then set up other channels to render to the other three-quarters of the window. For example, you can use four channels to create a four-way view for an architectural or CAD application. See “Using Multiple Channels” to learn more about multiple channels.

Setting Up a Viewing Frustum

A viewing frustum is a truncated pyramid that defines a viewing volume. Everything outside this volume is clipped, while everything inside is projected onto the viewing plane for display. A frustum is defined by the following:

  • field–of–view (FOV) in the horizontal and vertical dimensions

  • near and far clipping planes 

A viewing frustum is created by the intersections of the near and far clipping planes with the top, bottom, left, and right sides of the infinite viewing volume formed by the FOV and aspect ratio settings. The aspect ratio is the ratio of the vertical and horizontal dimensions of the FOV.

Figure 2-4 shows the parameters that define a symmetric viewing frustum. To establish asymmetric frusta refer to the pfChannel(3pf) or pfFrustum(3pf) man pages for further details.

Figure 2-4. Symmetric Viewing Frustum

Symmetric Viewing Frustum

The viewing frustum is called symmetric when the vertical half-angles are equal and the horizontal half-angles are equal.

Field–of–View

The FOV is the angular width of view. Use pfChanFOV(chan, horiz, vert) to set up viewing angles in OpenGL Performer. The quantities horiz and vert are the total horizontal and vertical fields of view in degrees; usually you specify one and let OpenGL Performer compute the other. If you are specifying one angle, pass any amount less than or equal to zero, or greater than or equal to 180, as the other angle. OpenGL Performer automatically computes the unspecified FOV angle to fit the pfChannel viewport using the aspect-ratio preserving relationship

tan(vert/2) / tan(horiz/2) = aspect ratio

That is, the ratio of the tangents of the vertical and horizontal half-angles is equal to the aspect ratio. For example, if horiz is 45 degrees and the channel viewport is twice as wide as it is high (so the aspect ratio is 0.5), then the vertical field-of-view angle, vert, is computed to be 23.4018 degrees. If both angles are unspecified, pfChanFOV() assumes a default value of 45 degrees for horiz and computes the value of vert as described.

Clipping Planes

Clipping planes define the near and far boundaries of the viewing volume. These distances describe the extent of the visual range in the view, because geometry outside these boundaries is clipped, meaning that it is not drawn.

Use pfChanNearFar(chan, near, far) to specify the distance along the line of sight from the viewpoint to the near and far planes that bound the viewing volume. These clipping planes are perpendicular to the line of sight. For the best visual acuity, choose these distances so that near is as far away as possible from the viewpoint and far is as close as possible to the viewpoint. Minimizing the range between near and far provides more resolution for distance comparisons and fog computations.

Setting Up a Viewpoint

A viewpoint describes the position and orientation of the viewer. It is the origin of the viewing location, the direction of the line of sight from the viewer to the scene being viewed, and an up direction. The default viewpoint is at the origin (0, 0, 0) looking along the +Y axis, with +Z up and +X to the right.

Use pfChanView(chan, point, dir) to define the viewpoint for the pfChannel identified by chan. Specify the view origin for point in x, y, z world coordinates. Specify the view direction for dir in degrees by giving the degree measures of the three  Euler angles: heading, pitch, and roll.

Heading is a rotation about the Z axis, pitch is a rotation about the X axis, and roll is a rotation about the Y axis. The value of dir is the product of the rotations ROTy(roll) * ROTx(pitch) * ROTz(heading), where ROTa(angle) is a rotation matrix about axis A of angle degrees.

Angles have not only a degree value, but also a sense, + or –, indicating whether the direction of rotation is clockwise or counterclockwise. Because different systems follow different conventions, it is very important to understand the sense of the Euler angles as they are defined by OpenGL Performer. OpenGL Performer follows the right-hand rule. According to the right-hand rule, counterclockwise rotations are positive. This means that a rotation about the X axis by +90 degrees shifts the +Y axis to the +Z axis, a rotation about the Y axis by +90 degrees shifts the +Z axis to the +X axis, and a rotation about the Z axis by +90 degrees shifts the +X axis to the +Y axis.

Figure 2-5 shows a toy plane (somewhat reminiscent of the Ryan S-T) at the origin of a coordinate system with the angles of rotation labeled for heading, pitch, and roll. The arrows show the direction of positive rotation for each angle.

Figure 2-5. Heading, Pitch, and Roll Angles

Heading, Pitch, and Roll Angles

A roll motion tips the wings from side to side. A pitch motion tips the nose up or down. Changing the heading, a yaw motion steers the plane. Accurate readings of these angles are critical information for a pilot during a flight, and a thorough understanding of how the angles function together is required for creation of an accurate flight simulation visual with OpenGL Performer. The same is also true of marine and other vehicle simulations.

Alternatively, you can use pfChanViewMat(chan, mat) to specify a 4x4 homogeneous matrix mat that defines the view coordinate system for channel chan. The upper left 3x3 submatrix defines the coordinate system axes, and the bottom row vector defines the origin of the coordinate system. The matrix must be orthonormal, or the results will be undefined. You can construct matrices using tools in the libpr library.

The origin and heading, pitch, and roll angles, or the view matrix, create a complete view specification. The view specification can locate the eyepoint frame-of-reference origin at any point in world coordinates. The gaze vector, the eye's +Y axis, can point in any direction. The up vector, the eye's +Z axis, can point in any direction perpendicular to the gaze vector.

You can query the system for the view and eyepoint-direction values with pfGetChanView(), or obtain the view matrix directly with pfGetChanViewMat().

The view direction can be modified by one or more offsets, relative to the eyepoint frame-of-reference. View offsets are useful in situations where several channels render the same scene into adjacent displays for a wider field–of–view or higher resolution. Offsets are also used for multiple viewer perspectives, such as pilot and copilot views.

Use pfChanViewOffsets(chan, xyz, hpr) to specify additional translation and rotation offsets for the viewpoint and direction; xyz specifies a translation vector and hpr specifies a heading/pitch/roll rotation vector. Viewing offsets are automatically added each frame to the view direction specified by pfChanView() or pfChanViewMat().

For example, to create three different perspectives of the same scene as displayed by three windows in an airplane cockpit, use azimuth offsets of 45, 0, and -45 for left, middle, and right views. To create vertical view groups such as might be seen through the windscreen of a helicopter, use both azimuth and elevation offsets. Once the view offsets have been set up, you need only set the view once per frame. View offsets are applied after the eyepoint position and gaze direction have been established. As with the other angles, be aware that the conventions for measuring azimuth and elevation angles vary between graphics systems; so, you should verify that the sense of the angles is correct.

Example of Channel Use

Example 2-2 shows how to use various pfChannel-related functions. The code is derived from OpenGL Performer sample programs.

Example 2-2. Using pfChannels

main()
{
    pfInit();
    ...
    pfConfig();
    ...
    InitScene();
    InitPipe();
    InitChannel();
 
    /* Application main loop */
    while(!SimDone())
    {
        ...
    }
}
 
void InitChannel(void)
{
    pfChannel *chan;
    chan = pfNewChan(pfGetPipe(0));
 
    /* Set the callback routines for the pfChannel */
    pfChanTravFunc(chan, PFTRAV_CULL, CullFunc);
    pfChanTravFunc(chan, PFTRAV_DRAW, DrawFunc);
 
    /* Attach the visual database to the channel */
    pfChanScene(chan, ViewState->scene);
 
    /* Attach the EarthSky model to the channel */
    pfChanESky(chan, ViewState->eSky);
 
    /* Initialize the near and far clipping planes */
    pfChanNearFar(chan, ViewState->near, ViewState->far);
 
    /* Vertical FOV is matched to window aspect ratio. */
    pfChanFOV(chan, 45.0f/NumChans, -1.0f);
 
    /* Initialize the viewing position and direction */
    pfChanView(chan, ViewState->initView.xyz,
               ViewState->initView.hpr);
}
 
/* CULL PROCESS CALLBACK FOR CHANNEL*/
/* The cull function callback.  Any work that needs to be
 * done in the cull process should happen in this function.
 */
void
CullFunc(pfChannel * chan, void *data)
{
    static long first = 1;
 
    if (first)
    {
        if ((pfGetMultiprocess() & PFMP_FORK_CULL) &&
            (ViewState->procLock & PFMP_FORK_CULL))
                pfuLockDownCull(pfGetChanPipe(chan));
        first = 0;
    }
    PreCull(chan, data);
 
    pfCull();               /* Cull to the viewing frustum */
 
    PostCull(chan, data);
}

/* DRAW PROCESS CALLBACK FOR CHANNEL*/
/* The draw function callback. Any graphics functionality 
 * outside OpenGL Performer must be done here. 
 */
void
DrawFunc(pfChannel *chan, void *data)
{
    PreDraw(chan, data);     /* Clear the viewport, etc. */
 
    pfDraw();                /* Render the frame */
 
    /* draw HUD, or whatever else needs
     * to be done post-draw.
     */
    PostDraw(chan, data);
}


Controlling the Video Output


Note: This is an advanced topic.

You use pfPipeVideoChannel to query and control the configuration of a hardware video channel. The methods allow you to, for example, query or specify the origin and size of the video output and scale the display.

By default, all pfVideoChannels on a pfPipe use the first entire video channel on the screen selected by the pfPipe. Each pfPipeWindow initially has a default pfPipeVideoChannel already assigned to it. When pfChannels are added to pfPipeWindows, they will be using, by default, this first pfPipeVideoChannel. You can get a pfPipeVideoChannel of a pfPipeWindow with pfGetPWinPVChan() and specifying the index of the pfPipeVideoChannel on the pfPipeWindow; the initial default one will be at index 0. You can then reconfigure this pfPipeVideoChannel to select a different video channel or change the attributes of the selected video channel. You can create a pfPipeVideoChannel with pfNewPVChan(). To use this for a given pfChannel, you must add it to a pfPipeWindow that will cover the screen area of the desired video channel. When a pfPipeVideoChannel is added to a pfPipeWindow with pfAddPWinPVChan(), the index into the pfPipeWindow list of video channels is returned and by default the pfPipeVideoChannel will get the next active hardware video channel after the previous pfPipeVideoChannel on that pfPipeWindow. You can explicitly select the hardware video channel with  pfPVChanId(). The pfChannel will then reference this pfPipeVideoChannel through the index that you got back from pfAddPWinPVChan() and assign to the pfChannel with pfChanPWinPVChanIndex().

pvc = pfNewPVChan(p);
pvcIndex = pfAddPWinPVChan(pw, pvc);
pfChanPWinPVChanIndex(chan, pvcIndex);

Note that the screen of the pfPipe must be known to fully specify the desired video channel. Queries on the pfPipeVideoChannel will return values indicating unknown configuration until the screen is known. The screen can be determined by OpenGL Performer when the window is opened in the DRAW process but you can also explicitly set the screen of the pfPipe with pfPipeScreen().

You can also get to the hardware video channel structure, pfVideoChannelInfo(), for more configuration options, such as reading gamma data or even a specific video format. For more information on pfPipeWindows and pfPipeVideoChannels, see Chapter 17, “pfPipeWindows and pfPipeVideoChannels”.

Using Multiple Channels

Each rendering pipeline can render multiple channels with multiple pfPipeVideoChannels to a single pfPipeWindows. Multiple pfPipeWindows can also be used but at the cost of some additional processing overhead. The pfChannel is assigned to the proper pfPipeWindow and selects its pfPipeVideoChannel from that pfPipeWindow. The pfChannel must also have a viewport, set with pfChanViewport(), that covers the proper window area to match that of the desired pfPipeVideoChannel.

Each channel represents an independent viewpoint into either a shared or an independent visual database. Different types of applications can have vastly different pipeline-window-channel configurations. This section describes two extremes: visual simulation applications, where there is typically one window per pipeline, and highly interactive uses that require dynamic window and channel configuration.

One Window per Pipe, Multiple Channels per Window

Often there is a single channel associated with each pipeline, as shown in the top half of Figure 2-6. This section describes two important uses for multiple-channel support—multiple pipelines per system and multiple windows per pipeline—the second of which is illustrated in the bottom half of Figure 2-6.

Figure 2-6. Single-Channel and Multiple-Channel Display

Single-Channel and Multiple-Channel Display

One situation that requires multiple channels occurs when inset views must appear within an image. A simple example of this application is a driving simulator in which the screen image represents the view out the windshield. If a rear-view mirror is to be drawn, it must overlay the main forward view to provide a separate view of the same database within the borders of the simulated mirror's frame.

Channels are rendered in the order that they are assigned to a pfPipeWindow on their parent pfPipe. Channels, upon creation, are assigned to the end of the channel list of the first window of their pfPipe. In the driving simulator example, creating pipes and channels with the following structure creates two channels on a single shared pipeline:

pipeline = pfGetPipe(0);
frontView = pfNewChan(pipeline);
rearView = pfNewChan(pipeline);

In this case, OpenGL Performer's actual drawing order becomes the following:

  1. Clear frontView.

  2. Draw frontView.

  3. Clear rearView.

  4. Draw rearView.

This default ordering results in the rear-view mirror image always overlaying the front-view image, as desired. You can control and reorder the drawing of channels within a pfPipeWindow with the pfInsertChan(pwin, where, chan) and pfMoveChan(pwin, where, chan) routines. More details about multiple channels and multiple window are discussed in the next section.

When the host has multiple Geometry Pipelines, as supported on Onyx RealityEngine2 and InfiniteReality systems, you can create a pfPipe and pfChannel pair for each hardware pipeline. The following code fragment illustrates a two-channel, two-pipeline configuration:

leftPipe = pfGetPipe(0);
leftView = pfNewChan(leftPipe);
rightPipe = pfGetPipe(1);
rightView = pfNewChan(rightPipe);

This configuration forms the basis for a high-performance stereo display system, since there is a hardware pipeline dedicated to each eye and rendering occurs in parallel.

The two-channel stereo-view application described in this example and the inset-view application described in the previous example can be combined to provide stereo views for a driving simulator with an inset rear-view mirror. The correct management of each eye's viewpoint and the mirror reflection helps provide a convincing sense of physical presence within the vehicle.

The third and most common multiple-channel situation involves support for multiple video outputs per pipeline. To do this, first associate each pipeline with a set of nonoverlapping channels, one for each desired view. Next, use one of the following video-splitting methods:

  • Use the multi-channel hardware options, available from SGI, for systems such as the 8-channel Display Generator (DG) for InfiniteReality graphics, where you can create up to eight independent video outputs from a single Graphics Pipeline, with each video output corresponding to one of the tiled channels. The Octane video option supports four video outputs and the RealityEngine2 MultiChannel Option supports six video channels per Graphics Pipeline.

  • Connect multiple video monitors in series to a single pipeline's video output. Because each monitor receives the same display image, a masking bezel is used to obscure all but the relevant portion of each display surface.

The three multiple-channel concepts described here can be used in combination. For example, use of three InfiniteReality pipelines, each equipped with the 8-channel DG , allows creation of up to 24 independent video displays. The channel-tiling method can also be used for some or all of these displays.

Example 2-3 shows how to use multiple channels on separate pipes.

Example 2-3. Multiple Channels, One Channel per Pipe

pfChannel *Chan[MAX_CHANS];
 
void InitChannel(int NumChans)
{
    /* Initialize each channel on a separate pipe */
    for (i=0; i< NumChans; i++)
        Chan[i] = pfNewChan(pfGetPipe(i));
 
    ...
 
    /* Make channel n/2 the master channel (can be any
     * channel).
     */
    ViewState->masterChan = Chan[NumChans/2];
 
    {
        long share;
 
        /* Get the default channel-sharing mask */
        share = pfGetChanShare(ViewState->masterChan);
 
        /* Add in the viewport share bit */
        share |= PFCHAN_VIEWPORT;
 
        if (GangDraw)
        {
            /* add GangDraw to channel share mask */
            share |= PFCHAN_SWAPBUFFERS_HW;
        }
        pfChanShare(ViewState->masterChan, share);
    }
 
    /* Attach channels */
    for (i=0; i< NumChans; i++)
        if (Chan[i] != ViewState->masterChan)
            pfAttachChan(ViewState->masterChan, Chan[i]);
 
    ...
    /* Continue with channel initialization */
}


Using Channel Groups

In many multiple-channel situations, including the examples described in the previous section, it is useful for channels to share certain attributes. For the three-channel cockpit scenario, each pfChannel shares the same eyepoint while the left and right views are offset using pfChanViewOffsets(). OpenGL Performer supports the notion of channel groups, which facilitate attribute sharing between channels.

pfChannels can be gathered into channel groups that share like attributes. A channel group is created by attaching one pfChannel to another, or to an existing channel group. Use pfAttachChan() to create a channel group from two channels or to add a channel to an existing channel group. Use pfDetachChan() to remove a pfChannel from a channel group.

A channel share mask defines shared attributes for a channel group. The attribute tokens listed in Table 2-1 are bitwise OR-ed to create the share mask.

Table 2-1. Attributes in the Share Mask of a Channel Group

Token

Shared Attributes

PFCHAN_FOV

Horizontal and vertical fields of view

PFCHAN_VIEW

View position and orientation

PFCHAN_VIEW_OFFSETS

(x, y, z) and (heading, pitch, roll) offsets of the view direction

PFCHAN_NEARFAR

Near and far clipping planes

PFCHAN_SCENE

All channels display the same scene.

PFCHAN_EARTHSKY

All channels display the same earth/sky model.

PFCHAN_STRESS

All channels use the same stress filter.

PFCHAN_LOD

All channels use the same LOD modifiers.

PFCHAN_SWAPBUFFERS

All channels swap buffers at the same time.

PFCHAN_SWAPBUFFERS_HW

Synchronize swap buffers for channels on different graphics pipelines.

Use pfChanShare() to set the share mask for a channel group. By default, channels share all attributes except PFCHAN_VIEW_OFFSETS. When you add a pfChannel to a channel group, it inherits the share mask of that group.

A change to any shared attribute is applied to all channels in a group. For example, if you change the viewpoint of a pfChannel that shares PFCHAN_VIEW with its group, all other pfChannels in the group will acquire the same viewpoint.

Two attributes are particularly important to share in adjacent-display multiple-channel simulations: PFCHAN_SWAPBUFFERS and PFCHAN_LOD. PFCHAN_LOD ensures that geometry that straddles displays is drawn the same way in each channel. In this case, all channels will use the same LOD modifier when rendering their scenes so that LOD behavior is consistent across channels. PFCHAN_SWAPBUFFERS ensures that channels refresh the display with a new frame at the same time. pfChannels in different pfPipes that share PFCHAN_SWAPBUFFERS_HW will frame-lock the graphics pipelines together.

Example 2-4 illustrates the use of multiple channels and channel sharing.

Example 2-4. Channel Sharing

pfChannel *Chan[MAX_CHANS];
 
main()
{
    pfInit();
    ...
    /* Set number of pfPipes desired.  THIS MUST BE DONE
     * BEFORE CALLING pfConfig().
     */
    pfMultipipe(NumPipes);
    ...
    pfConfig();
    ...
    InitScene();
 
    InitChannels();
 
    pfFrame();
 
    /* Application main loop */
    while(!SimDone())
    {
        ...
    }
}
 
void InitChannel(int NumChans)
{
    /* Initialize all channels on pipe 0 */
    for (i=0; i< NumChans; i++)
        Chan[i] = pfNewChan(pfGetPipe(0));
 
    ...
 
    /* Make channel n/2 the master channel (can be any
     * channel).
     */
    ViewState->masterChan = Chan[NumChans/2];
 
    ...
 
    /* Attach all Channels as slaves to the master channel */
    for (i=0; i< NumChans; i++)
        if (Chan[i] != ViewState->masterChan)
            pfAttachChan(ViewState->masterChan, Chan[i]);
 
    pfSetVec3(xyz, 0.0f, 0.0f, 0.0f);
    /* Set each channel's viewing offset. In this case use
     * many channels to create one multichannel contiguous
     * frustum with a 45˚ field of view.
     */
    for (i=0; i < NumChans; i++)
    {
        float fov = 45.0f/NumChans;
 
        pfSetVec3(hpr, (((NumChans - 1) * 0.5f) - i) * fov,
                  0.0f, 0.0f);
        pfChanViewOffsets(Chan[i], xyz, hpr);
    }
 
    ...
 
    /* Now, just configure the master channel and all of the
     * other channels will share those attributes.
     */
 
    chan = ViewState->masterChan;
    pfChanTravFunc(chan, PFTRAV_CULL, CullFunc);
    pfChanTravFunc(chan, PFTRAV_DRAW, DrawFunc);
    pfChanScene(chan, ViewState->scene);
    pfChanESky(chan, ViewState->eSky);
    pfChanNearFar(chan, ViewState->near, ViewState->far);
    pfChanFOV(chan, 45.0f/NumChans, -1.0f);
    pfChanView(chan, ViewState->initView.xyz,
               ViewState->initView.hpr);
    ...
}


Multiple Channels and Multiple Windows

For some interactive applications, you may want to be able to dynamically control the configuration of channels and windows. OpenGL Performer allows you to dynamically create, open, and close windows. You can also move channels among the windows of the shared parent pfPipe, and reorder channels within a pfPipeWindow. Channels can be appended to the end of a pfPipeWindow channel list with pfAddChan() and removed with pfRemoveChan(). A channel can only be attached to one pfPipeWindow — no instancing of pfChannels is allowed. When a pfChannel is put on a pfPipeWindow, it is automatically deleted from its previous pfPipeWindow. A channel that is not assigned to a pfPipeWindow is not drawn (though it may still be culled).

You can control and reorder the drawing of channels within a pfPipeWindow with the pfInsertChan(pwin, where, chan) and pfMoveChan(pwin, where, chan) routines. Both of these routines do a type of insertion: pfInsertChan() will add chan to the pwin channel list in front of the channel in the list at location where. pfMoveChan() will delete chan from its old location and move it to where in the pwin channel list.

On IRIX systems, if you have pfChannels in different pfPipeWindows or pfPipes that are supposed to combine to form a continuous scene, you will want to ensure that both the vertical retrace and double buffering of these windows is synchronized. This is required for both reasonable performance and visual quality. Use the genlock(7) system video feature to ensure that the vertical retraces of different graphics pipelines are synchronized. To synchronize double buffering, you want to either specify PFCHAN_SWAPBUFFERS_HW in the share mask of the pfChannels and put the pfChannels in a share group, or else create a pfPipeWindow swap group, discussed in Chapter 17, “pfPipeWindows and pfPipeVideoChannels”.

Importing OpenGL Multipipe SDK (MPK) Configuration Files

OpenGL Multipipe SDK (MPK) is a software package for managing a multipipe rendering environment. MPK uses a configuration file to describe the layout and hierarchy of pipes, windows, and channels used by an application. The manual SGI OpenGL Multipipe SDK User's Guide describes the format of the configuration file.

An OpenGL Performer application can import MPK configuration files and skip the explicit generation of pipes, windows, and channels. The library libpfmpk contains functions for importing and configuring pipes, windows, and channels from an MPK configuration file. The functions in libpfmpk store the display configuration information in a pfvDisplayMngr class for easy access by the application. The pfvDisplayMngr class is part of the pfvViewer implementation, which is described in Chapter 25, “Building a Visual Simulation Application Using libpfv”.

The pfMPKImportFile() function takes an MPK configuration filename and generates OpenGL Performer objects (pfPipes, pfPipeWindows, and pfChannels) accordingly. The function pfMPKImportConfig() is very similar. Instead of accepting a filename, it accepts an MPK configuration class MPKConfig. The result of both these functions is two-fold:

  • OpenGL Performer is configured with pipes, windows, and channels as specified in the MPK configuration file.

  • The pfvDisplayMngr class contains a description of the configured display topology (what pipe has what windows and what channels). It also contains pointers to all the newly generated OpenGL Performer classes (pfPipe, pfPipeWindow, and pfChannel).

The following is a code sample section for using the pfMPKImportFile() function:

// Initialize Performer
pfInit();

// Initialize the MultipipeSDK import library.
// No need to initialize MPK directly.
pfmpkInit();

// Import a MultipipeSDK file. This function calls pfConfig
// so we don't have to.
pfMPKImportFile(config_filename);

// Load a model file for display.
pfNode *root = pfdLoadFile(model_filename);

// Attach loaded file to a new pfScene
pfScene *scene = new pfScene;
scene->addChild(root);

// Create a pfLightSource and attach it to scene
scene->addChild(new pfLightSource);

// Get access to the results of the MultipipeSDK import.
// pfvDisplayMngr contains pointers to all the
// pipes/windows/channels that the MultipipeSDK file specified.
pfvDisplayMngr *dm = pfvDisplayMngr::getMngr();

// All configured channels share the scene graph so we only
// have to assign one channel.
pfChannel *chan = dm -> getChan(0) -> getHandle();
chan->setScene(scene);


Note: Since the pfvDisplayMngr class has no C API, you can only use libpfmpk from C++ programs.

Figure 2-1 contains a diagram of the various objects participating in any libpfmpk import operation.

Figure 2-7. The libpfmpk Import Operation

The libpfmpk Import Operation

Both functions pfMPKImportFile() and pfMPKImportConfig() encapsulate the entire OpenGL Performer configuration stage including the call to function pfConfig(). This may be too inflexible for some applications. An additional set of functions in libpfmpk provides lower-level access.

The following code sample shows the internal structure of function pfMPKImportConfig(). All calls that pfMPKImportConfig() makes are publicly accessible and an application can call them directly:

void pfMPKImportConfig(MPKConfig *cfg)
{
pfvDisplayMngr      *dm = pfvDisplayMngr::getMngr();
pfMPKImportInfo     info;

// Prepare temporary storage for pipe information.
info . numPipes = mpkConfigNPipes(cfg);
info . pipeInfo = (pfMPKImportPipeInfo *)
malloc (info.numPipes * sizeof (pfMPKImportPipeInfo));

// Translate contents of MPKConfig into pfvDisplayMngr terms.
pfMPKPreConfig(cfg, &info);

// Let pfvDisplayMngr run all its pre-pfConfig processing.
dm -> preConfig();

// Performer configuration: After this point, we can start
// creating Performer windows and channels.
pfConfig();

// Inquire pipe sizes, and configure all pfvDisplayMngr
// objects that depend on them.
pfMPKPostConfig(cfg, &info);

// Ask pfDisplayMngr to create all the windows/channels.
dm -> postConfig();

// Invoke any pfPipe/pfPipeWindow/pfChannel calls that
// pfDisplayMngr doesn't encapsulate.

pfMPKPostDMConfig(cfg, &info);
}

For completeness, the following is the source code for pfMPKImportFile():

void pfMPKImportFile(char *filename)
{
// Ask MPK to load the configuration file and pass to 
// pfMPKImportConfig
pfMPKImportConfig(mpkConfigLoad(filename));
}

The function pfMPKPreConfig() traverses the MPKConfig class and creates its pfvDisplayMngr equivalent. The function pfMPKPostConfig() patches the previous pfvDisplayMngr configuration using pipe size information. This information becomes available only after the call to pfConfig(); hence, patching cannot happen in pfMPKPreConfig().

The function pfMPKPostDMConfig() traverses the pfvDisplayMngr hierarchy one last time. This time pfvDisplayMngr already contains valid pointers to the OpenGL Performer classes it creates. The function pfMPKPostDMConfig() makes OpenGL Performer calls on the pfPipe, pfPipeWindow, and pfChannel pointers. Since pfvDisplayMngr does not encapsulate all configuration details, pfMPKPostDMConfig() makes these configuration calls directly on the new OpenGL Performer classes.