[BACK]Return to divotex.c CVS log [TXT][DIR] Up to [Development] / performer / src / pguide / libpf / C

File: [Development] / performer / src / pguide / libpf / C / divotex.c (download)

Revision 1.1, Tue Nov 21 21:39:36 2000 UTC (16 years, 11 months ago) by flynnt
Branch: MAIN
CVS Tags: HEAD

Initial check-in based on OpenGL Performer 2.4 tree.
-flynnt

/*
 * Copyright 2000 Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * This source code ("Source Code") was originally derived from a
 * code base owned by Silicon Graphics, Inc. ("SGI")
 * 
 * LICENSE: SGI grants the user ("Licensee") permission to reproduce,
 * distribute, and create derivative works from this Source Code,
 * provided that: (1) the user reproduces this entire notice within
 * both source and binary format redistributions and any accompanying
 * materials such as documentation in printed or electronic format;
 * (2) the Source Code is not to be used, or ported or modified for
 * use, except in conjunction with OpenGL Performer; and (3) the
 * names of Silicon Graphics, Inc.  and SGI may not be used in any
 * advertising or publicity relating to the Source Code without the
 * prior written permission of SGI.  No further license or permission
 * may be inferred or deemed or construed to exist with regard to the
 * Source Code or the code base of which it forms a part. All rights
 * not expressly granted are reserved.
 * 
 * This Source Code is provided to Licensee AS IS, without any
 * warranty of any kind, either express, implied, or statutory,
 * including, but not limited to, any warranty that the Source Code
 * will conform to specifications, any implied warranties of
 * merchantability, fitness for a particular purpose, and freedom
 * from infringement, and any warranty that the documentation will
 * conform to the program, or any warranty that the Source Code will
 * be error free.
 * 
 * IN NO EVENT WILL SGI BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT
 * LIMITED TO DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES,
 * ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED WITH THE
 * SOURCE CODE, WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT OR
 * OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM,
 * OR AROSE OUT OF USE OR RESULTS FROM USE OF, OR LACK OF ABILITY TO
 * USE, THE SOURCE CODE.
 * 
 * Contact information:  Silicon Graphics, Inc., 
 * 1600 Amphitheatre Pkwy, Mountain View, CA  94043, 
 * or:  http://www.sgi.com
 */

/* This code works for CCIR601 525 using DIVO on ONYX2. */
/* It has been modified from movietex.c to work and is */
/* probably better for other platforms also but this has */
/* not been confirmed so both examples exist here. */
/* movietex.c almost certainly has some problems. */
/* do a diff to see the differences if you are interested */
/* in using video texture on other systems. Remember there's */
/* an O2 specific example in this directory also. */


#include <stdlib.h>
#include <getopt.h> /* for cmdline handler */
#include <X11/keysym.h>
#include <vl/vl.h>
#include <getopt.h> /* for cmdline handler */
#include <Performer/pf.h>
#include <Performer/pfutil.h>
#include <Performer/pfdu.h>

/* 
 * file: videotex.c
 * ----------------
 *
 * demonstrates use of video input for textures across SGI product line
 *
 * cmdline controls
 *  -m procsplit : multiprocess mode
 *  -v	: use fake video loop from memory - not real video input
 *
 * Run-time controls:
 *       ESC-key: exits
 *      g/s-keys: stats profile
 *       SPACE  : toggle spinning of video cube
 */

#define NUMANIMFRAMES 7
unsigned int animation[];
#define TEXW 8
#define TEXH 8


/* Performer config vars */
static int ProcSplit = PFMP_DEFAULT;
static int Video = 1;
static char ProgName[PF_MAXSTRING];

static pfMatrix texture_matrix = {
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
};

