Chapter 21. Light Points

OpenGL Performer provides sophisticated light point objects:

Uses of Light Points

Light points are bright points of light that have the following characteristics:

  • intensity

  • directionality

  • attenuation shape

  • distance computation

  • attenuation through fog

  • size and fading

These attributes make light points excellent for use as stars, beacons, strobes, runway edge and end illumination, and taxiway lights.

For example, three light points with specific directionality may be used to create a VASI light system at an airport that appears as follows:

  • Red when the pilot is above the landing glide path.

  • Green when the pilot is below the landing glide path.

  • White when the pilot is on the landing glide path, as shown in Figure 21-1.

    Figure 21-1. VASI Landing Light

    VASI Landing Light

Creating a Light Point

To create a light point, do the following::

  1. Create a pfGeoSet of points (PFGS_POINTS):

    pfGeoSet *lpoint = pfNewGSet(arena);
    pfGSetPrimType(lpoint, PFGS_POINTS);
    

  2. Create a pfLPointState and define its mode and values:

    pfLPState *lpstate = pfNewLPState(arena);
     
    pfLPStateMode(lpstate, PFLPS_SIZE_MODE, PFLPS_SIZE_MODE_ON);
    pfLPStateVal(lpstate, PFLPS_SIZE_MIN_PIXEL, 0.25f);
    pfLPStateVal(lpstate, PFLPS_SIZE_ACTUAL, 0.07f);
    pfLPStateVal(lpstate, PFLPS_SIZE_MAX_PIXEL, 4.0f);
    

  3. Attach the pfLPointState to the pfGeoState associated with the pfGeoSet:

    pfGStateMode( gstate, PFSTATE_ENLPOINTSTATE, PF_ON);
    pfGStateAttr( gstate, PFSTATE_LPOINTSTATE, lpstate);
    pfGSetGState( gset, gstate ) ;
    

    This pfGeoSet must have a PFGS_COLOR4 attribute binding of PFGS_PER_VERTEX.

Setting the Behavior of Light Points

The pfLPointState attached to a pfGeoSet controls the behavior of all light points in the pfGeoSet. For example, the color, position, and direction vector of each light point is set by the color, vertex, and normal stored in the pfGeoSet.

You use pfLPStateMode, to enable or disable the behavior, and pfLPStateVal to set the following pfLPointState values:

  • Intensity—setting the intensity of light from no attenuation, 1, to fully attenuated, 0.

  • Directionality—specifying the direction and the shape of the emanation of light. The shapes are either a single or a pair of opposite-facing elliptical cones. pfLPStateShape() specifies the shape of the emanation.

  • Fading—specifying how the light point fades when receding into the distance. Fading is often more realistic than simply shrinking the point size to 0.

  • Fog punch-through—specifying how a light point is obscured by fog.

  • Size—setting the maximum and minimum size of the light point based on perspective.

The following sections describe how to set these values.

Intensity

The intensity of the light points in a pfLPointState can be attenuated using pfLPStateVal(), as follows:

pfLPStateVal(lpstate, PFLPS_INTENSITY, intensity)

The intensity value, intensity, must range between 0 and 1; 1, no attenuation, is the default.

The intensity of each light point is defined by its four component colors.

Directionality

Each light point has a normal, which defines the direction of its emanation. By default, all of the light points in a pfLPointState point in the same direction. That direction is defined as the “front” of the light point.

If all light points in a pfGeoSet share the same normal, use the PFGS_OVERALL attribute to optimize the light point computation.

If the light points are directional, the pfGeoSet attributes must specify normals.

Enabling Directionality

To enable or disable light point directionality, use the following method:

pfLPStateMode(lpstate, PFLPS_DIR_MODE, mode);

The mode value can be one of the following:

  • PFLPS_DIR_MODE_ON, the default, turns on directionality computation.

  • PFLPS_FOG_MODE_OFF disables directionality computation.

To specify whether or not the light point emanates towards the front and back, use the following method:

pfLPStateMode(PFLPS_SHAPE_MODE, mode);

The mode value can be one of the following:

  • PFLPS_SHAPE_MODE_UNI, the default, makes the light point not visible from the back side.

  • PFLPS_SHAPE_MODE_BI makes the light point bidirectional, the behavior is the same for both sides.

  • PFLPS_SHAPE_MODE_BI_COLOR makes the light point bidirectional, but if seen from the back side, the light change its color. This color is set using pfLPStateBackColor().

Emanation Shape

The emanation shape is an elliptical cone, as shown in Figure 21-2.

Figure 21-2. Attenuation Shape

Attenuation Shape

Optionally, the intensity of light can fall off from the normal to the edge of the cone.

The direction of emanation is defined along the Y axis and the shape is defined in the local coordinate system. The coordinate system is rotated so that the Y axis aligns with the normal of each light point.

To specify the shape of emanation, use the following method:

pfLPStateShape(lpstate, horiz, vert, roll, falloff, ambient)

Shape

horiz and vert are total-angles (not angles to the normal) in degrees, which specify the horizontal and vertical dimensions of the cone about the normal. The maximum value for these angles is 180 degrees, which creates a non-directional light point. The default values for horiz and vert is 90 degrees.


Tip: A symmetric cone (where horiz and vert are equal) is faster to compute than an asymmetric cone.


Rotation

The cone is rotated by roll degrees through +Y. The default roll value is 0.

Falloff

When the vector from the light's position to the eye point is outside the cone, the light point's intensity is ambient. The default ambient value is 0.

If the vector from the light's position to the eye point is within the cone, the intensity of the light point is based on the angle between the normal and that vector. The intensity values range from 1.0, when the eye point lies on the normal, to ambient, at the edge of the cone. How the light attenuates between the normal and the edge of the cone is specified by falloff.

falloff is an exponent that modifies the light point's intensity. A value of 0 indicates that there is no falloff; a value of 1, the default, indicates a linear falloff; values greater than 1 indicate a progressively greater geometric falloff, as shown in Figure 21-3.

Figure 21-3. Attenuation of Light

Attenuation of Light

Distance

The distance between the light point and the eye point is used when computing the light point's size and intensity through fog. Since these calculations are intensive, OpenGL Performer provides an approximation for the distance between the light point and the eye point using the following method:

pfLPState(lpstate, PFLPS_RANGE_MODE, mode)

The mode value can be one of the following:

  • PFLPS_RANGE_MODE_TRUE approximates the distance using the depth (Z) difference between the eye and the light point.

  • PFLPS_RANGE_MODE_DEPTH, the default, uses the real distance between the eye and the light point.


    Tip: The wider the field of vision (FOV), the less accurate is the approximation.


