Chapter 11. Using DPLEX and Hyperpipes

The Digital Video Multiplexer ( DPLEX) is an optional daughter card that permits multiple graphics hardware pipelines in an Onyx-class system to work simultaneously on a single visual application. DPLEX provides this capability in hardware, which results in nearly perfect scaling of both geometry rate and fill rate on some applications.


Note: For more information about DPLEX, see http://www.sgi.com/onyx2/dvplex.html.

OpenGL Performer taps the power of DPLEX by using hyperpipes. This chapter describes how to use hyperpipes in the following sections:

Hyperpipe Concepts

A pfH yperpipe is a combination of pfPipes or pfMultipipes; there is one pfPipe for each graphics pipe in a DPLEX ring or chain. A DPLEX ring or chain is a collection of interconnected graphic boards.

Temporal Decomposition

Think of a rendered sequence as a 3D data set with time being the third axis. With temporal decomposition, the data set is subdivided along the time axis and distributed across, in this case, each of the graphic pipes in the hyperpipe group.

Temporal decomposition is different from spatial decomposition in which the data set is subdivided along the x, y, or both x and y axes.

Configuring Hyperpipes

It is the responsibility of the application to establish the hyperpipe group configuration for OpenGL Performer. There are two steps in the configuration process:

  1. Establish the number of graphic pipes (or pfPipes because there is a one-to-one correspondence) in each hyperpipe group.

  2. Map the pfPipes to specific graphic pipes.

Establishing the Number of Graphic Pipes

Use the argument in the pfHyperpipe() function to establish the number of graphic pipes in the hyperpipe group, for example:

pfHyperpipe(2);
pfConfig();

In this example, two pfPipes combine to create the pfHyperpipe, as shown in Figure 11-1.

Figure 11-1. pfPipes Creating pfHyperpipes


Like the pfMultipipe() function, pfHyperpipe() must be invoked prior to configuring the pfPipes using pfConfig() and after the call to pfInit().

The number of pipes is used by pfConfig() to associate the configured pfPipes. The pfHyperpipe() function can be invoked multiple times to construct multiple hyperpipe groups, as shown in Figure 11-2.

Figure 11-2. Multiple H yperpipes


Additionally, the pfHyperpipe() function can be combined with the pfMultipipe() call to configure pfPipes that are not associated with a hyperpipe group. The num argument to the pfMultipipe() function defines the total number of pfPipes to configure (including those in hyperpipe groups).

Example 11-1, diagrammed in Figure 11-2, shows the configuration of a system with three hyperpipe groups. The first hyperpipe group consists of three graphic pipes. The remaining two hyperpipe groups have two graphic pipes each. This example also configures one non- hyperpipe group graphic pipe.

Example 11-1. Configuring a System with Three Hyperpipe Groups

pfInit();
pfMultipipe(8);    /* need eight pfPipes 3-2-2-1 */
pfHyperpipe(3);    /* pfPipes 0, 1, 2 are the first group */
pfHyperpipe(2);    /* pfPipes 3, 4 are the second group */
pfHyperpipe(2);    /* pfPipes 5, 6 are the third group */
pfConfig();        /* construct the pfPipes */

If the target configuration includes only hyperpipe groups, it is not necessary to invoke pfMultipipe(). OpenGL Performer correctly determines the number of pfPipes from the pfHyperpipe() calls.

Mapping Hyperpipes to Graphic Pipes

The pfPipes constructed by pfConfig() are ordered into a linear array and are selected with the pfGetPipe() function. The pfPipes that are part of a hyperpipe group always appear in this array before any non-hyperpipe group pfPipes.

pfHyperpipe groups pfPipes together starting, by default, with pfPipe number 0. In the following example, there are four pfPipes; the first two are combined into a hyperpipe group:

pfMultipipe(4);
pfHyperpipe(2);
pfConfig();

Performer maps each pfPipe to a graphic pipe, which is associated with a specific X display, as shown in Figure 11-3:

Figure 11-3. Mapping to Graphic Pipes


Using Non-Default Mappings

Each graphics pipe is associated with only one X screen. By default, OpenGL Performer assigns each pfPipe to the screen of the default X display that matches the pfPipe index in the pfPipe array; in other words, pfPipe(0) in the hyperpipe is mapped to X screen 0.