/*
** As usual, anything that the cull or draw processes need to access must be
** placed in shared memory.  I put these things in shared memory just in
** case an application might have a use for them there.
*/
typedef struct {
    /* video scene stuff */
    pfTexture	*vtex;
    pfGeoState  *gstate;
    
    /* video stuff */
    int		tvxsize, tvysize; /* size of texture */
    int		vXSize, vYSize; /* video frame size */
    int		fXSize, fYSize; /* memory field size */
    int		F1_is_first;   /* Which field is first (i.e., spacially higher) */
    int		packing;
    VLServer	svr;
    VLPath	path;
    VLNode	src, drn;
    GLXVideoSourceSGIX glxvidsrc;
    
    /* video config controls */
    int		interlace;
    int		vldmedia; /* use old style VL (RE and OCTANE) */
    int		divo;
    int		octane;
    
    /* program controls */
    pfPipeWindow *pw;
    pfWSConnection dsp;
    int		spin;
    int		stats;
    int		XInputInited;
    int		exitFlag;

} shareData;
shareData *Shared;


static void OpenPipeWin(pfPipeWindow *pw);
static pfGeoSet *cube( void );
static void InitXInput(pfWSConnection dsp);
static void GetXInput(Display *dsp);

/******************************************************************************
 *			    VL Variables and Routines
 ******************************************************************************
 */

int capType =  VL_CAPTURE_NONINTERLEAVED;


/* VL Draw Process Variables */
#ifndef IRIX6_2
DMbufferpool pool = 0; /* needed for cleanup */
#endif

void
UpdateTiming(void)
{
    VLControlValue timing, dominance;

    if (vlGetControl(Shared->svr, Shared->path, Shared->src, VL_TIMING, &timing) <0) {
	vlPerror("VlGetControl of timing");
	exit(EXIT_FAILURE);
    }
    /*
     * in 525 ("NTSC"), the odd field is drawn first (higher spacially).
     * in 625 ("PAL"), the even field is drawn first
     */
    Shared->F1_is_first = ( (timing.intVal == VL_TIMING_625_SQ_PIX) ||
		  (timing.intVal == VL_TIMING_625_CCIR601) );

    if (Shared->divo)
    {
#ifndef IRIX6_2
	if (vlGetControl(Shared->svr, Shared->path, Shared->src, 
				    VL_FIELD_DOMINANCE, &dominance) <0) 
	{
	    vlPerror("VlGetControl of dominance");
	    exit(EXIT_FAILURE);
	}
	Shared->F1_is_first ^= (dominance.intVal == VL_F2_IS_DOMINANT);
#endif
    }
}