Attenuation through Fog

Light points are visible at greater distance than non-emissive polygons. For that reason, the light points appear to punch through fog. To account for this higher visibility, you can supply a punch-through value in the following method:

pfLPStateVal(PFLPS_FOG_SCALE, punch-through);

punch-through is a float that is multiplied times the distance. When punch-through is less than 1.0, the product of the distance and the punch-through value is less than the distance. The revised distance is used in calculating the apparent brightness of the light point; when the punch-through is less than 1.0, the light point appears brighter than it otherwise would. The default punch-through value is 0.25.

Enabling Punch-Through

To use punch-through, you must first enable it using the following method:

pfLPStateMode(lpstate, PFLPS_FOG_MODE, mode);

The mode value can be one of the following:

  • PFLPS_FOG_MODE_ON, the default, enables fog punch-through computation.

  • PFLPS_FOG_MODE_OFF does not modify the distance before fog is applied.

Size

Light points can exhibit perspective behavior. The following method sets the size of all the light points in a pfGeoSet:

pfLPStateVal(lpstate,PFLPS_SIZE_ACTUAL, real-size); 

real-size is a float that is multiplied times the distance. When real-size is less than 1.0, the product of the distance and the real-size value is less than the distance. The revised distance is used in calculating the apparent size of the light point; when the real-size is less than 1.0, the light point appears larger than it otherwise would. The default real-size value is 0.25.

Size Limitations

The apparent size of a light point can only range between the minimum and maximum sizes specified by the following lines of code:

pfLPStateVal(lpstate,PFLPS_SIZE_MIN_PIXEL, min-size);
pfLPStateVal(lpstate,PFLPS_SIZE_MAX_PIXEL, max-size);

Limiting Size Calculations

To optimize rendering, you can avoid changing the size (glPointSize) of each light point when the difference between the new and old sizes is less than a specified amount, called the threshold. To set the threshold value, use the following method:

pfLPStateVal(lpstate, PFLPS_SIZE_DIFF_THRESH, threshold);

Enabling Perspective

To use real-size, you must first enable it using the following method:

pfLPStateMode(lpstate, PFLPS_SIZE_MODE, mode);

The mode value can be one of the following:

  • PFLPS_SIZE_MODE_ON makes real-size follow perspective so that light points closer to the eye are rendered larger than those points farther away.

  • PFLPS_SIZE_MODE_OFF, the default value, disables perspective computations.

If perspective is not enabled, pfGeoSet (pfGSetPntSize) specifies the size of the rendered light points.

Fading

You can enhance the illusion of perspective by making the light point become more and more transparent as it recedes from view using the following method:

pfLPStateVal(lpstate,PFLPS_TRANSP_PIXEL_SIZE, transp-size);

transp-size is a light point size. When the actual size is smaller than transp-size, the light point becomes transparent.

Using fading to simulate perspective is often more realistic than shrinking the light point size; fading avoids the aliasing problems that occur when light points become too small.

Fading Calculation

Fading is calculated as follows:

Max(clamp, 1-scale*(transp-size - computed-size)^exp)

You set the values in this argument in the following lines of code:

pfLPStateVal(lpstate, PFLPS_TRANSP_EXPONENT, exp);
pfLPStateVal(lpstate, PFLPS_TRANSP_SCALE, scale);
pfLPStateVal(lpstate, PFLPS_TRANSP_CLAMP, clamp);

Enabling Fading

To use fading, you must first enable it using the following method:

pfLPStateMode(lpstate, PFLPS_TRANSP_MODE, mode)

The mode value can be one of the following:

  • PFLPS_TRANSP_MODE_ON enables fading when the computed size is less than transp-size.

  • PFLPS_TRANSP_MODE_OFF, the default, disables fading.

Callbacks

For each light point, two parameters are computed based on the location of the eye point, the location of the light point, and the fog:

  • Alpha—Specifies the intensity and transparency of a light point. Non-transparent light points have the maximum intensity given by their four color components.

  • Size—Specifies the diameter of each light point if the perspective mode, PFLPS_SIZE_MODE, is enabled; otherwise, the size is constant for all of the light points in a pfGeoSet.

Instead of accepting the calculations done by OpenGL Performer, you can use callback functions to supply your own calculations. Callback functions can be completed at the following times:

  • Before OpenGL Performer calculates the parameters, thus replacing the Performer calculation completely.

  • After OpenGL Performer calculates the parameters, thus modifying Performer's result.

You enable, disable, and specify the kind of callback used in the following method:

pfLPStateMode(lpstate, PFLPS_CALLBACK_MODE, mode)

The mode value can be one of the following:

  • PFLPS_CALLBACK_MODE_OFF, the default, means that no callback is attached to lpstate.

  • PFLPS_CALLBACK_MODE_PRE means that the callback is executed before OpenGL Performer has calculated the parameters. Your callback function must compute the size and alpha values for all the light points in the pfGeoSet.

  • PFLPS_CALLBACK_MODE_POST means that the callback is executed after OpenGL Performer's computation.

To install a callback on a pfLPointState, use the following method:

pfRasterFunc(lpstate, (void *) yourCallback(pfRasterData*), 
    void **userData);

userdata is a pointer to the following structure, which is given to your callback and the function itself:

typedef struct {
    pfLPointState   *lpstate;  /* Read Only LPState */
    pfGeoSet        *geoset;   /* Read Only GeoSet */
    void            *userData; /* Provided when setting the callback */
    float           *sizes;    /* Write Only - resulting sizes */
    float           *alphas;   /* Write Only - resulting alphas */
} pfRasterData;

geoset is the currently-preprocessed pfGeoSet. lpstate is the pfLPointState applied to that pfGeoSet. userData is the same data provided when declaring the callback.

sizes and alphas are pre-allocated arrays that contain the callback function results used by the DRAW process. If you use a pre-callback, you must provide a size and an alpha value for every light point in the pfGeoSet. A negative alpha value indicates that the backcolor must be used in place of the light point color.

Example 21-1 provides the skeleton of a raster callback. A more detailed example is on the pfLPointState man page.

Example 21-1. Raster Callback Skeleton

