New 4 wall CAVE programming interface (CAVElib 2.6alpha)

(This documentation is for the alpha0 version of the library, found in ~pape/cave/src/v2.6/alpha0/.)

The new application programming interface for the distributed (i.e. 4 wall) CAVE is based on the idea of "display data", which is pipelined from the computation process to the display processes. The main process requests a special buffer of shared memory; it performs computations and stores the results in the buffer, and then tells the library to pass on the new data. The data will be sent, frame accurately, to the display processes on the local (master) node, and on all the slave nodes. The application's rendering functions request a pointer to the latest display data buffer, and draw using that data.

The display data buffer can contain either all the shared data needed in rendering, or messages describing changes to the database. The second approach (messages) may be needed if an application has a very large data base and only makes small changes at a time.

This new interface also takes care of access control to the shared data, even in non-distributed CAVE applications. You should not need to do any semaphoring/locking to synchronize the computation and display processes's access to the display data.

The basic approach to using the display data functions is as follows:

	main()
	{
	...
	calcbuf = CAVEAllocDisplayData(size);
	if (CAVEDistribMaster())                /** This part is needed to    */
		{                               /** make sure that valid data */
		init calcbuf                    /** is available before       */
		CAVEPassDisplayData(0);         /** draw_function() starts    */
		}
	CAVEDisplay(draw_function,0);
	...
	if (CAVEDistribMaster())
	   while (1)
		{
		compute with calcbuf
		CAVEPassDisplayData(0);
		}
	else
	   while (!CAVESync->Quit) sleep(1);
	}

	draw_function()
	{
	drawbuf = CAVEGetDisplayData(NULL);
	render with drawbuf
	}
As shown above, both the master and slave nodes need to call CAVEAllocDisplayData, but only the master node should perform the calculations and call CAVEPassDisplayData().

Currently, only the TCP and Hippi distribution protocols have been implemented.

Function descriptions

boolean CAVEDistribMaster(void)
Same as before - returns TRUE on master node, FALSE on any slave nodes. Always returns TRUE when running in non-distributed mode.
volatile void * CAVEAllocDisplayData(size_t size)
Allocates shared memory to use for passing data to the display processes, and returns a pointer to one block for the computation process to use. size is the size of the block in bytes. Four blocks of memory are actually allocated - the one that is returned, one for the display processes to use, and two others that are used in staging the data when it is sent by CAVEPassDisplayData(). The blocks are all allocated using CAVEMalloc(), so its shared arena must be large enough to hold the four blocks, plus any other shared data the application will allocated (the size can be set by CAVESetOption()). Note: Currently, only one display data buffer can be allocated (i.e. CAVEAllocDisplayData() should only be called once).
void CAVEPassDisplayData(size_t size)
Sends data from the buffer returned by CAVEAllocDisplayData() to the display processes. size is the number of bytes to send, starting from the beginning of the buffer; if size is 0, the entire buffer is sent. The data is copied from the computation process's buffer into one of the staging buffers, which will then be returned by CAVEGetDisplayData() on the next frame.
volatile void * CAVEGetDisplayData(size_t *size)
Returns a pointer to the shared buffer with the latest data which has been passed to the display processes. If size is non-NULL, it will return the number of bytes which were sent by CAVEPassDisplayData().
boolean CAVEDisplayDataChanged(void)
Returns TRUE if new data has been passed to the display processes by CAVEPassDisplayData() since the previous frame.
boolean CAVEDistribOpenConnection(int chanID)
Opens a communication channel between the master and slave nodes of the distributed CAVE. chanID is an integer identifying the channel; it must be in the range 0 to CAVE_DISTRIB_MAX_CHANNELID (defined in cave.h, currently 7). Returns TRUE if successful, FALSE if the channel could not be opened. Once the channel is opened, it can be used with CAVEDistribRead(), CAVEDistribWrite(), CAVEDistribBarrier(), and CAVEDistribCloseConnection(); these functions should only be called in the same process which opened the channel. Only one process on a CAVE node should open a given channel.
void CAVEDistribCloseConnection(int chanID)
Closes a previously opened distributed CAVE communications channel. chanID is the channel ID which was passed to CAVEDistribOpenConnection().
void CAVEDistribWrite(int chanID,void *buf,size_t size)
Sends a block of data over a given distributed CAVE channel. chanID is the channel ID which was passed to CAVEDistribOpenConnection(); buf is a pointer to the data to send; size is the number of bytes to send. When called by the master node, this sends a copy of the data to each of the slave nodes; when called by a slave node, this sends the data only to the master.
size_t CAVEDistribRead(int chanID,void *buf,size_t size)
Receives the next block of data sent over a distributed CAVE channel by CAVEDistribWrite(). chanID is the channel ID which was passed to CAVEDistribOpenConnection(); buf is a pointer to the buffer to receive the data in; size is maximum number of bytes to receive. The return value is the number of bytes that were actually read. This function blocks until data is received. On the master node, a single call to CAVEDistribRead() will receive data from only one of the slave nodes, in no particular order; it should be called once for each of the slaves (assuming they all call CAVEDistribWrite()); the number of slave nodes is CAVEDistribNumNodes()-1.
void CAVEDistribBarrier(int chanID)
Blocks until all of the distributed CAVE nodes reach the barrier. chanID is the channel ID which was passed to CAVEDistribOpenConnection().
int CAVEDistribNumNodes(void)
Returns the number of nodes in the distributed CAVE.

Sample program (bouncing balls)

#include <cave.h>
#include <malloc.h>
#include <gl/sphere.h>

struct _balldata
	{
	float y;
	};

void init_gl(void),draw_balls(void);

main(int argc,char **argv)
{
 struct _balldata *ball;
 CAVEConfigure(&argc,argv,NULL);
 ball = (struct _balldata *) CAVEAllocDisplayData(2*sizeof(struct _balldata));
 CAVEInit();
 if (CAVEDistribMaster())
	{
	ball[0].y = ball[1].y = 0;
	CAVEPassDisplayData(0);
	}
 CAVEInitApplication(init_gl,0);
 CAVEDisplay(draw_balls,0);
 if (CAVEDistribMaster())
	while (!getbutton(ESCKEY))
		{
		float t = CAVEGetTime();
		ball[0].y = fabs(sin(t)) * 6 + 1;
		ball[1].y = fabs(sin(t*1.2)) * 4 + 1;
		CAVEPassDisplayData(0);
		sginap(2);
		}
 else
	while (!CAVESync->Quit)
		sginap(25);
 CAVEExit();
}

void init_gl(void)
{
 float redMaterial[] = { DIFFUSE, 1, 0, 0, LMNULL };
 float blueMaterial[] = { DIFFUSE, 0, 0, 1, LMNULL };
 lmdef(DEFLMODEL,1,0,NULL);
 lmbind(LMODEL,1);
 lmdef(DEFLIGHT,1,0,NULL);
 lmbind(LIGHT0,1);
 lmdef(DEFMATERIAL,1,0,redMaterial);
 lmdef(DEFMATERIAL,2,0,blueMaterial);
}

void draw_balls(void)
{
 struct _balldata *ball;
 float sphereParam0[] = { 2, 4, -5, 1}, sphereParam1[] = { -2, 4, -5, 1};

 ball = (struct _balldata *) CAVEGetDisplayData(NULL);

 czclear(0,getgdesc(GD_ZMAX));

 lmbind(MATERIAL,1);
 sphereParam0[1] = ball[0].y;
 sphdraw(sphereParam0);

 lmbind(MATERIAL,2);
 sphereParam1[1] = ball[1].y;
 sphdraw(sphereParam1);
}