static void
InitializeVL(void)
{
#ifndef IRIX6_2
    DMparams* plist;
#endif
    VLControlValue size;
    VLDevList	devlist;
    VLTransferDescriptor xferDesc;
    VLControlValue val;
    VLDev	deviceId;
    int		devicenum = -1;
    int		mask;
    int		frameSize;

    
    
    /* open connection to VL server */
    if (!(Shared->svr = vlOpenVideo(""))) {
	printf("couldn't open connection to VL server\n");
	exit(EXIT_FAILURE);
    }

    /* Get the list of devices that the daemon supports */
    if (vlGetDeviceList(Shared->svr, &devlist) < 0) {
	fprintf(stderr, "%s: getting device list: %s\n",
		ProgName, vlStrError(vlErrno));
	exit(1);
    }

    /* Set up a source node from video */
    Shared->src = vlGetNode(Shared->svr, VL_SRC, VL_VIDEO, VL_ANY);

    /* Set up a drain node in memory */
    if (Shared->vldmedia)
	Shared->drn = vlGetNode(Shared->svr, VL_DRN, VL_MEM, VL_ANY);
    else
	Shared->drn = vlGetNode(Shared->svr, VL_DRN, VL_TEXTURE, VL_ANY);


    /*
     * If the user didn't specify a device create a path using the first
     * device that will support it
     */
    if (devicenum == -1) {
	if ((Shared->path = vlCreatePath(Shared->svr, VL_ANY, Shared->src, Shared->drn)) < 0) {
	    vlPerror(ProgName);
	    exit(1);
	}
	/* Get the device number and name */
	devicenum = vlGetDevice(Shared->svr, Shared->path);
    }
    else 
    {
	/* Create a path using the user specified device */
	deviceId   = devlist.devices[devicenum].dev;
	if ((Shared->path = vlCreatePath(Shared->svr, deviceId, Shared->src, Shared->drn)) < 0)
	    {
		vlPerror(ProgName);
		exit(1);
	    }
    }


    /* Set up the hardware for and define the usage of the path */
    if (vlSetupPaths(Shared->svr, (VLPathList)&Shared->path, 1, VL_SHARE, VL_SHARE) < 0) {
	vlPerror(ProgName);
	exit(1);
    }

    if (Shared->vldmedia)
    {
	/*
	 * Specify what path-related events we want to receive.
	 * In this example we want transfer complete and transfer failed
	 * events only.
	 */
	mask= VLStreamStartedMask | VLTransferCompleteMask | VLTransferFailedMask
	    | VLStreamStoppedMask | VLSequenceLostMask;
	vlSelectEvents(Shared->svr, Shared->path, mask);
    }

    /* VL Congif vars and defaults */
    if (Shared->vldmedia)
    {
#ifndef IRIX6_2    
	if (Shared->divo)
	    Shared->packing = VL_PACKING_4444_8; /* DIVO only */
	else
	    Shared->packing = VL_PACKING_ABGR_8; /* O2  */
#endif
    }
    else
	Shared->packing = VL_PACKING_RGB_8;

    val.intVal = Shared->packing;
    if (vlSetControl(Shared->svr, Shared->path, Shared->drn, VL_PACKING, &val) < 0) {
	vlPerror("vlSetControl(VL_PACKING)");
	pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "PACKING: %d", Shared->packing);
	exit(1);
    }

    if (Shared->divo)
    {
#ifndef IRIX6_2
	int cspace = VL_COLORSPACE_RGB;
	val.intVal = cspace;
	if (vlSetControl(Shared->svr, Shared->path, Shared->drn, VL_COLORSPACE, &val) < 0) {
	    vlPerror("vlSetControl(VL_COLORSPACE_RGB)");
	    exit(1);
	}
#endif
    }

    /*
     * we want individual fields, so we can demonstrate manual interleaving
     */
    val.intVal = capType;
    if (vlSetControl(Shared->svr, Shared->path, Shared->drn, VL_CAP_TYPE, &val) < 0) {
	vlPerror("vlSetControl(VL_CAP_TYPE)");
	exit(1);
    }
    
    /* get input mem frame size */
    vlGetControl(Shared->svr, Shared->path, Shared->src, VL_SIZE, &size);\
    Shared->fXSize = size.xyVal.x;
    Shared->fYSize = size.xyVal.y;
    fprintf(stderr,"frame: xsize is %d, ysize is %d\n",size.xyVal.x, size.xyVal.y);
    /* get video field size for texture which is based on field size */
    vlGetControl(Shared->svr, Shared->path, Shared->drn, VL_SIZE, &size);
    fprintf(stderr,"field: xsize is %d, ysize is %d\n",size.xyVal.x, size.xyVal.y);
    Shared->vXSize = size.xyVal.x;
    Shared->vYSize = size.xyVal.y;

    frameSize = vlGetTransferSize(Shared->svr, Shared->path);
#ifndef IRIX6_2
    if (dmParamsCreate(&plist)) {
        printf("dmParamsCreate failed!\n");
        exit(1);
    }