void myCallback(pfRasterData *rasterData)
{
    pfVec3* vertices;
    unsigned short *vindex;
    pfVec3* norms;
    unsigned short *nindex;
    int nbind;
    pfFog *fog;
    int fogEnabled = 0;
    int i,n;
    int sizeMode;
    pfMatrix ViewMat, InvModelView;
     
    /* get pointers to the geoset */
    pfGetGSetAttrLists(rasterData->gset,PFGS_COORD3, &vertices, &vindex);
    pfGetGSetAttrLists(rasterData->gset,PFGS_NORMAL3, &norms, &nindex);
    nbind = pfGetGSetAttrBind(rasterData->gset,PFGS_NORMAL3);
    /* get matrices */
    pfGetViewMat(ViewMat);
    pfGetInvModelMat(InvModelMat);
    
    /* get the number of lights */
    n = pfGetGSetNumPrims(rasterData->gset);
    
    /* see if there's fog */
    fog = pfGetCurFog();
    if (pfGetEnable(PFEN_FOG) && fog)
        fogEnabled = 1;
    
    /* get information on the lpstate */
    sizeMode =     pfGetLPStateMode(rasterData->lpstate,PFLPS_SIZE_MODE);
    ........
    ........
    /* do the computation */
    for (i=0; i<n; i++)
    {
        /* get the normal */
        if (vindex)
        {
        if (nbind & PFGS_OVERALL)
        nj=nindex[0];
        else if (nbind)
            nj=nindex[i];
        else
            nj=-1; /* this geoset has no normals */
    
        ........
        rasterData->alphas[i] = ....
        rasterData->sizes[i] = ....
    }


Multisample, Size, and Alpha

On InfiniteReality, light points of a given size, up to 100 multisamples, have the same number of multisamples even when the light points cross multiple pixels.

The intensity of a light point is defined by its alpha value. If pfTransparency is set to PFTR_MS_ALPHA_MASK, the alpha value modifies the number of multisamples lit in a light point. For example, if alpha= 0.5, size = 1.0, and the number of multisamples per pixel is 8, the number of illuminated multisamples per the light point is 3.0, as shown in Figure 21-4.

Figure 21-4. Lit Multisamples

Lit Multisamples

The area of a circle with diameter 1.0 is 0.785. The number of multisamples per pixel is given in the frame buffer configuration. If there are 8 multisamples, only 0.785 of them, about 6, are in the light point. Since alpha = 0.5, only half of the 6 multisamples are actually lit.

pfCalligMultisample() tells pfCalligraphic how many multisamples are used in the specified video channel. pfGetCalligMultisample() returns the current setting. Make this call as soon as the information is known or changed.

Minimum Number of Multisamples

If your light points move, you must have at least two multisamples lit per light point; if you only have one, the light point is either on or off. This condition creates flicker. With two multisamples, the light point can transition to a different location with one multisample on and the other off.

Reducing CPU Processing Using Textures

Light point computations are expensive. You can replace some of the computation by using a table lookup mechanism and the texture hardware to get an alpha value for each light point. This mechanism uses a precomputed texture and glTexGen to approximate:

  • fog attenuation

  • fading over distance

  • falloff attenuation when the eye point is not on the normal

Because only one texture per polygon is supported by the hardware, it is not possible to approximate all three computations at once. The fading and the fog are combined in one texture, however, so it is possible to select both at the same time.

The following methods specify the attenuations:

pfLPStateMode(lpstate, PFLPS_DIR_MODE, PFLPS_DIR_MODE_XXX)
pfLPStateMode(lpstate,PFLPS_TRANSP_MODE, PFLPS_TRANSP_MODE_XXX)
pfLPStateMode(lpstate, PFLPS_FOG_MODE, PFLPS_MODE_XXX)

XXX is one of the following:

  • TEX for texture lookup

  • ALPHA for CPU computation

Default values are equivalent to APLHA computation.


Tip: Falloff is the most expensive effect to compute on the CPU. For that reason, it is better to use PFLPS_DIR_MODE_TEX if the point is not omnidirectional.

Only use textures for low quality light points; textures are approximations with numerous limitations, including incorrect falloff attenuation if the emanation is not symmetric and incompatibility with callbacks.

Preprocessing Light Points

To optimize your application, you should fork off a light point process to preprocesses your light point computations. The light point process runs in parallel with the DRAW process but on a different CPU. Preprocessing the light points does the following:

  • Computes the size and alpha of each light point and passes the result directly to the DRAW process.

  • Executes callbacks attached to the pfLPointState.

To fork off a light point process, use the PFMP_FORK_LPOINT token in pfMultiprocess() and call it before pfConfig().


Note: You can start a light point process in perfly using the -m option and adding 16 to your preferred multiprocess model.


Stage Configuration Callbacks

As with any other OpenGL Performer process, callback functions can configure the process stages using the following:

pfStageConfigFunc(-1, PFPROC_LPOINT, ConfigLPoint);
pfConfigStage(-1, PFPROC_LPOINT | PFPROC_XXX ....);
pfChanTravFunc(chan, PFTRAV_LPOINT, LpointFunc);

pfStageConfigFunc() specifies a callback function, and pfConfigStage() triggers it at the start of the current application frame; both are methods in pfConfig. Configuration callbacks are typically used for process initialization; for example, they are used to do the following:

  • Assign non-degrading priorities and lock processes to CPUs.

  • Download textures in the DRAW stage callback.

pfStageConfigFunc() identifies the OpenGL Performer stages, such as PFPROC_ISECT, PFPROC_APP, and PFPROC_DBASE, to configure.

pfChanTravFunc

The pfChannel method, pfChanTravFunc(), sets a callback function for either the APP, CULL, DRAW, or Light Point process using one of the following tokens: PFTRAV_APP, PFTRAV_CULL, PFTRAV_DRAW, or PFTRAV_LPOINT, respectively.

User data that is passed to these functions by pfChanData() is allocated on a per-channel basis by pfAllocChanData().

How the Light Point Process Works

This section explains how the light point process works. All the functions exist in the API; however, all of the processing is done automatically by OpenGL Performer.


Note: Light point processing is not done automatically if your application is only using the libpr process model.

If the light point process is enabled, a special bin, PFSORT_LPSTATE_BIN, is created to sort all of the pfGeoSets that have a pfLPointState attached to their pfGeoState. This bin is directly used as a display list, LPointBinDL, as shown in Example 21-2.

All bins, except PFSORT_LPSTATE_BIN, are handed to the DRAW process. The DRAW process, after rendering everything in the other bins, renders a ring display list, called DrawRingDL, as shown in Example 21-2. The ring display list contains a synchronization mechanism: the DRAW process waits until it sees the PFDL_END_OF_FRAME token.

Example 21-2. Preprocessing a Display List - Light Point Process code

/* open the draw ring display list */
pfOpenDList(DrawRingDL);
 
/* preprocess the light points bin. */
/* so the results go in the DrawRingFL */
pfPreprocessDList(LPointBinDL,PFDL_PREPROCESS_LPSTATE);
 
/* Signal the end of the list to the Draw process */
pfAddDListCmd(PFDL_END_OF_FRAME);
 
/* close the draw display list */
pfCloseDList(DrawRingDL);


Calligraphic Light Points

Calligraphic light points are very bright lights that can be displayed only on specially-equipped display systems. Displaying calligraphic light points requires the following:

  • A calligraphic light point board ( LPB) with a special device driver. The driver is not part of the OpenGL Performer distribution.

  • A calligraphic display system.

  • Special cables running between the graphics pipe video synchronization and the raster manager (RM) boards on the LPB.

  • A platform, such as an InifiniteReality, OnyxIR, or Onyx2/3, that has a visibility (VISI) bus.


    Note: If you are not running on a system that has a VISI bus or you do not have this optional hardware, you are limited to raster light points, as supported by pfLPointState. The functionality described in the remainder of this chapter is not available on your system. Nevertheless, a program and database designed for calligraphic light points can be simulated on a non-calligraphic system.


Unlike raster displays, calligraphic displays direct the display system's electron beam at specified places on the screen. By directing the beam at specified places for specified durations, it is possible to produce extremely bright light sources.


Warning: It is possible to destroy your display system by allowing the electron beam to remain too long on the same screen location either by allotting too long a time or by an application hanging. Extreme caution is required.


Calligraphic Versus Raster Displays

Table 21-1 summarizes the differences be tween raster and calligraphic displays.

Table 21-1. Raster Versus Calligraphic Displays

Raster

Calligraphic

Requires no special hardware.

Requires special hardware, including a Light Point Board (LPB), cables, and a calligraphic-enabled display system. Applications using calligraphic light points must run on a machine that has a VISI board.

The electron beam sweeps across and down the screen left-to-right and top-to-bottom.

The beam lands only on those parts of the screen where calligraphic lights are located.

The electron beam stays on each pixel the same amount of time.

The electron beams stays on pixels for a variable length of time potentially producing exceedingly bright light sources.

A black dot produces a black pixel.

A black dot produces nothing; it is invisible.

If more than one point is drawn at the same location, only the last point drawn is visible.

Light points are added to whatever light is already falling on the pixel. A calligraphic light does not hide another calligraphic light.

Raster images are displayed within set time intervals, for example, 60 times a second.

When raster and calligraphic are displayed, the calligraphic light points are displayed in whatever time is left after the raster image is scanned. For more information, see “Display Modes”

 

No real dangers associated with raster displays.

A hanging application, for example, can leave the electron beam aimed at a single point on the screen and quickly burn it out. The same result is true if you programmatically light up a pixel for too long.

If the entire image is not drawn to the buffer, frames are dropped until the entire image is ready for display.

If all of the light points are not drawn, frames are not dropped; some light points are just not drawn.

(If a raster image is repeated in successive frames, the light points are also repeated.)

Whenever the calligraphic mode renders a black pixel to the screen, it is completely transparent and the raster image shows through.

When a pixel on the screen is targeted for raster and calligraphic light, both the raster and calligraphic light points are displayed at the same pixel thus making it large (raster) and bright (calligraphic).

Display Modes

A calligraphic display system can run in three modes:

  • calligraphic-only

  • mixed mode

  • raster-only

In calligraphic-only mode, only calligraphic points can be rendered by the display system.

In mixed mode, both raster and calligraphic images are rendered on the same display system; the raster image is displayed first and the calligraphic image is displayed in whatever time remains before the vertical sync. This mode requires a special video format used by the Video Format Compiler available on the SGI web site at http://www.sgi.com/software/vfc/.

You can combine a calligraphic-only display with a raster-only display on the same video channel; the effect is to give the full frame to the calligraphic display so that you can render the maximum number of light points. It is also more expensive and sometimes not convenient for mechanical reasons.

Light point modes are specified by pfLPStateMode() and one of the following tokens:

  • PFLPS_DRAW_MODE_RASTER, the default mode, forces the light points to be raster even if the system has a calligraphic display.

  • PFLPS_DRAW_MODE_CALLIGRAPHIC enables the rendering of calligraphic light points on calligraphic display systems.

Maximum Number of Calligraphic Lights

The maximum number of calligraphic lights that can be displayed is related to the following:

  • raster display time

  • duration of the calligraphic display time

  • time spent jumping from one calligraphic light point to another

The maximum number of calligraphic lights that can be displayed is inversely proportional to the raster display time. For example, in a mixed mode, if the screen refreshes every 1/60th of a second, the calligraphic display time is 1/60th of a second minus the time it takes for the raster mode to draw its image on the screen. The shorter the raster display time, the more calligraphic lights that can be displayed.

At night, it is possible to reduce the time for the raster display, which has the effect of reducing the global raster brightness. Reducing the raster display time increases the maximum number of calligraphic light points that can be displayed. To reduce the raster display time, you need to do the following:

  • Two different video formats made using the Video Format Compiler.

  • Preprogram the projector so it recognizes the format change when it happens on the fly by the application.

Changing the video format is done using the XSGIvc extension.

The maximum number of calligraphic lights that can be displayed is inversely proportional to the draw time of the calligraphic light points. Unlike raster lights, you can control the length of time the electron beam hits a specified location on the display system; the longer the duration, the brighter the light. Also, the longer the duration, the less time is left for drawing other calligraphic light points.

Finally, the time it takes the electron beam to jump between calligraphic light points also adversely affects the maximum number of light points that can be displayed; the more time spent jumping between calligraphic light points, the fewer light points that can be displayed.

LPB Hardware Configuration

A Light Point Board (LPB) is a circuitry card that enables the rendering of calligraphic lights by providing the interface with a calligraphic display. The LPB is configured as follows:

  • Connected to one graphics pipeline only.

  • Connected to all the video channels produced by a single pipe.

  • Connected to all (1,2 or 4) raster manager (RM) boards of its pipe.

    Figure 21-5. Calligraphic Hardware Configuration

    Calligraphic Hardware Configuration

The configuration in Figure 21-5 shows the following:

  • A CPU is used for the light point process so the computation and the communication to the LPB through the VME bus are done in parallel to the DRAW process. It is mandatory to start a light point process for calligraphic; see “Preprocessing Light Points”.

  • The VME bus transfers to the LPB all of the calligraphic light point information, including the color, focus, exposure time, quality, and position of each calligraphic light point. The only information not transferred by the VME bus is occlusion information, which is supplied by the VISI bus.

  • The VISI (visibility) bus specifies whether all, part, or none of a calligraphic light point is displayed. A calligraphic light might be partially displayed, or not at all, if a geometry in the scene is between the light point and the viewer. Each light point has a unique ID; this ID is used to match the information in the VISI bus with the correct light point.

    The VISI bus is a connector on each RM board. The visibility information is available only to the LPB.


    Note: The LPB board may be used in a system without a VISI bus (systems prior to InfiniteReality), in which case no Z-buffer information is given to the board; so, all light points are 100% visible.


  • The LPB uses the vertical and horizontal (not the composite) synchronization signals to trigger the calligraphics display. Use ircombine to set the Hsync in place of the composite sync.

  • The LPB receives the Swap Ready signal when the raster display has completed drawing to the display buffer; so, it should also swap its internal buffers.

    If the LPB does not get a Swap Ready signal, the LPB redisplays the same calligraphic light points; since the raster frame is repeated, the calligraphic lights must remain unchanged. Do not forget to connect the SwapReady signal to the light point board even if you are using a single pipe configuration.

  • The LPB receives the VISI and VME bus light point information and combines it and send the result to the calligraphic display.

Visibility Information

All the calligraphic light point information is computed by the light point process and goes directly to the LPB. The only missing information is knowing how much of each light point can be seen. To compute that value, the following events take place:

  1. A footprint of the calligraphic light point is sent to the graphic pipe.

  2. The footprint is compared against the Z-buffer.

  3. The result of the test is sent to the LPB using the VISI bus.

The graphic pipe takes great care that the number of multisamples covered is a constant wherever the light point is. The number of multisamples is not constant after the footprint covers more than 100 multisamples, however. A footprint can cover numerous pixels but is limited to 256 multisamples by the LPB.

pfCalligZFootPrintSize() sets the diameter of the footprint and pfGetCalligZFootPrintSize() returns the diameter. The number of multisamples covered by a footprint is equal to the following:

(n x size2)/(4 x ms)

ms is the number of multisamples per pixel.

Required Steps For Using Calligraphic Lights

To use calligraphic light points, you have to configure the channels on the LPB board and the corresponding channels using ircombine. ircombine operates on the following:

  • video format combinations

  • descriptions of raster sizes and timings used on video outputs

  • configuration of the underlying frame buffer

You must also enable the channels on the LPB board before starting the application.

OpenGL Performer does not provide direct access to the LPB drivers. You can, however, write a program that does.

Now, in OpenGL Performer a few steps are still necessary.

  1. Check and open the LPB on each pipe, as follows:

    pfQueryFeature(PFQFTR_CALLIGRAPHIC, &q)
    pfCalligInitBoard(pipe)
    

  2. Start a light point process to process the light point calculations, as follows:

    pfMultiprocess(PFMP_APP_CULL_DRAW |  PFMP_FORK_LPOINT);
    pfConfig();
    

  3. Initialize the OpenGL Performer stages, as follows:

    pfStageConfigFunc(-1,PPROC_LPOINT,ConfigLPoint)
    pfFrame();
    

  4. Set the callback function in the light point process, as follows:

    pfChanTravFunc(chan, PFTRAV_LPOINT, LpointFunc);
    

  5. Enable the calligraphic display on all of the channels, as follows:

    pfChanCalligEnable(chan[i], 1);
    

  6. Synchronize the VME and VISI bus signals on the LPB, as follows:

    pfCalligSwapVME(pipe);
    pfCalligSwapVME(pipe);
    

You now have calligraphic light points in your application if you have calligraphic light points in your database.

Customizing LPB Initialization

Instead of using the default initialization, OpenGL Performer provides a set of functions that allow your application to customize the initialization of the LPB.

pfInitBoard() opens the LPB device and retrieves the current configuration. This function returns TRUE if successful, FALSE otherwise. You can also use pfIsBoardInit() to determine if the board has been initialized. Each LPB must be initialized before calling pfConfig().

pfCalligCloseBoard() closes the device.


Note: The board number and the pipe number are the same because there is only one board per pipe.

To access to the LPB directly, return its ID using pfGetCalligDeviceID(). The ID allows you to make direct calls to the LPB driver.

pfGetCalligInfo() returns a pointer to the configuration information structure maintained by the LPB driver. To use this structure, LPB_info, you must include the driver lpb.h file before any OpenGL Performer include files.


Note: lpb.h is not distributed with OpenGL Performer; it is part of the LPB driver distribution.

Once a board is initialized, you can find out how much memory is available and allocate it among all of the enabled channels on the pipe. You can return the total amount of LPB memory in bytes by calling pfGetCalligBoardMemSize().

By default, each channel receives the same amount of memory. To set up a different partitioning of memory, use pfCalligPartition().

You can attach a pfCalligraphic to a specific pfChannel using the following code:

pfCalligraphic *callig = pfNewCallig(arena)
pfCalligChannel(callig, pipe, chan)

callig can then be attached to the pfPipeVideoChannel using pfPVChanCallig().

You can also set the pfCalligraphic on individual pfChannels using pfChanCallig(). You must enable calligraphic light point processing on the specified channels so the GangSwap mechanism is correctly handled by OpenGL Performer, as follows:

pfChanCalligEnable(chan, 1);


Note: The video channel number does not have to be the same channel number as the calligraphic board.

pfCalligWin() changes the resolution of the X and Y data accepted by the projector. The default values are for an EIS projector (2^16). If you have a conversion interface between the LPB and your projector, the scaling may already be done by the conversion interface. You can use pfCalligWin() to display calligraphic points on only part of the screen or to make multiple viewports.

pfCalligXYSwap() switches the X and Y axes in the display. You can also reverse the X or Y axes by giving negative width and height values in pfCalligWin().

Accounting for Projector Differences

Some display systems, such as the EIS projector, can calculate the following:

  • Slew values, the time for the electron beam to go from one calligraphic point to another.

  • Gamma correction, which takes into account that projectors differ in the colors they project.

If your system cannot perform those calculations, the light point board can calculate those values for your application.

Slew Values

A slew table is two dimensional; it gives the time in nanoseconds it takes the electron beam to go from one calligraphic point to another on one axis. A default, generic slew table, which contains conservative values, is loaded in the LPB when it is initialized.

The longer the slew time, the longer the electron beam has to reach a calligraphic light point on the display; any consequent wobble can dampen out in that time providing a very stable light point. Conversely, longer slew times subtract from the total time in which calligraphic light points can be drawn. Consequently, longer slew times could mean that fewer calligraphic points are drawn per frame.

There are eight slew tables defined by the pfCalligSlewTableEnum. For each axis, there are three slew tables: one table for high-quality (very stable) light points, one table for medium-quality (slightly shorter slew times) light points, and another for low-quality (very short slew times) light points.

You specify the drawing quality using pfLPStateVal() with one of the following mode values:

  • PFLPS_QUALITY_MODE_HIGH

  • PFLPS_QUALITY_MODE_MEDIUM

  • PFLPS_QUALITY_MODE_LOW

Two other tables are used when the defocus value changes in between two points; one table is used for high-quality light points, another table for medium- and low-quality light points.

typedef enum {
    pfXSlewQuality0 = 0, /* High quality on the X axis */
    pfXSlewQuality1 = 1, /* Medium quality on the X axis */
    pfXSlewQuality2 = 2, /* Low quality on the X axis */
    pfYSlewQuality0 = 3, /* High quality on the Y axis */
    pfYSlewQuality1 = 4, /* Medium quality on the Y axis */
    pfYSlewQuality2 = 5, /* Low quality on the Y axis */
    pfDefocusQuality0 = 6,/* High quality if focus change */
    pfDefocusQuality1 = 7 /* Medium and Low quality if focus change */
} pfCalligSlewTableEnum;

To load or upload customized slew tables, use the following methods:

  • pfCalligDownLoadSlewTable() downloads a specified slew table into the LPB.

  • pfCalligUpLoadSlewTable() returns a slew table.

Color Correction

Each projector has its own color characteristics; a light point that appears blue on one projector might appear aqua on another. To color correct the projected images, the light point board maintains one gamma table per channel. Each gamma table consists of three one-dimensional tables, one for each color component.

typedef enum {
    pfRedGammaTable = 0,
    pfGreenGammaTable = 1,
    pfBlueGammaTable = 2
} pfCalligGammaTableEnum;

The default value provided by OpenGL Performer is a linear ramp that can be modified with the following methods:

  • pfCalligDownLoadGammaTable() downloads a specified gamma table into the LPB.

  • pfCalligUpLoadGammaTable() returns a gamma table.

Callbacks

Like raster lights, calligraphic light points can have a callback function attached to the pfLPointState that can occur before (PRE) or after (POST) the light point processing. The calligraphic and raster callback functions compute different parameters.

If the stress test determines that a calligraphic light point is not going to be drawn as a raster light point, the calligraphic callback is not be called; the raster callback is called instead if set. For more information about the stress test, see “Significance”.

To install a calligraphic callback on a pfLPointState, you use a line of code similar to the following:

pfCalligFunc(lpstate, (void *) yourCallback(pfCalligData*), 
    void **userData);

userData is a user pointer given to your callback identifying the callback function and the following structure:

typedef struct {
    pfLPointState   *lpstate;  /* Read Only LPState */
    pfGeoSet        *geoset;   /* Read Only GeoSet */
    void            *userData; /* Provided when setting the callback */
    unsigned short  *index;    /* Read Write - index visible lpoints */
    int             *n;        /* Read Write - # of visible lpoints */
    pfVec3          *coords2D; /* Read Write - screen space X,Y,Z */
    float           *intensity;/* Write Only - resulting intensity */
    float           **focus;   /* Write Only - optional (de)focus */
    float           **drawTime;/* Write Only - optional drawTime */} pfCalligData;
} pfRasterData;

geoset is the pfGeoSet that is currently preprocessed. lpstate is the pfLPointState applied to that pfGeoSet. userData is the same as the data provided when declaring the callback.

index is a preallocated vector that points to light points that are visible. Even in a PRE callback the light points are projected on the screen before the user callback is called; the points outside of the screen are not in the index vector. See the pfLPointState man page on how to use the index vector in a callback.

n is a pointer to the number of elements in the index vector.

coords2D contains the coordinates of the light points on the screen, including the Z coordinates in screen space. The original coordinates can be accessed through the pfGeoSet. It is valid to change the values in coord2D in the callback, for example, to align the points on a grid.

intensity is a pre-allocated vector that contains the intensity of individual light points. A PRE callback must compute intensity.

focus is a NULL pointer. It is possible to provide an individual focus value for each point by doing the following:

  1. Allocating from the arena an array of floats of size n.

  2. Filling the array with the focus values.

  3. Setting focus to point to that array.

You should not allocate an array in real-time; instead, allocate temporary memory beforehand and use userData to pass the memory address to the callback.

drawTime is a NULL pointer. With it you can provide an array of floats that give a draw time for each light point.

Frame to Frame Control

Before calling pfLPoint() in the light point process callback function, you can change the parameters in a pfCalligraphic object on a frame-by-frame basis, as shown in Example 21-3.

Example 21-3. Setting pfCalligraphic Parameters

myLPointFunc(pfChannel *chan, void *data)
{
pfCalligraphic *calligraphic = pfGetCurCallig();
 
    if (calligraphic != NULL)
    {
        pfCalligFilterSize(calligraphic,FilterSizeX,FilterSizeY);
        pfCalligDefocus(calligraphic,Defocus);
        pfCalligRasterDefocus(calligraphic,rasterDefocus);
        pfCalligDrawTime(calligraphic, DrawTime);
        pfCalligStress(calligraphic, Stress)
   }
    pfLPoint();
}

FillterSizeX and FilterSizeY set the debunching distances along each axis. A filter size of 0 disables the debunching along that axis. Debunching can also be disabled on the pfLPointState. For more information about debunching, see “Debunching”.

Defocus sets the defocus applied to all calligraphic light points, unless a callback function returns a defocus array. In that case, each light point has a callback defocus value regardless of the one set on the pfCalligraphic object.

In a raster-plus-calligraphic video mode, a calligraphic system usually has the capability to apply a global defocus to the raster image, which is specified by the rasterDefocus parameter passed to the projector. This parameter affects the entire image, not just the raster light points.

For more information about defocus, see “Defocussing Calligraphic Objects”.

DrawTime is the time, in nano seconds, in which all calligraphic light points must be drawn, unless a callback returns a DrawTime array, which controls individual draw times. The overall effect of changing the draw time is to reduce or increase the intensity of all calligraphic light points.

The Stress value is compared against the significance value of a pfLPointState to determine whether the light points are displayed as calligraphic or raster. The Stress value should be as stable as possible to avoid having points going from raster to calligraphic or from calligraphic to raster each frame. For more information about stress, see “Significance”.

Significance

If there is not enough time to draw all of the calligraphic light points, some calligraphic light points can either not be drawn or, instead, drawn as raster light points, depending on the stress value of the calligraphic light point, described in “Frame to Frame Control”, and the significance value of the pfLPointState.

The stress value of a calligraphic light point is compared against the significance value of the pfLPointState. If the significance is greater or equal than the stress, the light is rendered as a calligraphic; otherwise, the light is rendered as a raster light.

To set the significance, use pfLPStateVal(lpstate, PFLPS_SIGNIFICANCE, significance).

Debunching

Debunching eliminates some calligraphic points when they occur close together on the display system. Bunched calligraphic light points can create a heap effect, or worse, burn the display system where there is a group of calligraphic light points.

The debunching distances are set in pfCalligraphic on the X and Y axis, as described in “Frame to Frame Control”.

If two points in a pfGeoSet are within the debunching distance, the point with the lowest intensity is not rendered.

If you do not want a pfLPointState to be affected by debunching, you can disable it using pfLPStateMode() with the PFLPS_DEBUNCHING_MODE_OFF mode; PFLPS_DEBUNCHING_MODE_ON, the default, enables debunching.

Defocussing Calligraphic Objects

Defocussing a calligraphic light point is often done to produce a specific lighting effect, for example, to simulate rainy conditions, light points should appear defocussed. The defocus is set within pfCalligraphic (see “Frame to Frame Control”) for all light points for one Frame, but it is possible to clamp the defocussing values by setting a min_defocus and a max_defocus value in the pfLPState using the following methods:

pfLPStateVal(lpstate,PFLPS_MIN_DEFOCUS, min_defocus);
pfLPStateVal(lpstate, PFLPS_MAX_DEFOCUS, max_defocus);

Using pfCalligraphic Without pfChannel

pfCalligraphic is a libpr object; so, programs based only on libpr can have access to pfCalligraphic without having access to a pfChannel, which is a libpf object. This section describes what pfChannel does automatically with pfCalligraphic objects.

The LPB is connected to the VME bus and the VISI bus. Both buses contain information for calligraphic light points buffered in the LPB. VISI and VME busses load their information into the LPB buffer. The SwapReady connection to the graphic pipe synchronizes the activity on the VISI bus; SwapReady tells the board when glXSwapBuffers() is called, and that at the next VSync, the next frame should be displayed.

pfCalligSwapVME() performs the same synchronization for the VME bus; whenever you call pfSwapPWinBuffers(), you must first call pfCalligraphicSwapVME(); otherwise, the light point board will not be synchronized with the rest of the system.


Tip: To resynchronize the LPB, make two consecutive calls to pfCalligSwapVME(); for example, perfly makes these calls after the OpenGL Performer logo has been displayed.



Note: These synchronization mechanisms are handled automatically in libpf, unless you override the channel swap buffer and call pfSwapPWinBuffers().


Timing Issues

The SwapReady signal should always occur after a VME Swap signal. If this does not happen, the light point board starts a TooLate timer and two things may happen:

  • Assuming the VME Swap signal has been lost, the LPB starts to draw the buffer .

    In this instance, not all of the calligraphic lights are rendered. pfCalligraphic handles this exception by making sure the LPB buffer always contains an end-of-buffer token at the end of the valid data.

  • The VME Swap signal is received shortly after the Swap Ready, making the LPB behave normally.

The LPB needs some time before it can accept new information from the VME, and the VISI bus needs some time after it has received the corresponding swap command. pfWaitForVmeBus() and pfWaitForVisiBus() allow the application to wait for the board to get ready before sending it new information.

Light Point Process and Calligraphic

The light point process is the same whether the light points are raster or calligraphic. The only difference is that, with calligraphic light points, pfCalligraphic has to be selected before calling pfPreprocessDList, which is done automatically by pfChannel.

pfSelectCalligraphic() selects the channel and LPB board to which the calligraphic light points are sent. pfGetCurCalligraphic() returns the current value set by pfSelectCalligraphic().

Debugging Calligrap hic Lights on Non-Calligraphic Systems

If you are developing on a non-calligraphics-enabled system but would like to see the effects of your pfCalligraphics programming, you can set the environment variable PF_LPOINT_BOARD. In this mode, the LPB is simulated, the calligraphic computations are performed, and raster light points are displayed in place of the calligraphic lights with the following limitations:

  • Calligraphic defocus has no effect.

  • A calligraphic light point size is defined with the Z-footprint.

Calligraphic Light Example

Example 21-4 shows a sample implementation of calligraphic lights. You can find the source code in perf/sample/pguide/libpf/C/callig.c.

Example 21-4. Calligraphic Lights

#include <stdlib.h>
#include <Performer/pf.h>
#include <Performer/pfutil.h>
#include <Performer/pfdu.h>
 
 
static NumScreens=1;
static NumPipes=1;
 
static void ConfigPipeDraw(int pipe, uint stage);
static void OpenPipeWin(pfPipeWindow *pw);
static void OpenXWin(pfPipeWindow *pw);
static void DrawChannel(pfChannel *chan, void *data);
 
/*
 * Usage() -- print usage advice and exit. This
 *      procedure is executed in the application process.
 */
static void
Usage (void)
{
    pfNotify(PFNFY_FATAL, PFNFY_USAGE, “Usage: multipipe file.ext     ...\n”);
 
    exit(1);
}
int
main (int argc, char *argv[])
{
    float       t = 0.0f;
    pfScene     *scene;
    pfPipe      *pipe[4];
    pfChannel   *chan[4];
    pfNode root;
    pfSphere sphere;
    int loop;
 
    /* Initialize Performer */
    pfInit();	
    pfuInitUtil();	
    
    /* specify the number of pfPipes */
    /* Configure and open GL windows */
    if ((NumScreens = ScreenCount(pfGetCurWSConnection())) > 1)
    {
        NumPipes = NumScreens;
    }
    
    pfMultipipe (NumPipes);
    
    /* Initialize Calligraphic HW */
    for (loop=0; loop < NumPipes; loop++)
    {
        int q;
        pfQueryFeature(PFQFTR_CALLIGRAPHIC, &q);
        if (!q)
       {
            pfNotify(PFNFY_NOTICE,PFNFY_RESOURCE, “Calligraphic points are NOT supported on this platform”);
        }
        
        if (pfCalligInitBoard(loop) == TRUE)
        {
            /* get the memory size */
            pfNotify(PFNFY_NOTICE, PFNFY_RESOURCE,“StartCalligraphic: board(%d) has %d Bytes of memory”,0, pfGetCalligBoardMemSize(loop));
        }
        else
        {
            pfNotify(PFNFY_NOTICE, PFNFY_RESOURCE, “Could not init calligraphic board %d”, loop);
        }
    }
 
    /* Force Multiprocessor mode to use the light point process */
    
    pfMultiprocess(PFMP_APP_CULL_DRAW |  PFMP_FORK_LPOINT);
    
    /* Load all loader DSO's before pfConfig() forks */
    if (argc > 1)
    pfdInitConverter(argv[1]);
    
    /* Configure multiprocessing mode and start parallel processes.*/
    
    pfConfig();
    /* Append to PFPATH additional standard directories where geometry and textures exist */
    
    scene = pfNewScene();
    if (argc > 1)
    {
        if (!(getenv(“PFPATH”)))
pfFilePathv(“.", "./data", "../data", "../../data",
           "/usr/share/Performer/data”, NULL);
    
        /* Read a single file, of any known type. */
        if ((root = pfdLoadFile(argv[1])) == NULL) 
        {
            pfExit();
            exit(-1);
        }
        
        /* Attach loaded file to a pfScene. */
        pfAddChild(scene, root);
        
        /* determine extent of scene's geometry */
        pfGetNodeBSphere (scene, &bsphere);
        /* Create a pfLightSource and attach it to scene. */
        pfAddChild(scene, pfNewLSource());
    }
    
    
    for (loop=0; loop < NumPipes; loop++)
    
    pfPipeWindow *pw;
    char str[PF_MAXSTRING];
    
    pipe[loop] = pfGetPipe(loop);
    pfPipeScreen(pipe[loop], loop);
    pw = pfNewPWin(pipe[loop]);
    pfPWinType(pw, PFPWIN_TYPE_X);
    sprintf(str, “OpenGL Performer - Pipe %d”, loop);
    pfPWinName(pw, str);
    if (NumScreens > 1)
    {
        pfPWinOriginSize(pw, 0, 0, 300, 300);
    } else
        pfPWinOriginSize(pw, (loop&0x1)*315, ((loop&0x2)>>1)*340, 300, 300);
    
        pfPWinConfigFunc(pw, OpenPipeWin);
        pfConfigPWin(pw);
        
    }
    
    
    /* set up optional DRAW pipe stage config callback */
    pfStageConfigFunc(-1 /* selects all pipes */, PFPROC_DRAW /* stage bitmask */, ConfigPipeDraw);
pfConfigStage(-1, PFPROC_DRAW);
    
    pfFrame();
    
    /* Create and configure pfChannels - by default, channels are placed in the first window on their pipe */
    
    for (loop=0; loop < NumPipes; loop++)
    {
        chan[loop] = pfNewChan(pipe[loop]);	
        pfChanScene(chan[loop], scene);
        pfChanFOV(chan[loop], 45.0f, 0.0f);
        pfChanTravFunc(chan[loop], PFTRAV_DRAW, DrawChannel);
        pfChanCalligEnable(chan[loop], 1);
        /* synchronize the lpoint board with the swap ready signal */
        pfCalligSwapVME(loop);
        pfCalligSwapVME(loop);
    }
    
    /* Simulate for twenty seconds. */
    while (t < 30.0f)
    {
        pfCoord view;
        float      s, c;
        
        /* Go to sleep until next frame time. */
        pfSync();		
        /* Initiate cull/draw for this frame. */
        pfFrame();		
        
        pfSinCos(45.0f*t, &s, &c);
        pfSetVec3(view.hpr, 45.0f*t, -10.0f, 0);
        pfSetVec3(view.xyz, 2.0f * bsphere.radius * s, -2.0f *         bsphere.radius *c, 0.5f * bsphere.radius);
        for (loop=0; loop < NumPipes; loop++)
            pfChanView(chan[loop], view.xyz, view.hpr);
    }
    
    /* Terminate parallel processes and exit. */
    pfExit();
    return 0;
}
    
/* ConfigPipeDraw() --
 * 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.
 */
    
static void
ConfigPipeDraw(int pipe, uint stage)
{
    pfPipe *p = pfGetPipe(pipe);
    int x, y;
    
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, “Initializing stage 0x%x of     pipe %d on screen %d, connection \”%s\””, stage,     pipe,pfGetPipeScreen(p),pfGetPipeWSConnectionName(p));
    
    pfGetPipeSize(p, &x, &y);
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, “Pipe %d size: %dx%d”, pipe, x,y);
}
    
