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);
}