#endif

    if (Shared->vldmedia) /* O2 and ONYX2 */
    {
#ifndef IRIX6_2
	{
	    int videoBufCt;
	    dmBufferSetPoolDefaults(plist, 4, frameSize, DM_FALSE, DM_FALSE);
	    dmParamsSetInt(plist, DM_BUFFER_COUNT, 0);
	    vlDMGetParams(Shared->svr, Shared->path, Shared->drn, plist);
	    videoBufCt = dmParamsGetInt(plist, DM_BUFFER_COUNT);
	    dmParamsSetInt(plist, DM_BUFFER_COUNT, videoBufCt + 1);
	    if (dmBufferCreatePool(plist, &pool) != DM_SUCCESS) 
	    {
		printf("dmBufferCreatePool failed %s\n", dmGetError(0, 0));
	    }
	    dmParamsDestroy(plist);
	}
    
	if (!pool) {
	    vlPerror(ProgName);
	    exit(1);
	}
    
	/* Associate the pool with the path */
	if (vlDMPoolRegister(Shared->svr, Shared->path, Shared->drn, pool)) 
	{
	    vlPerror(ProgName);
	    dmBufferDestroyPool(pool);
	    exit(1);
	}
#endif
    }
    UpdateTiming();

    /* On Octane and Sirius, use GLXVideoSource to get video data */
    if (!Shared->vldmedia) {
      Shared->dsp =  pfGetCurWSConnection();
      Shared->glxvidsrc = 
	glXCreateGLXVideoSourceSGIX(Shared->dsp, DefaultScreen(Shared->dsp),
				    Shared->svr,Shared->path,
				    VL_TEXTURE,Shared->drn);
    }

    if (Shared->divo)
    {
	xferDesc.mode = VL_TRANSFER_MODE_CONTINUOUS;
	xferDesc.count = 0;
	xferDesc.delay = 0;
	xferDesc.trigger = VLTriggerImmediate;
    
	/* Do not need gl cxt or win for this.  */
	vlBeginTransfer(Shared->svr, Shared->path, 1, &xferDesc);
    }
    else
	vlBeginTransfer(Shared->svr, Shared->path, 0, NULL );
}

void
cleanup(void)
{
    vlEndTransfer(Shared->svr, Shared->path);
#ifndef IRIX6_2
    if (pool)
    {
	vlDMPoolDeregister(Shared->svr, Shared->path, Shared->drn, pool);
	dmBufferDestroyPool(pool);
    }
#endif
    vlDestroyPath(Shared->svr, Shared->path);
    vlCloseVideo(Shared->svr);
    exit(EXIT_SUCCESS);
}

void
ProcessVideoEvents(void)
{
    VLEvent ev;

    if (vlCheckEvent(Shared->svr,
		     VLSequenceLostMask|VLStreamPreemptedMask|VLTransferCompleteMask|VLTransferFailedMask |
			VLStreamStartedMask | VLStreamStoppedMask,
		     &ev) == -1) {
	return;
    }
    switch(ev.reason) {
      case VLSequenceLost:
	pfNotify(PFNFY_INFO,PFNFY_PRINT,"VLSequenceLost....\n");
	break;
      case VLTransferComplete:
	pfNotify(PFNFY_DEBUG,PFNFY_PRINT,"VLTransferComplete....\n");
	break;
      case VLTransferFailed:
	pfNotify(PFNFY_NOTICE,PFNFY_PRINT,"VLTransfer FAILED ....\n");
	break;
      case VLStreamPreempted:
        cleanup();
        exit(EXIT_SUCCESS);
	/*NOTREACHED*/
      default:
	break;
    }
}



/******************************************************************************
 *			    Performer Structural Routines
 ******************************************************************************
 */

static void 
ConfigDraw( int pipe, uint stage )
{
    /* Ininialize VL in draw stage config function */
    if (Video)
	InitializeVL();
    
}