In most configurations, this default mapping is not sufficient. The second phase, therefore, involves associating the configured pfPipes with the graphic pipes. This is achieved through the pfPipeScreen() or pfPipeWSConnectionName() function on the pfPipes of the hyperpipe group.

Example 11-2 shows, given the configuration in Example 11-1, how to map the pfPipes to the appropriate screens. In this example, all of the graphic pipes are managed under the same X display, that is, a different screen on the same display.

Example 11-2. Mapping Hyperpipes to Graphic Pipes

/* assign the single pfPipe to screen 0 */
pfPipeScreen(pfGetPipe(7), 0);

/* assign the pfPipes of hyperpipe group 0 to screens 1,2,3 */
for (i=0; i < 3; i++)
pfPipeScreen(pfGetPipe(i), i+1);
	
/* assign the pfPipes of hyperpipe group 1 to screens 4,5 */
for (i=3; i<5; i++)
    pfPipeScreen(pfGetPipe(i), i+1);

/* assign the pfPipes of hyperpipe group 2 to screens 6,7 */
for (i=5; i<7; i++)
    pfPipeScreen(pfGetPipe(i), i+1);

The following is a more complex example that uses GLXHyperpipeNetworkSGIX returned from glXQueryHyperpipeNetworkSGIX() to configure the pfPipes. This example is much more complete and is referred to in the following sections.

Example 11-3. More Complete Example of Mapping Hyperpipes to Graphic Pipe

int hasHyperpipe;
	GLXHyperpipeNetworkSGIX* hyperNet;
	int numHyperNet;
	int i;
	Display* dsp;
	int numNet;
	int pipeIdx;
	pfChannel* masterChan;

	/* initialize Performer */
	pfInit();

	/* does this configuration support hyperpipe */
	pfQueryFeature(PFQFTR_HYPERPIPE, &hasHyperpipe);
	if (!hasHyperpipe) {
	    pfNotify(PFNFY_FATAL, PFNFY_RESOURCE, "no hyperpipe support");
	    exit(1);
	}

	/* query the network */
	dsp = pfGetCurWSConnection();
	hyperNet = glXQueryHyperpipeNetworkSGIX(dsp, &numHyperNet);
	if (numHyperNet == 0) {
	    pfNotify(PFNFY_FATAL, PFNFY_RESOURCE, "no hyperpipes");
	    exit(1);
	}

	/*
	 * determine the number of distinct hyperpipe networks. network
	 * ids are monotonically increasing from zero. a value < 0
	 * is used to indicate pipes that are not members of any hyperpipe.
	 */
	for (i=0, numNet=-1; i<numHyperNet; i++)
	    if (numNet < hyperNet[i].networkId)
		numNet = hyperNet[i].networkId;
	numNet += 1;

	/*
	 * configure all of the hyperpipes in the net
	 *
	 * NOTE -
	 * while it is possible to be selective about which hyperpipe(s)
	 * to configure, that is left as an exercise.
	 */
	for (i=0; i<numNet; i++) {
	    int count = 0;
	    int j;
	    for (j=0; j<numHyperNet; j++)
    		if (hyperNet[i].networkId == i) count++;
	    pfHyperpipe(count);
	}

	pfConfig();

	/* associate pfPipes with screens */
	for (i=0, pipeIdx=0; i<numNet; i++) {
	    int j;
	    for (j=0; j<numHyperNet; j++)
    		if (hyperNet[i].networkId == i)
		    pfPipeWSConnectionName(pfGetPipe(pipeIdx++),
			    hyperNet[i].pipeName);
	}

	/* construct the pfPipeWindows for each hyperpipe */
	masterChan = NULL;
	for (i=0, pipeIdx=0; i<numNet; i++) {
	    pfPipe* pipe;
	    pfPipeWindow* pwin;
	    pfChannel* chan;
	    PFVEC3 xyz, hpr;

	    pipe = pfGetPipe(pipeIdx);
	    pwin = pfNewPWin(pipe);
	    pfPWinName(pwin, "Hyperpipe Window");

	    /*
	     * void
	     * openPipeWindow(pfPipeWindow* pwin)
	     * {
	     *     pfPWinOpen(pwin);
	     * }
	     */
	    pfPWinConfigFunc(pwin, openPipeWindow);
	    pfPWinFullScreen(pwin);
	    pfPWinMode(pwin, PFWIN_NOBORDER, 1);
	    pfPWinConfig(pwin);

	    chan = pfNewChan(pipe);
	    pfPWinAddChan(pwin, chan);

	    /*
	     * layout channels left to right in hyperpipe order. this
	     * ordering is arbitrary and should be redefined for the
	     * specific application.
	     */
	    pfChanShare(chan,
		    pfGetChanShare() | PFCHAN_VIEWPORT |
		    PFCHAN_SWAPBUFFERS | PFCHAN_SWAPBUFFERS_HW);
	    pfMakeSimpleChan(chan, 45);
	    pfChanAutoAspect(chan, PFFRUST_CALC_VERT);

	    xyz[0] = xyz[1] = xyz[2] = 0;
	    hpr[0] = (((numNet-1)*.5f)-i)*45.f;
	    hpr[1] = hpr[2] = 0;
	    pfChanViewOffsets(chan, xyz, hpr);
	    pfChanNearFar(.000001, 100000);

	    /*
	     * void
	     * drawFunc(pfChannel* chan, void* notUsed)
	     * {
	     *     pfClearChan(chan);
	     *     pfDraw();
	     * }
	     */
	    pfChanTravFunc(PFTRAV_DRAW, drawFunc);
	    if (i == 0)
		       masterChan = chan;
	    else
       		pfAttachChan(masterChan, chan);

	    /* bump to the first pipe of the next hyperpipe */
	    pipeIdx += pfGetHyperpipe(pipe);
	}

	/*
	 * the next step is to construct the scene, attach it to
	 * masterChan and start the main loop. this bit of code
	 * is not included here since it follows other demonstration
	 * applications included elsewhere in the Programmer's Guide.
	 */