/*
 * OpenPipeWin() -- create a GL window: set up the
 *        window system and OpenGL Performer. This
 *        procedure is executed for each window in the draw process 
 *        for that pfPipe.
 */
    
    
void
OpenXWin(pfPipeWindow *pw)
{
    /* -1 -> use default screen or that specified by shell DISPLAY variable */
    pfuGLXWindow     *win;
    pfPipe  *p;
    
    p = pfGetPWinPipe(pw);
    if (!(win = pfuGLXWinopen(p, pw, “TESTIN”)))
        pfNotify(PFNFY_FATAL, PFNFY_RESOURCE,“OpenXPipeline: Could not create GLX Window.”);
    
    win = win; /* suppress compiler warn */
    
    
}
    
static void
OpenPipeWin(pfPipeWindow *pw)
{
    pfPipe *p;
    pfLight *Sun;
    
    p = pfGetPWinPipe(pw);
    
    /* open the window on the specified screen. By default,
    * if a screen has not yet been set and we are in multipipe mode,
    * a window of pfPipeID i will now open on screen i
    */
    pfOpenPWin(pw);
    pfNotify(PFNFY_NOTICE, PFNFY_PRINT, 
    PipeWin of Pipe %d opened on screen %d”,     pfGetId(p),pfGetPipeScreen(p));
    
    /* create a light source in the “south-west” (QIII) */
    Sun = pfNewLight(NULL);
    pfLightPos(Sun, -0.3f, -0.3f, 1.0f, 0.0f);
}
    
static void
DrawChannel (pfChannel *channel, void *data)
{
    static int first = 1;
    
    if (first)
    {
        first = 0;
        pfNotify(PFNFY_NOTICE,PFNFY_PRINT,”Calligraphics: 0x%p”, pfGetChanCurCallig(channel));
    }
    
    /* erase framebuffer and draw Earth-Sky model */
    pfClearChan(channel);
    
    /* invoke Performer draw-processing for this frame */
    pfDraw();
}