static void
InitVideoTexture(void)
{
    int texFormat;
    unsigned int    *img=NULL;
    
    Shared->tvxsize = TEXW;
    Shared->tvysize = TEXH;
    
    if (!Shared->vtex)
    {
	pfNotify(PFNFY_NOTICE,PFNFY_PRINT,"Video Texture not initialized!!!!");
	return;
    }
    
    {
	float vheight = Shared->vYSize * 2.0f; /* XXX assume draw config is done */
	float s_scale, t_scale;

	/* Establish offsets that will align right side of the video texture on
	 * a 32 texel boundary.  This is a requirement of Impact graphics.
	 */
	int fieldWidth = Shared->vXSize;
	int xOffset = ((fieldWidth + 0x001f) & ~0x001f) - fieldWidth;

        /* well zero sounds just fine to me,
         * besides the above tends to produce an offset of 16
         * and the texture coordinates and matrix don't even
         * know about this shift so it can't work
         */
        xOffset = 0;
	
	pfTexLoadOrigin(Shared->vtex, PFTEX_ORIGIN_DEST, xOffset, 0);
	pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "Tex Load x offset: %d", xOffset);

	/* compute texture size - must be power of 2 in each dimension */
	Shared->tvxsize = 1;
	Shared->tvysize = 1;
	while (Shared->tvxsize < Shared->vXSize) Shared->tvxsize <<= 1;
 	while (Shared->tvysize < vheight) Shared->tvysize <<= 1;
	s_scale = (float)Shared->vXSize / (float)Shared->tvxsize;
	t_scale = vheight / (float)Shared->tvysize;

	texture_matrix[0][0] =  s_scale;
	texture_matrix[1][1] =  -t_scale; 
	texture_matrix[3][1] =  t_scale;
    }

    if (Video) {
    /* configure the pfTexture for video */
    pfTexImage(Shared->vtex, img, 4, Shared->tvxsize, Shared->tvysize, 0);
    pfTexLoadSize(Shared->vtex, Shared->vXSize, Shared->vYSize);
    /* these modes are important */
    pfTexFilter(Shared->vtex, PFTEX_MINFILTER, PFTEX_LINEAR);
    pfTexFilter(Shared->vtex, PFTEX_MAGFILTER, PFTEX_LINEAR);
    pfTexFormat(Shared->vtex, PFTEX_SUBLOAD_FORMAT, PF_ON);
    pfTexFormat(Shared->vtex, PFTEX_INTERNAL_FORMAT, GL_RGBA8_EXT);
    
    /* Texture external format should match the VL packing format */
    switch(Shared->packing)
    {
#ifndef IRIX6_2
	case VL_PACKING_X4444_5551:
	    texFormat = PFTEX_UNSIGNED_SHORT_5_5_5_1;
	    break;
	case VL_PACKING_444_332:
	    texFormat = PFTEX_UNSIGNED_BYTE_3_3_2;
	    break;
	case VL_PACKING_4444_8:
#endif
	case VL_PACKING_ABGR_8:
	default:
	    texFormat = PFTEX_PACK_8;
    }
    pfTexFormat(Shared->vtex, PFTEX_EXTERNAL_FORMAT, texFormat);
   
    pfTexLoadMode(Shared->vtex, PFTEX_LOAD_BASE, PFTEX_BASE_AUTO_SUBLOAD);

    pfTexLoadMode(Shared->vtex, PFTEX_LOAD_SOURCE, PFTEX_SOURCE_VIDEO);
      

    if (Shared->vldmedia)
      {
	/* these settings come from InitializeVL called in the draw process */
	pfTexLoadVal(Shared->vtex, PFTEX_LOAD_VIDEO_VLSERVER, (void*)Shared->svr);
	pfTexLoadVal(Shared->vtex, PFTEX_LOAD_VIDEO_VLPATH, (void*)Shared->path);
	pfTexLoadVal(Shared->vtex, PFTEX_LOAD_VIDEO_VLDRAIN, (void*)Shared->drn);
      }
    
    if (Shared->interlace)
      {
	pfTexLoadMode(Shared->vtex, PFTEX_LOAD_VIDEO_INTERLACED, 	
		      (Shared->F1_is_first ? PFTEX_VIDEO_INTERLACED_ODD : PFTEX_VIDEO_INTERLACED_EVEN));
      }
    }
}
 
static void
OpenPipeWin(pfPipeWindow *pw)
{
    /* a pfState is created for the pfPipeWindow upon open */
    pfOpenPWin(pw);
    
    /* set the per-draw process video params on the pfTexture */
      InitVideoTexture();

}



/*
** This program doesn't need to have a draw callback, but I put one in just
** in case DIVO apps need to do something here (such as initiate the video
** load or do some sort of synchronization).
*/
static void
drawfunc(pfChannel *chan, void *data)
{
    pfClearChan(chan);

    if (Video) {
      if (!Shared->vldmedia) {
	glXMakeCurrentReadSGI(Shared->dsp,pfGetPWinCurWSDrawable(Shared->pw),
			      Shared->glxvidsrc,
			      pfGetPWinGLCxt(Shared->pw));
      }
    }

    pfDraw();
    if (Shared->stats)
	pfDrawChanStats(chan);
    
    /* XXXX Yuck - we'd like this to be in the application process!
     * Also,  putting it here means it can hold off swapbuffers
     * so we will need to had a post-swap-func callback to Performer
     */
    if (Video)
	ProcessVideoEvents();

   {
   int err;
   if ((err = glGetError()) != GL_NO_ERROR)
        pfNotify(PFNFY_NOTICE,PFNFY_USAGE,"OpenGL Error 0x%x - %s",err, gluErrorString(err));
   }
}


