Multiple Channel Setup with Quad-buffering

What can you do with multiple channels and quad-buffering (and 1 pipe)?

Multiple channels coming from a single pipe can be used to render an image in stereo or provide several views of an image on a single screen (or group of connected screens). A separate channel is used for each view of the image and 2 channels are used to render the image in stereo. Click here for a picture of multiple image views on a screen in Hank. (Click here for the same picture with a gap between the channels).

Basics:

Decide on the number of channels you need. Set the Call backs, Earth-Sky model, Scene, etc. of each channel. (If the same image is being rendered all channels, these will all be the same). Set the Viewport and ViewOffsets for each channel. (The Viewport will differ for multiple for multiple views of an object but not for stereo. The ViewOffsets will differ for stereo and may differ for multiple views). Choose the buffers to be drawn in the draw process callback. For overlapped images, call clear and draw for each channel. If necessary, set a buffer swap function to swap the front and back buffers. (The buffers should swap automatically but if they do not, use a buffer swap function). Make sure you can support stereo.

Note: All of the examples in these sections are in C++. Since we only had one pipe, these examples are meant for situations in which one pipe is used and may need to be altered if more pipes are utilized. In Hank, the code examples belong in the file generic.C



Channel Initialization

To avoid clutter in the main function, the following section is usually done in a channel initialization function. It is common to declare all channels globally to avoid passing many parameters into functions which deal with the channels.
A channel is of type pfChannel and is initialized with the value of 'new pfChannel(pfGetPipe(pipe_num))' where pipe_num is the number of the pipe for the channel. In our case we only have one pipe so pipe_num=0. Multiple channels can be declared on each pipe and rendered into that pipe's window. After initializing a channel, several attributes of the channel should be initialized. Callbacks for the Draw, Cull, and/or Application functions need to be set. The scene and the EarthSky model need to be attached to the channel. The near and far clipping planes and the field of view need to be set as well. The initial viewing position and direction, often the same for all channels, should also be set for each channel.
Near and far clipping planes are the boundaries on the z-axis (using the 3-dimensional xyz planes) within which the image displayed will be visible. If your view point is from (0,0,0), it would make sense for the near clipping plane to be at z = 0.0f since you would not normally be able to see an object in the opposite direction of the one you face. The far clipping plane can be at any point greater than z = near. Far is often set depending on the scene size: far = PF_MAX2(sceneSize * 2.0f, 1000.0f).
To understand field of view, imagine standing in the center of the world in which your image exists (imagine an earth even if your image doesn't provide one). Pretend that this world is flat and shaped like a pie. Your field of view is the amount of the pie that you can see from where you stand. If you can see 1/8 of the world, then your FOV is 45.0. Click here for a picture of a pie with a 45.0 FOV slice. FOV can range from 0.0 to 180.0. If a number outside this range is chosen, Performer will default to 45.0. FOV can differ for each channel but it is not necessary.
Attaching the channels to form a channel group makes further declarations easier because attributes given to the master of the group are shared by all channels in the group. Unless specified, the following attributes are automatically shared by all members of a channel group:

Table 1. Sharable attributes in a channel group

(Taken from the IRIS Performer 2.0 libpf C++ pfChannel Reference Pages)

PFCHAN_FOV
Horizontal and vertical fields of view are shared.
PFCHAN_VIEW
The view position and orientation are shared.
PFCHAN_VIEW_OFFSETS
The XYZ and HPR offsets from the view direction are shared.
PFCHAN_NEARFAR
The near and far clip planes are shared.
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 parameters.
PFCHAN_LOD
All channels use the same LOD modifiers.
PFCHAN_SWAPBUFFERS
All channels swap buffers at the same time, even when the channels
are on multiple pfPipes.
PFCHAN_SWAPBUFFERS_HW
All channels swap buffers at the same time. The GANGDRAW feature
of the mswapbuffers function is used to synchronize buffer swapping
through hardware interlocking. This feature can synchronize graphics
pipelines across multiple machines.
PFCHAN_STATS_DRAWMODE
All channels draw the same statistics graph.
PFCHAN_APPFUNC
The application callback is invoked once for all channels
sharing PFCHAN_APPFUNC.
PFCHAN_CULLFUNC
All channels invoke the same channel cull callback.
PFCHAN_DRAWFUNC
All channels invoke the same channel draw callback.
PFCHAN_VIEWPORT
All channels use the same viewport specification.

ex1: A simple declaration and initialization of 3 channels.

    void
    InitChannel(void)
    {
        /* The channels are often declared globally */
        pfChannel    *chan, *chan2, *chan3;

    /* Initialize and attach the channels */
        chan  = new pfChannel(pfGetPipe(0));
        chan2 = new pfChannel(pfGetPipe(0));
        chan3 = new pfChannel(pfGetPipe(0));

        /* Form a channel group with 'chan' as the master */
        chan->attach(chan2);
        chan->attach(chan3);

    /* Initialize all attributes shared between channels */
            /* Set the callback routines for the pfChannel */
            chan->setTravFunc(PFTRAV_APP, AppFunc);
            chan->setTravFunc(PFTRAV_CULL, CullFunc);
            chan->setTravFunc(PFTRAV_DRAW, DrawFunc);

            /* Attach the visual database to the channels */
            chan->setScene(scene);

            /* Attach the EarthSky model to the channels */
            chan->setESky(eSky);

            /* Initialize the near and far clipping planes */
            chan->setNearFar(near, far);

            /* Initialize the viewing position and direction */
            chan->setView(initView->xyz, initView->hpr);

           /* Set channel frustums */
           chan->setAutoAspect(PFFRUST_CALC_VERT);
           chan->makeSimple(fov); 

            .....
    }
The Viewport of a channel is the area of the screen that the channel's image is rendered to. Imagine an xy plane over the screen of your monitor with (0,0) at the bottom left corner and (1,1) at the top right corner. Each channel's Viewport consists of the coordinate of its left, right, bottom, and top sides.
A ViewOffset consists of two vectors, one giving the xyz position of the view and the other giving the heading/pitch/roll direction of the view. To look straight ahead from the center of the scene, both vectors would be (0,0,0). Changing the heading will alter the direction of the view.
With 3 channels, headings of +FOV, 0 , and -FOV would show a straight view and the views on either side without gaps between the views. For stereo, the heading and x coordinates of one vector should differ slightly from the other vector. You will need to try several combinations before you find one that looks best.
For stereo, 2 channels have the same Viewport but differ in their ViewOffsets. If necessary, you may want to set up a share mask for Viewport.
It is important to remember that the attributes of the master channel are given to all channels in the group when setting an attribute that differs for each channel. The master must be set first and then all other channels can be set. (Otherwise setting the master would overwrite the settings on other channels). The attributes shared by the members of a channel group are specified by chan->setShare(mask). mask is a bitwise OR of some or all of the attributes listed in table 1. Setting the share mask may only be necessary when using multiple pipes.

ex2: Setting Viewports and ViewOffsets of 2 channels to create a stereo image.

    void
    InitChannel(void)
    {
        float    paralax = 0.5f   /* Convergence */
        float    ioc     = 0.02f  /* Intraocular distance */
        int      share;           /* Share mask */
        pfVec3   xyz, hpr;

        .....

        chan->attach(chan2);

        .....

    /* channels need to share a viewport ---> Viewports should be shared
       automatically.  This should be unnecessary unless multiple 
       pipes are used.*/
 
        /* Get the default */
        share = chan->getShare();

        /* Add in the viewport share bit */
        share |= PFCHAN_VIEWPORT;
        share &= ~PFCHAN_FOV;

        chan->setShare(share);

    /* Set the Viewport and ViewOffsets for both channels */
        chan->setViewport(0.0f, 1.0f, 0.0f, 1.0f);

        /* Set left channel offsets */
        hpr.set(paralax, 0.0f, 0.0f);
        xyz.set(-ioc, 0.0f, 0.0f);
        chan->setViewOffsets(xyz, hpr);

        /* Set right channel offsets */
        hpr.set(-paralax, 0.0f, 0.0f);
        xyz.set(ioc, 0.0f, 0.0f);
        chan2->setViewOffsets(xyz, hpr);      
 
        .....
    }

ex3: Setting the Viewports and Offsets of 2 channels with each view in a different direction. Click here for a picture of front and 45 degrees left views as seen from a car in Hank. (Click here for the same picture with a gap between the channels).

    void
    InitChannel(void)
    {
        pfVec3  xyz, hpr;
        float   fov = 45.0; 

        .....

    /* Set front channel offsets */
        chan->setViewport(1.0f/2.0f, 1.0f, 0.0f, 1.0f);
        hpr.set(0.0f, 0.0f, 0.0f);
        xyz.set(0.0f, 0.0f, 0.0f);
        chan->setViewOffsets(xyz, hpr);

    /* Set left channel offsets */
        chan2->setViewport(0.0f, 1.0f/2.0f, 0.0f, 1.0f);
        hpr.set(45.0f, 0.0f, 0.0f);
        xyz.set(0.0f, 0.0f, 0.0f);
        chan2->setViewOffsets(xyz, hpr);     

        .....
    { 
Return to the top.

Drawing into the different buffers

The most popular method of choosing which buffer to draw into is simply to alternate with each function call. The buffer must be chosen in the Draw callback routine before the call to pfDraw() and should be chosen before clearing the frame buffer. (There is another method of choosing buffers in which an argument is passed to the draw callback along with the channel and the buffer is chosen depending on the argument).

ex4: Choosing a buffer by alternating with each call to the draw callback.

    void
    PreDraw(pfChannel *chan, void *data)
    {
        static long stereoFlag = 0 ;

        if (stereoFlag)
          glDrawBuffer(GL_BACK_RIGHT);
        else    
          glDrawBuffer(GL_BACK_LEFT);
      
        stereoFlag = !stereoFlag;    

        .....
    }
Return to the top.

Overlapping views

To render channels with partially overlapping view ports, clear and draw the channels one by one from the back layer to the front layer. This is often done in a pre-draw function.

ex5: Clearing and drawing two partially overlapping channels.

    void
    PreDraw(void)
    {
        .....

        /* Clear the frame buffer of the background channel */
        chan->clear();

        /* Draw the background channel */
        pfDraw();

        /* Clear the frame buffer of the foreground channel */
        chan2->clear();

        /* Draw the foreground channel */
        pfDraw();
    }
Return to the top.

Buffer Swapping

In Stereo mode with quad-buffer stereo, the back buffers are drawn into and then swapped with the front buffers. The front buffers are rendered onto the screen. Swapping should occur automatically but in case it does not, a swapping function can be set for each pipe. This function can be set in the PipeWindow configuration function (In Hank the function is OpenWin()) or in the Pipe initialization function (InitPipe() in Hank).

ex6: A buffer swapping function.

    void OpenWin(pfPipeWindow *pw)
    {
        .....

        pfGetPipe(0)->setSwapFunc(StereoSwap(pw));
        .....
    }

    pfPipeSwapFuncType StereoSwap(pfPipeWindow *pw)
    {
        pw->swapBuffers();
        return 0;         /* prevents warnings in compilation */
    }
Return to the top.

Querying the system (for stereo)

A stereo query can be placed in the Pipe Window configuration function or in the Pipe initialization function. Channels should be rendered in stereo only if the query returns a positive response. (A stereo query can also be done with the X SGI stereo functions).

ex7: One way to discover stereo capability (Note: this particular method occasionally reports a falsely positive response)

    void OpenWin(pfPipeWindow *pw)
    {
        int          Stereo = 1; /* Stereo-Mode, default is on */
        int          stereo_flag = 0;

        .....

        pw->query(PFQWIN_STEREO,&stereo_flag);
        if(stereo_flag==PFQFTR_FALSE)
        {
          cout << "Mono-Mode" << endl;
          // here might be a good place to reset the channel
          // viewing offsets since stereo isn't possible.
          {
            pfVec3 xyz, hpr;
            xyz.set(0.0f, 0.0f, 0.0f);
            hpr.set(0.0f, 0.0f, 0.0f);
            chan->setViewOffsets(xyz,hpr);
            chan2->setViewOffsets(xyz,hpr);
          }
          Stereo=0;
        } 
        else
          cout << "Stereo-Mode" << endl;

        .....
    }

Return to the top.

Putting your monitor in stereo-mode

For Stereo, the window attributes should be set to double buffer and stereo. This can be done in the Pipe initialization function before the call to configure the pfPipeWindow. We have found that this sometimes only works when done before the configuration function for the pfPipeWindow is set.

ex8: Setting the window attributes to allow for stereo.

    static int FBAttrs[]={PFFB_DOUBLEBUFFER,
                          PFFB_STEREO,
                          None};

    void
    InitPipe(void)
    {

        pfPipeWindow *pw;

        .....

        pw->setFBConfigAttrs(FBAttrs); /* set window attributes */

        pw->setConfigFunc(OpenWin);    /* configuration function */
        pw->config();                  /* call to configure */

        .....
    }
Return to the top.

Return to the main page.