Configuring pfPipeWindows and pfChannels

pfPipes grouped into a pfHyperpipe are indexed; the first pfPipe is pfPipe(0) and it is referred to as the master pfPipe. Most actions taken on the hyperpipe group are affected through this pfPipe; for example, all objects, such as pfPipeWindows and pfC hannels, are attached to the master pfPipe. OpenGL Performer automatically clones all objects, except pfChannels, across all of the pfPipes in the pfHyperpipe, as shown in Figure 11-4.

Figure 11-4. Attaching Objects to the Master pfPipe


When constructing pfPipeWindows or pfChannels, the pfPipe argument should be the master pfPipe. OpenGL Performer ensures that the constructed objects are cloned (pfPipeWindows) or attached (pfChannels) as needed to the other pfPipes in the hyperpipe group.

 With the exception of certain attributes, detailed below, OpenGL Performer propagates attribute updates to the cloned pfPipeWindows when they occur. The following is a list of pfPipeWindow functions for which the attributes do not propagate.

Table 11-1. pfPipeWindow Functions That Do Not Propagate

C Function

C++ Member Function

pfPWinSwapBarrier()

setSwapBarrier()

pfPWinWSConnectionName()

setWSConnectionName()

pfPWinOverlayWin()

setOverlayWin()

pfPWinStatsWin()

setStatsWin()

pfPWinScreen()

setScreen()

pfPWinWSWindow()

setWSWindow()

pfPWinWSDrawable()

setWSDrawable()

pfPWinFBConfigData()

setFBConfigData()

pfPWinFBConfigAttrs()

setFBConfigAttrs()

pfPWinFBConfig()

setFBConfig()

pfPWinFBConfigId()

setFBConfigId()

pfPWinGLCxt()

setGLCxt()

pfPWinList()

setWinList()

pfPWinPVChan()

setPVChan()

pfPWinAddPVChan()

addPVChan()

pfPWinRemovePVChan()

removePVChan()

pfPWinRemovePVChanIndex()

removePVChanIndex()

pfBindPWinPVChans()

bindPVChans()

pfUnbindPWinPVChans()

unbindPVChans()

pfSelectPWin()

select()

pfAttachPWinWin()

attachWin()

pfDetachPWinWin()

detachWin()

pfAttachPWin()

attach()

pfAttachPWinSwapGroup()

attachSwapGroup()

pfAttachPWinWinSwapGroup()

attachWinSwapGroup()

pfDetachPWinSwapGroup()

detachSwapGroup()

pfChoosePWinFBConfig()

chooseFBConfig()

When using any of the above interfaces within an application, set the appropriate attribute in the cloned pfPipeWindow.

Clones