static int
docmdline(int argc, char *argv[])
{
    int	    opt;

    strcpy(ProgName, argv[0]);
    
    /* process command-line arguments */
    while ((opt = getopt(argc, argv, "m:p:sv?")) != -1)
    {
	switch (opt)
	{
	case 's':
	    Shared->spin ^= 1;
	    break;
	case 'm':
	case 'p':
	    ProcSplit = atoi(optarg);
	    break;
	case 'v':
	    Video ^= 1;
	}
    }
    return optind;
}


int
main (int argc, char *argv[])
{
    float	    t = 0;
    pfScene	    *scene;
    pfGroup	    *root;
    pfPipe	    *p;
    pfChannel	    *chan;
    pfSphere	    bsphere;
    pfGeode         *geode;
    pfTexture	    *vtex;
    pfGeoState      *gstate;
    pfGeoSet	    *gset;
    pfCoord	    view;
    void	    *arena;
    int		    arg;
    unsigned int    *img=NULL;
    int		    queryvals[2];
static int querytoks[] = {PFQFTR_VLDMEDIA, PFQFTR_INTERLACE, 0};


    pfInit();	
    arena = pfGetSharedArena();
    Shared = pfCalloc(1, sizeof(shareData), arena);
    Shared->stats = 1;
    
    /* query machine dependent parameters */
    {
	pfMQueryFeature(querytoks, queryvals);
	Shared->vldmedia =  (queryvals[0] != 0);
	Shared->interlace = 1; 
	/* check for DIVO vs Sirius */
	Shared->divo = ((strncasecmp(pfGetMachString(), "IRL", 3)== 0) ||
			(strncasecmp(pfGetMachString(), "IRE",3)== 0));
	/* another check for Octane because the video is flipped vertically */
	if (!Shared->divo)
	{
	    Shared->octane = (strncasecmp(pfGetMachString(), "MGRAS", 5)== 0);
	}
	if (!Shared->divo)
	    Shared->interlace = 0;
	pfNotify(PFNFY_NOTICE, PFNFY_PRINT, "Config %s: vl=%d divo=%d octane=%d", 
	    pfGetMachString(), Shared->vldmedia, Shared->divo, Shared->octane);
    }
    
    arg = docmdline(argc, argv);
    
    pfMultiprocess(ProcSplit);
    pfConfig();	

    /* Shared->put these in shared memory so draw process can access them */

    /* set up scene graph */
    geode = pfNewGeode(); 
    gset = cube();
    pfAddGSet(geode, gset);
    root = pfNewGroup();
    pfAddChild(root, geode);
    scene = pfNewScene();
    pfAddChild(scene, root);
    pfAddChild(scene, pfNewLSource());

    /* make video texture */
    Shared->vtex = vtex = pfNewTex(arena);

    /* make a window */
    p = pfGetPipe(0);
    pfStageConfigFunc(0, PFPROC_DRAW, ConfigDraw);
    
    Shared->pw = pfNewPWin(p);
    pfPWinType(Shared->pw, PFPWIN_TYPE_X);
    pfPWinName(Shared->pw, "videotex");
    pfPWinOriginSize(Shared->pw, 0, 0, 720, 486);
    pfPWinConfigFunc(Shared->pw, OpenPipeWin);

    /* OpenWindow and Init Draw */
    pfConfigStage(0, PFPROC_DRAW);
    pfConfigPWin(Shared->pw);
    pfFrame();
    
    /* make a channel */
    chan = pfNewChan(p);	
    pfChanScene(chan, scene);
    pfChanFOV(chan, 45, 45);
    pfChanTravFunc(chan, PFTRAV_DRAW, drawfunc); /* per-frame draw func */

    /* determine extent of scene's geometry */
    pfGetNodeBSphere (root, &bsphere);
    pfChanNearFar(chan, 0.5f, 10.0f * bsphere.radius);

    pfTexRepeat(vtex, PFTEX_WRAP_S, PFTEX_CLAMP);
    pfTexRepeat(vtex, PFTEX_WRAP_T, PFTEX_CLAMP);
    
    /* make a geostate */
    gstate = pfNewGState (arena);
    pfGSetGState(gset, gstate);
    pfGStateMode(gstate, PFSTATE_ENTEXTURE, 1);
    pfGStateAttr(gstate, PFSTATE_TEXTURE, vtex);
    pfGStateMode(gstate, PFSTATE_ENTEXMAT, 1);
    pfGStateMode(gstate, PFSTATE_CULLFACE, PFCF_BACK);
    pfGStateAttr(gstate, PFSTATE_TEXMAT, texture_matrix);

    if (!Video) {
	img = (unsigned int *) pfCalloc(Shared->tvxsize * Shared->tvysize * sizeof(int), 1, NULL);
    }

    pfFrame();

    while (!Shared->exitFlag)
    {
	static int i=0;
	float      s, c;
	unsigned int *pb;
	
	/* set view position for next frame */
	if (Shared->spin)
	{
	t = pfGetTime();
	/* Compute new view position for next frame. */
	pfSinCos(45.0f*t, &s, &c);
	pfSetVec3(view.hpr, 45.0f*t, -20.0f, 0);
	pfSetVec3(view.xyz, 3.0f * bsphere.radius * s, 
		-3.0f * bsphere.radius *c, 
		 1.1f * bsphere.radius);
	}
	else
	{
	    pfSetVec3(view.hpr, 0.0f, 0.0f, 0);
            /* assumes 2x2x2 cube centered at origin */
	    /* tan 22.5 degrees (angle to edge of cube @ 45 degree cube) */
	    /* this gives correct proportions in conjunction wit texture */
	    /* matrix and window resolution. */
	    pfSetVec3(view.xyz, 0, -1.0f - 2.4142136f, 0);
	}
	
	pfChanView(chan, view.xyz, view.hpr);
	
	pfSync();

	/* Update the buffer for the next frame.  Here we simply copy an
	** image from the animation array into the buffer, but DIVO would
	** initiate a video load into the buffer instead.
	*/
	if (!Video)
	{
	    /* Divide i by 10 to slow down the speed of the animation. */
	    pb = animation + Shared->tvxsize * Shared->tvysize * ((i/10) % NUMANIMFRAMES);
	    memcpy(img, pb, Shared->tvxsize * Shared->tvysize * sizeof(int));
	    pfTexLoadImage(Shared->vtex, img);
	    i++;
	}

	pfFrame();
	
	{
	    static pfWSConnection dsp = NULL;
	    if (!dsp)
		dsp = pfGetCurWSConnection();
	    if (!Shared->XInputInited)
		InitXInput(dsp);
	    if (Shared->XInputInited)
		GetXInput(dsp);
	}

    }
    pfExit();
    return 0;
}


