Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

Composite.c

From Higher Intellect Vintage Wiki
Revision as of 10:07, 1 September 2020 by Netfreak (talk | contribs) (Created page with "<pre> /*********************************************************************** ** ** composite.c ** ** This example program demonstrates the use of DMbuffers to ** do simple c...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
/***********************************************************************
**
** composite.c
**
** This example program demonstrates the use of DMbuffers to
** do simple compositing.  It does "picture in picture", using two
** live video inputs.
**
** Two video streams are captured.  The first one is captured in
** "pbuffer" format so that it can be attached to a GLXDMbuffer and
** used as a drawable with GLX.  The second stream is captured (as a
** texture) and drawn as a "picture in picture" on top of an image
** from the first stream.
**
** The resulting composite image is sent to video out.
**
**
** Copyright 1996, Silicon Graphics, Inc.
** ALL RIGHTS RESERVED
**
** UNPUBLISHED -- Rights reserved under the copyright laws of the United
** States.   Use of a copyright notice is precautionary only and does not
** imply publication or disclosure.
**
** U.S. GOVERNMENT RESTRICTED RIGHTS LEGEND:
** Use, duplication or disclosure by the Government is subject to restrictions
** as set forth in FAR 52.227.19(c)(2) or subparagraph (c)(1)(ii) of the Rights
** in Technical Data and Computer Software clause at DFARS 252.227-7013 and*or
** in similar or successor clauses in the FAR, or the DOD or NASA FAR
** Supplement.  Contractor*manufacturer is Silicon Graphics, Inc.,
** 2011 N. Shoreline Blvd. Mountain View, CA 94039-7311.
**
** THE CONTENT OF THIS WORK CONTAINS CONFIDENTIAL AND PROPRIETARY
** INFORMATION OF SILICON GRAPHICS, INC. ANY DUPLICATION, MODIFICATION,
** DISTRIBUTION, OR DISCLOSURE IN ANY FORM, IN WHOLE, OR IN PART, IS STRICTLY
** PROHIBITED WITHOUT THE PRIOR EXPRESS WRITTEN PERMISSION OF SILICON
** GRAPHICS, INC.
**
***********************************************************************/

#include <dmedia/dm_buffer.h>
#include <dmedia/dm_image.h>
#include <dmedia/vl.h>
#include <dmedia/vl_mvp.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>

#include <math.h>
#include <unistd.h>

#define CAMERA_Z	1000

#define MY_WIDTH	640
#define MY_HEIGHT	480

#define MY_TEX_WIDTH	512
#define MY_TEX_HEIGHT	256

#define MY_VL_PACKING	VL_PACKING_ARGB_1555
#define MY_VL_FORMAT	VL_FORMAT_RGB
#define MY_VL_RATE	30
#define MY_VL_TIMING	VL_TIMING_525_SQ_PIX

/**********************************************************************
*
* This is just a handy little routine for debugging.
*
**********************************************************************/

void PrintParams( const DMparams* p, int indent )
{
    int len = dmParamsGetNumElems( p );
    int i;
    int j;
    
    for ( i = 0;  i < len;  i++ ) {
	const char* name = dmParamsGetElem    ( p, i );
	DMparamtype type = dmParamsGetElemType( p, i );
	
	for ( j = 0;  j < indent;  j++ ) {
	    printf( " " );
	}

	printf( "%8s: ", name );
	switch( type ) 
	    {
	    case DM_TYPE_ENUM:
		printf( "%d", dmParamsGetEnum( p, name ) );
		break;
	    case DM_TYPE_INT:
		printf( "%d", dmParamsGetInt( p, name ) );
		break;
	    case DM_TYPE_STRING:
		printf( "%s", dmParamsGetString( p, name ) );
		break;
	    case DM_TYPE_FLOAT:
		printf( "%f", dmParamsGetFloat( p, name ) );
		break;
	    case DM_TYPE_FRACTION:
		{
		    DMfraction f;
		    f = dmParamsGetFract( p, name );
		    printf( "%d/%d", f.numerator, f.denominator );
		}
		break;
	    case DM_TYPE_PARAMS:
		PrintParams( dmParamsGetParams( p, name ), indent + 4 );
		break;
	    case DM_TYPE_ENUM_ARRAY:
		{
		    int i;
		    const DMenumarray* array = dmParamsGetEnumArray( p, name );
		    for ( i = 0;  i < array->elemCount;  i++ ) {
			printf( "%d ", array->elems[i] );
		    }
		}
		break;
	    case DM_TYPE_INT_ARRAY:
		{
		    int i;
		    const DMintarray* array = dmParamsGetIntArray( p, name );
		    for ( i = 0;  i < array->elemCount;  i++ ) {
			printf( "%d ", array->elems[i] );
		    }
		}
		break;
	    case DM_TYPE_STRING_ARRAY:
		{
		    int i;
		    const DMstringarray* array = 
			dmParamsGetStringArray( p, name );
		    for ( i = 0;  i < array->elemCount;  i++ ) {
			printf( "%s ", array->elems[i] );
		    }
		}
		break;
	    case DM_TYPE_FLOAT_ARRAY:
		{
		    int i;
		    const DMfloatarray* array = 
			dmParamsGetFloatArray( p, name );
		    for ( i = 0;  i < array->elemCount;  i++ ) {
			printf( "%f ", array->elems[i] );
		    }
		}
		break;
	    case DM_TYPE_FRACTION_ARRAY:
		{
		    int i;
		    const DMfractionarray* array = 
			dmParamsGetFractArray( p, name );
		    for ( i = 0;  i < array->elemCount;  i++ ) {
			printf( "%d/%d ", 
			        array->elems[i].numerator,
			        array->elems[i].denominator );
		    }
		}
		break;
	    case DM_TYPE_INT_RANGE:
		{
		    const DMintrange* range = dmParamsGetIntRange( p, name );
		    printf( "%d ... %d", range->low, range->high );
		}
		break;
	    case DM_TYPE_FLOAT_RANGE:
		{
		    const DMfloatrange* range = 
			dmParamsGetFloatRange( p, name );
		    printf( "%f ... %f", range->low, range->high );
		}
		break;
	    case DM_TYPE_FRACTION_RANGE:
		{
		    const DMfractionrange* range = 
			dmParamsGetFractRange( p, name );
		    printf( "%d/%d ... %d/%d", 
			    range->low.numerator, 
			    range->low.denominator, 
			    range->high.numerator,
			    range->high.denominator );
		}
		break;
	    defualt:
		printf( "UNKNOWN TYPE" );
	    }
	printf( "\n" );
    }
}

/**********************************************************************
*
* Global Variables
*
**********************************************************************/

/********
*
* theDisplay
*
********/

Display* theDisplay;

/********
*
* theVideoServer
*
* This is that handle that is used to access the video library.
*
********/

VLServer theVideoServer = NULL;

/**********************************************************************
*
* Utility Functions
*
**********************************************************************/

/********
*
* CHECK
*
* This is a simple error-checking macro that just exits the program if
* anything goes wrong.
*
********/

#define CHECK(condition,message)					\
    {									\
	if ( ! ( condition ) )						\
	{								\
	    fprintf( stderr, "Error at line %d of %s:\n",		\
		     __LINE__, __FILE__ );				\
	    fprintf( stderr, "    %s\n", message );			\
	    exit( 1 );							\
	}								\
    }

/********
*
* CHECK_VL
*
* This is similar to CHECK, but also prints a VL error message.
*
********/

#define CHECK_VL(condition,message)					\
    {									\
	if ( ! ( condition ) )						\
	{								\
	    fprintf( stderr, "Error at line %d of %s:\n",		\
		     __LINE__, __FILE__ );				\
	    fprintf( stderr, "    %s\n", message );			\
	    fprintf( stderr, "    VL error message: %s\n",		\
		     vlStrError( vlGetErrno() ) );			\
	    exit( 1 );							\
	}								\
    }

/********
*
* CHECK_DM
*
* This is similar to CHECK, but also prints a DM error message.
*
********/

#define CHECK_DM(condition,message)					\
    {									\
	if ( ! ( condition ) )						\
	{								\
	    const char* msg;						\
	    int errornum;						\
	    char detail[DM_MAX_ERROR_DETAIL];				\
	    fprintf( stderr, "Error at line %d of %s:\n",		\
		     __LINE__, __FILE__ );				\
	    fprintf( stderr, "    %s\n", message );			\
	    msg = dmGetError( &errornum, detail );			\
	    fprintf( stderr, "    DM error message: %s\n", detail );	\
	    exit( 1 );							\
	}								\
    }

/********
*
* getControlName
*
********/

const char* getControlName(
    VLPath path,
    VLNode node,
    VLControlType type
    )
{
    static char name[VL_NAME_SIZE];
    VLControlInfo* info;
    
    info = vlGetControlInfo( theVideoServer, path, node, type );
    CHECK_VL( info != NULL, "vlGetControlInfo failed" );
    
    strncpy( name, info->name, VL_NAME_SIZE );
    
    vlFreeControlInfo( theVideoServer, info );
    
    return name;
}

/********
*
* setIntControl
*
********/

void setIntControl(
    VLPath path,
    VLNode node,
    VLControlType type,
    int newValue
    )
{
    VLControlValue val;
    int vlstatus;
    
    val.intVal = newValue;
    vlstatus = vlSetControl( theVideoServer, path, node, type, &val );

#ifdef DEBUG
    printf( "Setting value %s: %d\n",
	    getControlName( path, node, type ),
	    val.intVal );
#endif
    
    if ( vlstatus != 0 && vlGetErrno() == VLValueOutOfRange )
    {
	vlstatus = vlGetControl( theVideoServer, path, node, type, &val );
	CHECK_VL( vlstatus == 0, "Could not get control" );
	printf( "Actual value for %s: %d\n",
		getControlName( path, node, type ), val.intVal );
    }
    else 
    {
	CHECK_VL( vlstatus == 0, "Could not set control" );
    }
}

/********
*
* setFractControl
*
********/

void setFractControl(
    VLPath path,
    VLNode node,
    VLControlType type,
    int numerator,
    int denominator
    )
{
    VLControlValue val;
    int vlstatus;
    
    val.fractVal.numerator   = numerator;
    val.fractVal.denominator = denominator;

#ifdef DEBUG
    printf( "Setting value %s: %d/%d\n",
	    getControlName( path, node, type ),
	    val.fractVal.numerator,
	    val.fractVal.denominator );
#endif
    
    vlstatus = vlSetControl( theVideoServer, path, node, type, &val );

    if ( vlstatus != 0 && vlGetErrno() == VLValueOutOfRange )
    {
	vlstatus = vlGetControl( theVideoServer, path, node, type, &val );
	CHECK_VL( vlstatus == 0, "Could not get control" );
	printf( "Actual value for %s: %d/%d\n",
		getControlName( path, node, type ),
		val.fractVal.numerator,
		val.fractVal.denominator );
    }
    else 
    {
	CHECK_VL( vlstatus == 0, "Could not set control" );
    }
}

/********
*
* setXYControl
*
********/

void setXYControl(
    VLPath path,
    VLNode node,
    VLControlType type,
    int x,
    int y
    )
{
    VLControlValue val;
    int vlstatus;
    
    val.xyVal.x = x;
    val.xyVal.y = y;

#ifdef DEBUG
    printf( "Setting value %s: %d %d\n",
	    getControlName( path, node, type ),
	    val.xyVal.x,
	    val.xyVal.y );
#endif
    
    vlstatus = vlSetControl( theVideoServer, path, node, type, &val );

    if ( vlstatus != 0 && vlGetErrno() == VLValueOutOfRange )
    {
	vlstatus = vlGetControl( theVideoServer, path, node, type, &val );
	CHECK_VL( vlstatus == 0, "Could not get control" );
	printf( "Actual value for %s: %d %d\n",
		getControlName( path, node, type ),
		val.xyVal.x,
		val.xyVal.y );
    }
    else 
    {
	CHECK_VL( vlstatus == 0, "Could not set control" );
	/* Make sure the VL was not lying: */
	vlstatus = vlGetControl( theVideoServer, path, node, type, &val );
	if ( val.xyVal.x != x || val.xyVal.y != y )
	{
	    printf( "XXX Actual value for %s: %d %d\n",
		    getControlName( path, node, type ),
		    val.xyVal.x,
		    val.xyVal.y );
	}
    }
}

/********
*
* getXYControl
*
********/

void getXYControl(
    VLPath path,
    VLNode node,
    VLControlType type,
    int* returnX,
    int* returnY
    )
{
    VLControlValue val;
    int vlstatus;
    
    vlstatus = vlGetControl( theVideoServer, path, node, type, &val );
    CHECK_VL( vlstatus == 0, "Could not get control" );
    
    *returnX = val.xyVal.x;
    *returnY = val.xyVal.y;
}

/********
*
* theErrorFlag
*
********/

static DMboolean theErrorFlag = DM_FALSE;

/********
*
* theOldHandler
*
********/

static int (*theOldHandler) ( Display*, XErrorEvent* ) = NULL;

/********
*
* errorHandler
*
********/

static int errorHandler(
    Display* display,
    XErrorEvent* event
    )
{
    theErrorFlag = DM_TRUE;
    (*theOldHandler)( display, event );
    return 0; /* return value is ignored */
}
    
/********
*
* installXErrorHandler
*
********/

void installXErrorHandler(
    void
    )
{
    /*
     * This is probably overly paranoid.  It will cause any errors
     * that are in the pipe already to use the old handler.
     */

    XSync( theDisplay, DM_FALSE );
    
    /*
     * Clear the flag.  It will get set if an error occurs.
     */
    
    theErrorFlag = DM_FALSE;

    /*
     * Install our error handler and save the old one.
     */

    theOldHandler = XSetErrorHandler( errorHandler );
}

/********
*
* removeXErrorHandler
*
********/

DMstatus removeXErrorHandler(
    void
    )
{
    /*
     * Force any errors out of the pipe.
     */

    XSync( theDisplay, DM_FALSE );
    
    /*
     * Replace the old error handler.
     */

    XSetErrorHandler( theOldHandler );
    
    /*
     * Did an error happen?
     */

    if ( theErrorFlag )
    {
	return DM_FAILURE;
    }
    else
    {
	return DM_SUCCESS;
    }
}

/**********************************************************************
*
* Video Input Paths
*
**********************************************************************/

/********
*
* setupVideoInput
*
********/

void setupVideoInput(
    int sourceNodeNumber,
    int layout,
    int zoomWidth,
    int zoomHeight,
    int width,
    int height,
    VLPath* returnPath,
    VLNode* returnNode
    )
{
    VLNode 	   source;
    VLNode 	   drain;
    VLPath 	   path;
    VLControlValue value;
    int vlstatus;
    
    /*
     * Create the two nodes.
     */

    source = vlGetNode( theVideoServer, VL_SRC, VL_VIDEO, sourceNodeNumber );
    CHECK_VL( source != -1, "Could not create video source node." )
    drain = vlGetNode( theVideoServer, VL_DRN, VL_MEM, VL_ANY );
    CHECK_VL( drain != -1, "Could not create video drain node." );
    
    /*
     * Create the path.
     */

    path = vlCreatePath( theVideoServer, VL_ANY, source, drain );
    CHECK_VL( path != -1, "Could not create video path." );
    vlstatus = vlSetupPaths( theVideoServer, &path, 1, VL_SHARE, VL_SHARE );
    CHECK_VL( vlstatus == 0, "Could not set up video path." );
    
    /*
     * Set the controls on the path for the correct format.  The
     * important one for this example is the layout parameter.
     * VL_LAYOUT_GRAPHICS generates images in the right format so that
     * the buffer can be used as a render area.
     * VL_LAYOUT_MIPMAP generated mipmapped images for use as textures.
     */

    setIntControl  ( path, source, VL_TIMING,   MY_VL_TIMING );

    /*
     * Set the ZOOMSIZE and SIZE.  These must be set before setting
     * MIPMAP, but after setting the timing.
     */

    setIntControl  ( path, drain,  VL_PACKING,       MY_VL_PACKING );
    setIntControl  ( path, drain,  VL_CAP_TYPE,      VL_CAPTURE_INTERLEAVED );
    setIntControl  ( path, drain,  VL_FORMAT,        MY_VL_FORMAT );
    setXYControl   ( path, drain,  VL_MVP_ZOOMSIZE,  zoomWidth, zoomHeight );
    setXYControl   ( path, drain,  VL_SIZE,          width, height );
    setFractControl( path, drain,  VL_RATE,          MY_VL_RATE, 1 );
    setIntControl  ( path, drain,  VL_LAYOUT,        layout );
    
    *returnPath = path;
    *returnNode = drain;
}

/********
*
* getVideoPoolConstraints
*
********/

void getVideoPoolConstraints(
    DMparams* poolParams,
    VLPath path,
    VLNode node
    )
{
    int vlstatus;
    int prevBuffCount;
    int videoBuffCount;
    
    /*
     * Because this program has multiple paths in operation at the
     * same time, the buffer counts for the paths need to be summed.
     * By default, though, vlDMPoolGetParams assumes that multiple
     * paths will not be used simultaneously, so it just increases the
     * buffer count if it is not already enough for the one path.
     */

    prevBuffCount = dmParamsGetInt( poolParams, DM_BUFFER_COUNT );
    dmParamsSetInt( poolParams, DM_BUFFER_COUNT, 0 );
    
    vlstatus = vlDMPoolGetParams( theVideoServer, path, node, poolParams );
    CHECK_VL( vlstatus == 0, "Could not get video pool parameters" );

    videoBuffCount = dmParamsGetInt( poolParams, DM_BUFFER_COUNT );
    dmParamsSetInt( poolParams, DM_BUFFER_COUNT, prevBuffCount + videoBuffCount );


}

/********
*
* startVideoInput
*
********/

void startVideoInput(
    VLPath path,
    VLNode drain,
    DMbufferpool pool
    )
{
    int vlstatus;
    
    /*
     * Tell the video library where to get the memory to store the
     * incoming frames.
     */

    vlstatus = vlDMPoolRegister( theVideoServer, path, drain, pool );
    CHECK_VL( vlstatus == 0, "Could not register pool with input path" );
    
    /*
     * Start the transfer.
     */

    vlstatus = vlBeginTransfer( theVideoServer, path, 0, NULL );
    CHECK_VL( vlstatus == 0, "Could not start video input transfer" );
}

/********
*
* getOneInputFrame
*
********/

DMbuffer getOneInputFrame(
    const char* pathName,
    VLPath path,
    stamp_t* msc
    )
{
    VLEvent event;
    DMbuffer buffer;
    int vlstatus;
    
    /*
     * Get the next event.
     */
    
    vlstatus = vlEventRecv( theVideoServer, path, &event );
    while ( vlstatus == -1 && vlGetErrno() == VLAgain )
    {
	sginap( 1 );
	vlstatus = vlEventRecv( theVideoServer, path, &event );
    }
    
    CHECK( vlstatus == 0, 
	   "Could not get video input event" );
    CHECK( event.reason == VLTransferComplete,
	   "Unexpected video event received" );
    
    /*
     * Get the buffer associated with the event.
     */

    vlstatus = vlEventToDMBuffer( &event, &buffer );
    CHECK( vlstatus == 0, "Video event does not have a buffer" );
    
    /*
     * Verify the MSC.
     */
    
    {
	USTMSCpair pair;
	pair = dmBufferGetUSTMSCpair( buffer );
	if ( *msc != 0 && pair.msc != *msc + 1 )
	{
	    printf( "skipped a frame: %s\n", pathName );
	}
	*msc = pair.msc;
    }
	    
    return buffer;
}

/********
*
* frameTooEarly
*
********/

DMboolean frameTooEarly(
    DMbuffer frame,
    DMbuffer otherFrame
    )
{
    USTMSCpair pair1 = dmBufferGetUSTMSCpair( frame );
    USTMSCpair pair2 = dmBufferGetUSTMSCpair( otherFrame );
    
    const stamp_t slop = 20000000;   /* 20ms */

    return pair1.ust + slop < pair2.ust;
}

/**********************************************************************
*
* Video Output Path
*
**********************************************************************/

/********
*
* setupVideoOutput
*
********/

void setupVideoOutput(
    VLPath* returnPath,
    VLNode* returnNode
    )
{
    VLNode 	   source;
    VLNode 	   drain;
    VLPath 	   path;
    VLControlValue value;
    int vlstatus;
    
    /*
     * Create the two nodes.
     */

    source = vlGetNode( theVideoServer, VL_SRC, VL_MEM, VL_ANY );
    CHECK_VL( source != -1, "Could not create video source node." )
    drain = vlGetNode( theVideoServer, VL_DRN, VL_VIDEO, VL_ANY );
    CHECK_VL( drain != -1, "Could not create video drain node." );
    
    /*
     * Create the path.
     */

    path = vlCreatePath( theVideoServer, VL_ANY, source, drain );
    CHECK_VL( path != -1, "Could not create video path." );
    vlstatus = vlSetupPaths( theVideoServer, &path, 1, VL_SHARE, VL_SHARE );
    CHECK_VL( vlstatus == 0, "Could not set up video path." );
    
    /*
     * Set the controls on the path for the correct format.  The
     * important one for this example is VL_LAYOUT_GRAPHICS, which
     * specifies that the frame sent for video output will be coming
     * from OpenGL.
     */

    setIntControl  ( path, source, VL_PACKING,  MY_VL_PACKING );
    setIntControl  ( path, source, VL_CAP_TYPE, VL_CAPTURE_INTERLEAVED );
    setFractControl( path, source, VL_RATE,     MY_VL_RATE, 1 );
    setIntControl  ( path, source, VL_FORMAT,   MY_VL_FORMAT );
    setIntControl  ( path, source, VL_LAYOUT,   VL_LAYOUT_GRAPHICS );
    setIntControl  ( path, drain,  VL_TIMING,   MY_VL_TIMING );
    setXYControl   ( path, source, VL_SIZE,     MY_WIDTH, MY_HEIGHT );
    
    *returnPath = path;
    *returnNode = source;
}

/********
*
* startVideoOutput
*
********/

void startVideoOutput(
    VLPath path
    )
{
    int vlstatus;
    
    /*
     * Start the transfer.
     */

    vlstatus = vlBeginTransfer( theVideoServer, path, 0, NULL );
    CHECK_VL( vlstatus == 0, "Could not start video input transfer" );
}

/********
*
* putOneOutputFrame
*
********/

void putOneOutputFrame(
    VLPath path,
    DMbuffer frame
    )
{
    int vlstatus;
    
    vlstatus = vlDMBufferSend( theVideoServer, path, frame );
    CHECK( vlstatus == 0, "Could not send video frame" );
}

/**********************************************************************
*
* Graphics
*
**********************************************************************/

#define ARRAY_COUNT(a)    (sizeof(a)/sizeof(a[0]))

static void printFBConfig(
    GLXFBConfigSGIX config
    )
{
    static struct { int attrib; const char* name; } attribs [] = {
	{ GLX_BUFFER_SIZE, "GLX_BUFFER_SIZE" },
	{ GLX_LEVEL, "GLX_LEVEL" },
	{ GLX_DOUBLEBUFFER, "GLX_DOUBLEBUFFER" },
	{ GLX_AUX_BUFFERS, "GLX_AUX_BUFFERS" },
	{ GLX_RED_SIZE, "GLX_RED_SIZE" },
	{ GLX_GREEN_SIZE, "GLX_GREEN_SIZE" },
	{ GLX_BLUE_SIZE, "GLX_BLUE_SIZE" },
	{ GLX_ALPHA_SIZE, "GLX_ALPHA_SIZE" },
	{ GLX_DEPTH_SIZE, "GLX_DEPTH_SIZE" },
	{ GLX_DRAWABLE_TYPE_SGIX, "GLX_DRAWABLE_TYPE_SGIX" },
	{ GLX_RENDER_TYPE_SGIX, "GLX_RENDER_TYPE_SGIX" },
	{ GLX_X_RENDERABLE_SGIX, "GLX_X_RENDERABLE_SGIX" },
	{ GLX_MAX_PBUFFER_WIDTH_SGIX, "GLX_MAX_PBUFFER_WIDTH_SGIX" },
	{ GLX_MAX_PBUFFER_HEIGHT_SGIX, "GLX_MAX_PBUFFER_HEIGHT_SGIX"},
    };
    int i;
    
    for ( i = 0;  i < ARRAY_COUNT( attribs );  i++ )
    {
	int value;
	int errorCode = glXGetFBConfigAttribSGIX(
	    theDisplay, config, attribs[i].attrib, &value );
	CHECK( errorCode == 0, "glXGetFBConfigAttribSGIX failed" );
	printf( "    %24s = %d\n", attribs[i].name, value );
    }
}

/********
*
* getFBConfig
*
********/

GLXFBConfigSGIX getFBConfig(
    void
    )
{
    int i;
    
    /*
     * Find a frame buffer configuration that suits our needs: it must
     * work with both the pbuffers and the window, and must have 5551 pixels,
     * because that is the only format that can be used to capture
     * mipmapped video.
     */

    GLXFBConfigSGIX config = NULL;
    int params[] = { 
	GLX_RENDER_TYPE_SGIX, 	GLX_RGBA_BIT_SGIX,
	GLX_DRAWABLE_TYPE_SGIX,	GLX_PBUFFER_BIT_SGIX,
	GLX_ALPHA_SIZE,		1,
	GLX_RED_SIZE, 		5,
	GLX_GREEN_SIZE, 	5,
	GLX_BLUE_SIZE, 		5,
	GLX_DEPTH_SIZE,		24,
	GLX_X_RENDERABLE_SGIX,	False,
	(int)None,
    };
    int configCount;
    GLXFBConfigSGIX* configs = 
	glXChooseFBConfigSGIX(
	    theDisplay,
	    DefaultScreen(theDisplay),
	    params,
	    &configCount
	    );
    CHECK( configs != NULL || configCount != 0,
	   "Could not get FBConfig" );
	
    for ( i = 0;  i < configCount;  i++ ) 
    {
	int value;
	
	glXGetFBConfigAttribSGIX(theDisplay, configs[i], GLX_ALPHA_SIZE, &value);
	if ( value != 1 )  continue;
	glXGetFBConfigAttribSGIX(theDisplay, configs[i], GLX_RED_SIZE, &value);
	if ( value != 5 )  continue;
	glXGetFBConfigAttribSGIX(theDisplay, configs[i], GLX_GREEN_SIZE, &value);
	if ( value != 5 )  continue;
	glXGetFBConfigAttribSGIX(theDisplay, configs[i], GLX_BLUE_SIZE, &value);
	if ( value != 5 )  continue;
	
	break;
    }

    if ( i != configCount )
    {
	config = configs[i];
    }
    
    CHECK( config != NULL, "No 5551 FBconfigs were found" );

    XFree( configs );
    
    printFBConfig( config );

    return config;
}
    
/********
*
* setupDrawable
*
********/

void setupDrawable(
    int width,
    int height,
    GLXPbufferSGIX* returnDrawable
    )
{
    GLXPbufferSGIX drawable;
    int attrib[] = {
	GLX_PRESERVED_CONTENTS_SGIX,    GL_TRUE,
        GLX_DIGITAL_MEDIA_PBUFFER_SGIX, GL_TRUE,
	None};
    
    /*
     * Find a frame buffer configuration that suits our needs.
     */

    GLXFBConfigSGIX config = getFBConfig();

    /*
     * Create the GLXPbufferSGIX.
     */

    printf( "Drawable size: %d x %d\n", width, height );

    installXErrorHandler();
    drawable = glXCreateGLXPbufferSGIX(
	theDisplay, config, width, height, attrib);
    CHECK( removeXErrorHandler() == DM_SUCCESS && drawable != None,
	   "Could not create pbuffer" );
    
    /*
     * All done.
     */

    *returnDrawable = drawable;
}

/********
*
* getGraphicsPoolConstraints
*
********/

void getGraphicsPoolConstraints(
    DMparams* imageFormat,
    DMparams* poolParams
    )
{
    DMstatus status;
    
    status = dmBufferGetGLPoolParams( imageFormat, poolParams );
    CHECK_DM( status == DM_SUCCESS, "Could not get graphics pool params" );

    status = dmParamsSetInt( poolParams, DM_BUFFER_COUNT,
			     dmParamsGetInt( poolParams, DM_BUFFER_COUNT ) + 2 ); 
    CHECK_DM( status == DM_SUCCESS, "dmParamsSetInt failed" );

}

/********
*
* setupTexture
*
********/

void setupTexture(
    void
    )
{
    /*
     * Before the mipmapped texture can be copied from video, there
     * must already be a texture there to copy into.
     */

    int w = MY_TEX_WIDTH;
    int h = MY_TEX_HEIGHT;
    int level = 0;
    void* black = calloc( w * h, 4 );
    
    level = 0;
    while ( 1 )
    {
	glTexImage2D(
	    GL_TEXTURE_2D,
	    level,
	    GL_RGB5_A1_EXT,	/* number of components */
	    w, h,
	    0,			/* border */
	    GL_RGBA,
	    GL_UNSIGNED_BYTE,
	    black
	    );
	printf( "%d: %d x %d\n", level, w, h );
	
	if ( w == 1 && h == 1 )
	{
	    break;
	}
	
	if ( w > 1 )   w /= 2;
	if ( h > 1 )   h /= 2;
	level++;
    }
    
    free( black );
}

/********
*
* startGraphics
*
********/

void startGraphics(
    GLXPbufferSGIX drawable,
    DMparams*	   drawableFormat,
    GLXPbufferSGIX readable,
    DMparams*      readableFormat,
    DMbufferpool   pool1,
    DMbufferpool   pool2,
    GLXContext*    returnContext
    )
{
    GLXFBConfigSGIX config;
    GLXContext context;
    int ok;
    
    /*
     * Get the FB configuration to use.
     */

    config = getFBConfig();
    
    /*
     * Create the graphics context.
     */

    installXErrorHandler();
    context = glXCreateContextWithConfigSGIX(
	theDisplay,
	config,
	GLX_RGBA_TYPE_SGIX,
	NULL,		
	GL_TRUE		
	);
    CHECK( removeXErrorHandler() == DM_SUCCESS && context != NULL,
	   "Could not create graphics context" );
    
    /*
     * The two pbuffers must have associated DMbuffer before they can
     * be made current.  (The will be associated with real video
     * frames before any rendering happens.)
     */
    
    {
	DMbuffer buffer;
	CHECK( dmBufferAllocate( pool1, &buffer ) == DM_SUCCESS,
	       "could not create dummy buffer" );
  
	installXErrorHandler();
	ok = glXAssociateDMPbufferSGIX( theDisplay, drawable,
					drawableFormat, buffer);
	CHECK( removeXErrorHandler() == DM_SUCCESS && ok,
	       "Could not associate DMbuffer with pbuffer" );
    }
    
    {
	DMbuffer buffer;
	CHECK( dmBufferAllocate( pool2, &buffer ) == DM_SUCCESS,
	       "could not create dummy buffer" );
  
	installXErrorHandler();
	ok = glXAssociateDMPbufferSGIX( theDisplay, readable,
					readableFormat, buffer);
	CHECK( removeXErrorHandler() == DM_SUCCESS && ok,
	       "Could not associate DMbuffer with pbuffer" );
    }
    
    /*
     * Because of a bug in OpenGL, dmpbuffers must be made current as
     * a drawable before being used as a readable.
     */

    installXErrorHandler();
    ok = glXMakeCurrent( theDisplay, readable, context );
    CHECK( removeXErrorHandler() == DM_SUCCESS && ok,
	   "glXMakeCurrent failed" );
    
    
    /*
     * Make it the current context.
     * DM Pbuffers must have associated DMbuffers before
     * they can be made current.
     */

    installXErrorHandler();
    ok = glXMakeCurrentReadSGI( theDisplay, drawable, readable, context );
    CHECK( removeXErrorHandler() == DM_SUCCESS && ok,
	   "glXMakeCurrent failed" );
    
    /*
     * Set up the OpenGL transforms.
     */

    glViewport( 0, 0, MY_WIDTH, MY_HEIGHT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glFrustum(
	-MY_WIDTH / 2.0, MY_WIDTH / 2.0,
	-MY_HEIGHT / 2.0, MY_HEIGHT / 2.0,
	CAMERA_Z - MY_WIDTH / 2.0,  CAMERA_Z + MY_WIDTH / 2.0 
	);
    glMatrixMode( GL_MODELVIEW );
    glTranslatef( 0.0, 0.0, -CAMERA_Z );

    glEnable( GL_TEXTURE_2D );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    
    glEnable( GL_DEPTH_TEST );
    
    /*
     * Load a black texture.  Video images will be loaded as
     * subtextures. 
     * XXX This is only until the mipmap/dmbuffer/OpenGL stuff works.
     */
    
    setupTexture();

    /*
     * Turn on the mipmap extension so that OpenGL will use the
     * mipmaps created by video.
     */

    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
    
    *returnContext = context;
}

/********
*
* drawFace
*
********/

void drawFace(
    Bool borders,
    float z
    )
{
    if ( borders )
    {
	glDisable( GL_TEXTURE_2D );
	glBegin( GL_QUADS );
	{
	    glTexCoord2f( 0.0, 0.0 ); glVertex3i( -160, -160, z );
	    glTexCoord2f( 0.0, 1.0 ); glVertex3i( -160, -120, z );
	    glTexCoord2f( 1.0, 1.0 ); glVertex3i(  160, -120, z );
	    glTexCoord2f( 1.0, 0.0 ); glVertex3i(  160, -160, z );

	    glTexCoord2f( 0.0, 0.0 ); glVertex3i( -160,  120, z );
	    glTexCoord2f( 0.0, 1.0 ); glVertex3i( -160,  160, z );
	    glTexCoord2f( 1.0, 1.0 ); glVertex3i(  160,  160, z );
	    glTexCoord2f( 1.0, 0.0 ); glVertex3i(  160,  120, z );
	}
	glEnd();
    }
    
    glEnable( GL_TEXTURE_2D );
    glBegin( GL_QUADS );
    {
	glTexCoord2f( 0.0, 0.0 ); glVertex3i( -160, -120, z );
	glTexCoord2f( 0.0, 1.0 ); glVertex3i( -160,  120, z );
	glTexCoord2f( 1.0, 1.0 ); glVertex3i(  160,  120, z );
	glTexCoord2f( 1.0, 0.0 ); glVertex3i(  160, -120, z );
    }
    glEnd();
    
}

/********
*
* drawCube
*
********/

void drawCube(
    void
    )
{
    glColor4f( 0.5, 0.5, 0.5, 1.0 );
    
    /* front */
    drawFace( False, 160 );
    
    /* right */
    glPushMatrix();
    glRotatef( 90.0, 0.0, 1.0, 0.0 );
    drawFace( False, 160 );
    glPopMatrix();
    
    /* left */
    glPushMatrix();
    glRotatef( -90.0, 0.0, 1.0, 0.0 );
    drawFace( False, 160 );
    glPopMatrix();
    
    /* back */
    glPushMatrix();
    glRotatef( 180.0, 0.0, 1.0, 0.0 );
    drawFace( False, 160 );
    glPopMatrix();
    
    /* top */
    glPushMatrix();
    glRotatef( -90.0, 1.0, 0.0, 0.0 );
    drawFace( True, 120 );
    glPopMatrix();
    
    /* bottom */
    glPushMatrix();
    glRotatef( 90.0, 1.0, 0.0, 0.0 );
    drawFace( True, 120 );
    glPopMatrix();
}

/********
*
* renderOneFrame
*
********/

void renderOneFrame(
    stamp_t  msc,
    DMbuffer frame1,
    DMbuffer frame2
    )
{
    static int counter = 0;
   
    /*
     * Load the texture.  This call to "copy" the texture actually
     * just changes the texture object to point at this dmbuffer
     * because:
     *    1) the entire texture is copied.
     *    2) the format of the dmbuffer is the same as the texture.
     */

    glCopyTexSubImage2DEXT(
	GL_TEXTURE_2D, 
	0, 			/* level */
	0, 0,			/* x, y offset into target */
	0, 0, 			/* lower-left corner of copied rect */
	MY_TEX_WIDTH, MY_TEX_HEIGHT   /* size of copied rect */
	);
    
    /*
     * The depth buffer must be cleared.  It is not part of the
     * DMbuffer, so is retained from frame to frame.
     */

    glClear( GL_DEPTH_BUFFER_BIT );
    
    /*
     * Animate the cube.
     */
    
    {
	float count = ( (float) (counter++) );
	float angleX = count / 2;
	float angleY = count / M_PI;
	float scale  = ( cosf( count / 300.0 ) + 2.0 ) / 2.5;

	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
	glRotatef( angleX, 1.0, 0.0, 0.0 );
	glRotatef( angleY, 0.0, 1.0, 0.0 );
	glScalef( scale, scale, scale );
    }
    
    /*
     * Draw a cube using the texture.
     */

    drawCube();
    
    /*
     * Restore the transform
     */

    glPopMatrix();
}

/**********************************************************************
*
* Top-Level Control
*
**********************************************************************/

/********
*
* processOneFrame
*
********/

void processOneFrame(
    DMparams*		path1Format,
    VLPath		path1,
    VLNode		drain1,
    stamp_t*		path1MSC,
    DMparams*		path2Format,
    VLPath		path2,
    VLNode		drain2,
    stamp_t*		path2MSC,
    GLXPbufferSGIX 	drawable,
    GLXPbufferSGIX 	readable,
    VLPath		path3,
    VLNode		source3,
    int			bufferSize1,
    int			bufferSize2
    )
{
    DMbuffer frame1;
    DMbuffer frame2;
    Bool ok;
    
    /*
     * Get a frame from each of the input paths.
     */

    frame1 = getOneInputFrame( "path 1", path1, path1MSC );
    frame2 = getOneInputFrame( "path 2", path2, path2MSC );
    
    /*
     * Make sure that they are in sync.
     */

    while ( frameTooEarly( frame1, frame2 ) )
    {
	printf( "path1 frame was too early.\n" );
	dmBufferFree( frame1 );
	frame1 = getOneInputFrame( "path 1", path1, path1MSC );
    }
    while ( frameTooEarly( frame2, frame1 ) )
    {
	printf( "path2 frame was too early.\n" );
	dmBufferFree( frame2 );
	frame2 = getOneInputFrame( "path 2", path2, path2MSC );
    }
    
#ifdef DEBUG
    printf( "path1 MSC = %lld    path2 MSC = %lld\n", *path1MSC, *path2MSC );
#endif
    
    /*
     * Bind them to the drawable and readable so that we can get at
     * them from OpenGL.
     */

    ok = glXAssociateDMPbufferSGIX( theDisplay, drawable, path1Format, frame1);
    CHECK( ok, "Could not associate path1 DMbuffer with pbuffer" );
    
    ok = glXAssociateDMPbufferSGIX( theDisplay, readable, path2Format, frame2);
    CHECK( ok, "Could not associate path2 DMbuffer with pbuffer" );

    /*
     * Do the graphics.
     */

    renderOneFrame( *path1MSC, frame1, frame2 );
    
    /*
     * Make sure the graphics are complete before we pass the output
     * frame to video.
     */

    glFinish();

    /*
     * Send the processed frame out.
     */

    putOneOutputFrame( path3, frame1 );
    
    /*
     * We're done with both of the buffers now.  The video library
     * still has a handle to frame1 (from the output path), which will
     * be released when the frame goes out.  glX still has handles to
     * both (from the drawable and readable), which will be released
     * the next time this function is called.
     */

    dmBufferFree( frame1 );
    dmBufferFree( frame2 );
}

/********
*
* main
*
********/

int main(
    )
{
    DMstatus status;
    DMbufferpool pool1;		/* For paths 1 and 3 */
    DMbufferpool pool2;		/* For path 2 */
    
    int bufferSize1;
    int bufferSize2;
    
    VLPath path1;		/* The first input path (passed through) */
    VLNode drain1;
    
    VLPath path2;		/* The other input path (used as texture) */
    VLNode drain2;
    
    GLXPbufferSGIX drawable;	/* This is where the rendering happens */
    GLXPbufferSGIX readable;	/* This is the source for loading textures */
    GLXContext     context;
    
    VLPath path3;		/* The output path */
    VLNode source3;
    
    DMparams* path1Format;	/* this is also the format for path3 */
    DMparams* path2Format;
    
    int vlstatus;
    
    /*
     * Set up the parameter lists that describes the image formats.
     */
    
    status = dmParamsCreate( &path1Format );
    CHECK_DM( status == DM_SUCCESS, "Could not create dmparams" );
    status = dmSetImageDefaults( path1Format, MY_WIDTH, MY_HEIGHT,
				 DM_IMAGE_PACKING_XRGB1555 );
    CHECK_DM( status == DM_SUCCESS, "Could not set image param" );
    status = dmParamsSetEnum( path1Format, DM_IMAGE_LAYOUT,
			      DM_IMAGE_LAYOUT_GRAPHICS );
    CHECK_DM( status == DM_SUCCESS, "Could not set image param" );

    status = dmParamsCreate( &path2Format );
    CHECK_DM( status == DM_SUCCESS, "Could not create dmparams" );
    status = dmSetImageDefaults( path2Format, MY_TEX_WIDTH, MY_TEX_HEIGHT,
				 DM_IMAGE_PACKING_XRGB1555 );
    CHECK_DM( status == DM_SUCCESS, "Could not set image param" );
    status = dmParamsSetEnum( path2Format, DM_IMAGE_LAYOUT,
			      DM_IMAGE_LAYOUT_MIPMAP );
    CHECK_DM( status == DM_SUCCESS, "Could not set image param" );

    printf( "\n\nPath 1 format:\n" ); PrintParams( path1Format, 4 );
    printf( "\n\nPath 2 format:\n" ); PrintParams( path2Format, 4 );
    

    /* XXX What should the orientation be? */
    
    /*
     * Open the connection to the X server.
     */

    theDisplay = XOpenDisplay( ":0" );
    CHECK( theDisplay != NULL, "Could not open connection to local X server" );

    /*
     * Open the video library.
     */

    theVideoServer = vlOpenVideo( "" );
    CHECK_VL( theVideoServer != NULL,
	      "Could not open connection to video server" );

    /*
     * Set up the input and output video paths.
     */

    setupVideoInput( 
	VL_MVP_VIDEO_SOURCE_CAMERA,
	VL_LAYOUT_GRAPHICS,
	MY_WIDTH, MY_HEIGHT,   		/* ZOOMSIZE */
	MY_WIDTH, MY_HEIGHT,		/* SIZE */
	&path1, &drain1
	);

    setupVideoInput(
	VL_MVP_VIDEO_SOURCE_COMPOSITE,
	VL_LAYOUT_MIPMAP,		
	MY_TEX_WIDTH, MY_TEX_HEIGHT,	/* ZOOMSIZE */
	MY_TEX_WIDTH, MY_TEX_HEIGHT,	/* SIZE */
	&path2, &drain2 );
    
    setupVideoOutput( &path3, &source3 );
    
    /*
     * Set up the drawable and readable.
     */

    setupDrawable( MY_WIDTH,     MY_HEIGHT,     &drawable );
    setupDrawable( MY_TEX_WIDTH, MY_TEX_HEIGHT, &readable );
    
    /*
     * Create the memory pools that will be used to hold the images.
     */
    
    {
	DMparams* poolParams;
	status = dmParamsCreate( &poolParams );
	CHECK_DM( status == DM_SUCCESS, "Could not create pool params." );

	status = dmBufferSetPoolDefaults( poolParams, 0, 0,
					  DM_FALSE, DM_FALSE );
	CHECK_DM( status == DM_SUCCESS, "Could not set pool defaults." );
	
	printf( "\n\nDefault pool params:\n" ); PrintParams( poolParams, 4 );

	getVideoPoolConstraints( poolParams, path1, drain1  );
	printf( "\n\nAfter path1:\n" ); PrintParams( poolParams, 4 );
	
	getVideoPoolConstraints( poolParams, path3, source3 );
	printf( "\n\nAfter path3:\n" ); PrintParams( poolParams, 4 );
	
	getGraphicsPoolConstraints( path1Format, poolParams );
	printf( "\n\nAfter path1 graphics:\n" ); PrintParams( poolParams, 4 );

	printf( "\n\nFinal:\n" ); PrintParams( poolParams, 4 );
	
	status = dmBufferCreatePool( poolParams, &pool1 );
	CHECK_DM( status == DM_SUCCESS, "Could not create buffer pool" );
	
	bufferSize1 = dmParamsGetInt( poolParams, DM_BUFFER_SIZE );
	
	dmParamsDestroy( poolParams );
    }

    {
	DMparams* poolParams;
	status = dmParamsCreate( &poolParams );
	CHECK_DM( status == DM_SUCCESS, "Could not create pool params." );

	status = dmBufferSetPoolDefaults( poolParams, 0, 0,
					  DM_FALSE, DM_FALSE );
	CHECK_DM( status == DM_SUCCESS, "Could not set pool defaults." );
	
	printf( "\n\nDefault pool params:\n" ); PrintParams( poolParams, 4 );

	getVideoPoolConstraints( poolParams, path2, drain2  );
	printf( "\n\nAfter path2:\n" ); PrintParams( poolParams, 4 );
	
	getGraphicsPoolConstraints( path2Format, poolParams );
	printf( "\n\nAfter path2 graphics:\n" ); PrintParams( poolParams, 4 );
	
	printf( "\n\nFinal:\n" ); PrintParams( poolParams, 4 );
	
	status = dmBufferCreatePool( poolParams, &pool2 );
	CHECK_DM( status == DM_SUCCESS, "Could not create buffer pool" );
	
	bufferSize2 = dmParamsGetInt( poolParams, DM_BUFFER_SIZE );
	
	dmParamsDestroy( poolParams );
    }

    /*
     * Do one-time initialization of graphics.
     */

    startGraphics(
	drawable, path1Format,
	readable, path2Format,
	pool1, pool2,
	&context
	);
    
    /*
     * Start all three video paths.
     */

    printf( "Starting path 1...\n" );
    startVideoInput( path1, drain1, pool1 );
    
    printf( "Starting path 2...\n" );
    startVideoInput( path2, drain2, pool2 );
    
    printf( "Starting path 3...\n" );
    startVideoOutput( path3 );
    
    /*
     * This is where the work gets done.
     */
    
    {
	int frameNum;
	stamp_t path1MSC = 0;
	stamp_t path2MSC = 0;
	for ( frameNum = 0;  frameNum < 10000;  frameNum++ )
	{
	    processOneFrame( path1Format, path1, drain1, &path1MSC,
			     path2Format, path2, drain2, &path2MSC,
			     drawable, readable,
			     path3, source3,
			     bufferSize1, bufferSize2 );
	}
    }
    
    /*
     * Clean up.
     */

    glXMakeCurrentReadSGI(theDisplay, None, None, NULL); 
    glXDestroyContext( theDisplay, context );
    glXDestroyGLXPbufferSGIX( theDisplay, drawable );
    glXDestroyGLXPbufferSGIX( theDisplay, readable );
    vlstatus = vlDestroyPath( theVideoServer, path1 );
    CHECK_VL( vlstatus == 0, "vlDestroyPath failed" );
    vlstatus = vlDestroyPath( theVideoServer, path2 );
    CHECK_VL( vlstatus == 0, "vlDestroyPath failed" );
    vlstatus = vlDestroyPath( theVideoServer, path3 );
    CHECK_VL( vlstatus == 0, "vlDestroyPath failed" );
    vlCloseVideo( theVideoServer );
    
    return 0;
}