Clones are identified by an index value. The index of a clone matches that of the master pfPipeWindow. This index is used to retrieve the clone pfPipeWindow from the other pfPipes in the hyperpipe group. Example 11-4 sets the FBConfigAttrs for each of the pfPipeWindows in the first hyperpipe group.

Example 11-4. Set FBConfigAttrs for Each pfPipeWindow.

static int attr[] = { 
	    GLX_RGBA,
	    GLX_DOUBLEBUFFER,
	    GLX_LEVEL, 0,
	    GLX_RED_SIZE, 8,
	    GLX_GREEN_SIZE, 8,
	    GLX_BLUE_SIZE, 8,
	    GLX_ALPHA_SIZE, 8,
	    GLX_DEPTH_SIZE, 16,
	    GLX_STENCIL_SIZE, 0,
	    GLX_ACCUM_RED_SIZE, 0,
	    GLX_SAMPLE_BUFFERS_SGIS, 1,
	    GLX_SAMPLES_SGIS, 4,
	    None
	};

	int numHyper = pfGetHyperpipe(pfGetPipe(0));
	for (i=0; i<numHyper; i++) {
	    /* get the first pfPipeWindow on pfPipe */
	    pfPipeWindow* pwin = pfGetPipePWin(pfGetPipe(i), 0);
	    pfPipeFBConfigAttrs(pwin, attr);
}

The current API has no support for directly querying the pfPipeWindow index within the pfPipe. The only mechanism to determine an index value is to track it in the application or search the pfPipeWindow list of the pfPipe. Example 11-5 performs such a search.

Example 11-5. Search the pfPipeWindow List of the pfPipe

/* search the master pfPipe pipe for the pfPipeWindow in pwin */
	int pwinIdx;
	int numPWins = pfGetPipeNumPWins(pipe);
	for (i=0; i<numPWins; i++)
	    if (pfGetPipePWin(pipe) == pwin) break;
	if (i == numPWins)
	    pfNotify(PFNFY_FATAL, PFNFY_PRINT, "oops!");
	pwinIdx = i;


Synchronization

When working with pfPipeWindows, it is possible for some updates to occur within the DRAW process. For this release (and possibly future releases) of OpenGL Performer, these updates are not automatically propagated to the clone pfPipeWindows. It is the responsibility of the application to ensure that the appropriate attributes are propagated or that similar actions occur on the clones.

The CULL and DRAW stages of different pfPipes within a hyperpipe group can run in parallel. For this reason, applications that assume a fixed pfChannel to pfPipe relationship or maintain global configuration data associated with a pfChannel that is updated in either the CULL or DRAW stages may fail. It is currently impossible (or at least very difficult) to transmit information from the CULL or DRAW stages of one pfPipe to another CULL or DRAW stage of another pfPipe within a hyperpipe group. All changes should be affected by the APP stage.

Programming with Hyperpipes

Programming with hyperpipes, as described above, generally involves the following steps:

  1. Configure the hyperpipe either on the fly or using a configuration file.

  2. Map screens to hyperpipes, if necessary.

  3. Allocate pfPipeWindow and pfChannels:

    • Create one pfPipeWindow for each pfHyperpipe.

    • Attach a pfPipeWindow to the master pfPipe.

    • Create a pfChannel for each pfHyperpipe.

  4. Start the main loop (pfFrame()...pfSync()).

There are two additional requirements for DPLEX:

  • You cannot use single buffer visuals.

    The DPLEX option uses the glXSwapBuffers() call as an indication to switch the multiplexor. This logic is bypassed for single buffered visuals.

  • glXSwapBuffers() and pfSwapWinBuffers() functions must not be invoked outside of the internal draw synchronization logic.

    Because the pfuDownloadTexList() function with the style parameter set to PFUTEX_SHOW calls glXSwapBuffers(), this feature must be disabled. (Simply set the style parameter to PFUTEX_APPLY).

    Also, the Perfly application displays a message at startup which also swaps the buffers. Again, this function must be disabled when using hyperpipe groups. The version of perfly that ships with performer_demo correctly disables these features.

Each pfPipe software rendering pipeline runs at a fraction of the target frame rate as defined by pfFrameRate(). The fraction is 1/(number of pipes in hyperpipe group). For example, if there are two pfPipes in the pfHyperpipe, each pfPipe runs at one half of the pfFrameRate(). Although the CULL and DRAW stages run at a slower rate, the APP stage must run at the target frame rate.