/******************************************************************************
 *				XInput Handler
 ******************************************************************************
 */

static void 
InitXInput(pfWSConnection dsp)
{
    Window w;
    
    /* wait for X Window to exist in Performer shared memory */
    if (w = pfGetPWinWSWindow(Shared->pw))
    {
	XSelectInput(dsp, w, PointerMotionMask |
			ButtonPressMask | ButtonReleaseMask | KeyPressMask);
	XMapWindow(dsp, w);
	XFlush(dsp);
	Shared->XInputInited = 1;
    }
}

static void
GetXInput(pfWSConnection dsp)
{
    if (XEventsQueued(dsp, QueuedAfterFlush))
    while (XEventsQueued(dsp, QueuedAlready))
    {
	XEvent event;
	    
	XNextEvent(dsp, &event);

	switch (event.type) 
	{
	case KeyPress:
	{
	    char buf[100];
	    int rv;
	    KeySym ks;
	    rv = XLookupString(&event.xkey, buf, sizeof(buf), &ks, 0);
	    switch(ks) {
	    case XK_Escape: 
		Shared->exitFlag = 1;
		exit(0);
		break;
	    case XK_space:
		Shared->spin ^= 1;
		break;
	    case XK_g:
	    case XK_s:
		Shared->stats = !Shared->stats;
		break;
	    default:
		break;
	    }
	}
	break;
	default:
	    break;
	}/* switch */
    }
}



/******************************************************************************
 *				data for faked memory video loop
 ******************************************************************************
 */


#define B 0x000000ff
#define W 0xffffffff

unsigned int animation[] = {
    /* frame 0 */
    B,B,W,W,W,W,W,W,
    B,B,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,

    /* frame 1 */
    W,W,W,W,W,W,W,W,
    W,B,B,W,W,W,W,W,
    W,B,B,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,

    /* frame 2 */
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,B,B,W,W,W,W,
    W,W,B,B,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,

    /* frame 3 */
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,B,B,W,W,W,
    W,W,W,B,B,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,

    /* frame 4 */
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,B,B,W,W,
    W,W,W,W,B,B,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,

    /* frame 5 */
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,B,B,W,
    W,W,W,W,W,B,B,W,
    W,W,W,W,W,W,W,W,

    /* frame 6 */
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,W,W,
    W,W,W,W,W,W,B,B,
    W,W,W,W,W,W,B,B,
};


/******************************************************************************
 *				cube
 ******************************************************************************
 */

static pfGeoSet *cube( void )
{
    pfVec3		*scoords;
    ushort 		*sindex;
    pfVec4	        *colors;
    pfVec2		*tcoords;
    ushort		*tindex;

    pfGeoSet	*gset;
    void 		*arena;

    int i, j,  c;

static float v[][3] = {
    { -1., -1., -1. },
    {  1., -1., -1. },
    {  1.,  1., -1. },
    { -1.,  1., -1. },
    { -1., -1.,  1. },
    {  1., -1.,  1. },
    {  1.,  1.,  1. },
    { -1.,  1.,  1. }
};

static int con[][4] = {
    { 0, 1, 2, 3 },
    { 2, 6, 5, 1 },
    { 4, 5, 6, 7 },
    { 7, 3, 0, 4 },
    { 7, 6, 2, 3 },
    { 0, 1, 5, 4 }
};

    arena = pfGetSharedArena();

    scoords = (pfVec3 *)pfMalloc( 8 * sizeof( pfVec3 ), arena  );
    sindex  = (ushort *)pfMalloc( 24 * sizeof( ushort ), arena );
    colors  = (pfVec4 *)pfMalloc( 4 * sizeof( pfVec4 ), arena  );
    tcoords = (pfVec2 *)pfMalloc( 4 * sizeof( pfVec2 ), arena ); 
    tindex  = (ushort *)pfMalloc( 24 * sizeof( ushort ), arena );

    for( i = 0; i < 8; i++ )
	    PFSET_VEC3(scoords[i], v[i][0], v[i][1], v[i][2] );

    c = 0;
    for( i = 0; i < 6; i++ )
	    for( j = 0; j < 4; j++ )
	    {
		    sindex[c++] = con[i][j];
	    }
    
    PFSET_VEC4(colors[0], 1, 1, 1, 1 ); 
    PFSET_VEC4(colors[1], 1, 0, 0, 1 ); 
    PFSET_VEC4(colors[2], 0, 1, 0, 1 ); 
    PFSET_VEC4(colors[3], 0, 0, 1, 0 ); 
    
    PFSET_VEC2(tcoords[0], 0.0, 0.0 ); 
    PFSET_VEC2(tcoords[1], 1.0, 0.0 ); 
    PFSET_VEC2(tcoords[2], 1.0, 1.0 ); 
    PFSET_VEC2(tcoords[3], 0.0, 1.0 ); 
    
    c = 0;
    for( i = 0; i < 6; i++ )
	    for( j = 0; j < 4; j++ )
		    tindex[c++] = (ushort)j;

    gset = pfNewGSet(arena);
    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, scoords, sindex);
    pfGSetAttr(gset, PFGS_TEXCOORD2, PFGS_PER_VERTEX, tcoords, tindex);
    pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, colors, tindex);
    pfGSetPrimType(gset, PFGS_QUADS);
    pfGSetNumPrims(gset, 6);
    
    return gset;
}