/*
 * OpenGL Functions
 * Authors: Markus Hadwiger
 *
 * ro_main.c
 *
 */


#include "stdafx.h"
#include "ext_pbuf.h"


// flags
#define __GL_ERRORS_CRITICAL



// opengl error code strings --------------------------------------------------
//
static char *gl_err_strings[] = {

	"GL_INVALID_ENUM ",
	"GL_INVALID_VALUE ",
	"GL_INVALID_OPERATION ",
	"GL_STACK_OVERFLOW ",
	"GL_STACK_UNDERFLOW ",
	"GL_OUT_OF_MEMORY ",

#ifdef GL_VERSION_1_2

	"GL_TABLE_TOO_LARGE ",

#endif // GL_VERSION_1_2

};


int gl_err_strings_size = 7;

// query opengl error code and display message on error -----------------------
//
void GL_QueryError( const char *file, int line )
{
	ASSERT( file != NULL );

	GLenum glerr = glGetError();
	if ( glerr == GL_NO_ERROR ) {
		return;
	}

	#define MAX_ERR_LEN 255
	char errmsg[ MAX_ERR_LEN + 1 ];

	// retrieve all errors
	size_t errpos = 0;
	while ( glerr != GL_NO_ERROR ) {

		// error codes are consecutive
		int errindx = glerr - GL_INVALID_ENUM;
		ASSERT( errindx >= 0 );

		char *errstr = ( errindx < gl_err_strings_size ) ?
			gl_err_strings[ errindx ] : "UNKNOWN ";

		if ( ( MAX_ERR_LEN - errpos ) < strlen( errstr ) ) {
			errpos = -1;
			break;
		}

		strcpy( &errmsg[ errpos ], errstr );
		errpos += strlen( errstr );

		// get next error
		glerr = glGetError();
	}

	// end with ellipsis if error string too long
	if ( errpos == -1 ) {
		errmsg[ MAX_ERR_LEN - 3 ] = '.';
		errmsg[ MAX_ERR_LEN - 2 ] = '.';
		errmsg[ MAX_ERR_LEN - 1 ] = '.';
		errmsg[ MAX_ERR_LEN     ] = '\0';
	}

	char tmpstr[ MAX_ERR_LEN + 30 ];

#ifdef GL_ERRORS_CRITICAL

	sprintf( tmpstr, "OpenGL error: file %s line %d: %s\n", file, line, errmsg );

	PANIC( tmpstr );

#else // GL_ERRORS_CRITICAL

#ifdef SYSTEM_WIN32

	printf( tmpstr );
	OutputDebugString( tmpstr );

#endif // SYSTEM_WIN32

#endif // GL_ERRORS_CRITICAL

}


// read back rectangular region from specified buffer -------------------------
//
int GL_ReadBuffRegion( int buffid, char *dstbuff, ugrid_t srcx, ugrid_t srcy, dword width, dword height, dword stride )
{
	ASSERT( dstbuff != NULL );
	ASSERT( stride == width );

	// default: read into destination buffer
	char *readbuff = dstbuff;

	// select buffer
	GLenum read_format;
	GLenum read_type;

	if ( buffid == BUFFERID_COLOR ) {

		read_format	= GL_RGB;
		read_type	= GL_UNSIGNED_BYTE;

		// read from back buffer
		glReadBuffer( GL_BACK );
//		glReadBuffer( GL_FRONT );

		// read into temp buffer instead of destination buffer
		char *_tmpbuff = (char *) ALLOCMEM( width * height * 3 );
		if ( _tmpbuff == NULL ) {
			return FALSE;
		}
		readbuff = _tmpbuff;

	} else if ( buffid == BUFFERID_DEPTH ) {

		read_format	= GL_DEPTH_COMPONENT;
		read_type	= GL_FLOAT;

	} else {

		return FALSE;
	}

//	glPixelStorei( GL_PACK_SWAP_BYTES, GL_FALSE );
//	glPixelStorei( GL_PACK_LSB_FIRST, GL_FALSE ); // GL_TRUE
//	glPixelStorei( GL_PACK_ROW_LENGTH, 0 );
//	glPixelStorei( GL_PACK_SKIP_ROWS, 0 );
//	glPixelStorei( GL_PACK_SKIP_PIXELS, 0 );
//	glPixelStorei( GL_PACK_ALIGNMENT, 1 );

//	glReadPixels( srcx, srcy + height - 1, width, height,
//		read_format, read_type, readbuff );
	glReadPixels( srcx, srcy, width, height,
		read_format, read_type, readbuff );
//	GL_ERROR();

	if ( buffid == BUFFERID_COLOR ) {

		size_t scanlinelen = width * 3;

		char *pixread  = readbuff + ( height - 1 ) * scanlinelen;
		char *pixwrite = dstbuff;

		// flip the image upside down
		for ( unsigned int i = 0; i < height; i++ ) {

			memcpy( pixwrite, pixread, scanlinelen );

			pixwrite += scanlinelen;
			pixread  -= scanlinelen;
		}

		FREEMEM( readbuff );
	}

	return TRUE;
}


// info for an allocated pbuffer ----------------------------------------------
//
struct pbuf_s {

	dword		flags;			// PBUF_xx
	HPBUFFERARB	hpbuf;			// pbuffer handle
	HDC			hdc;			// pbuffer device context
	HGLRC		hglrc;			// pbuffer rendering context
	int			width;			// pbuffer width
	int			height;			// pbuffer height
	GLuint		texname;		// texture name if already bound at least once
	int			visibleid;		// id of corresponding visible context (window)
};

enum {

	PBUF_DEFAULT   = 0x0000,
	PBUF_SHAREDRC  = 0x0001,	// pbuffer uses a shared (non-exclusive) context
	PBUF_TEXVALID  = 0x0002,	// pbuffer has been used as texture at least once
	PBUF_TEXBIND   = 0x0004,	// pbuffer can be bound to texture without copying
	PBUF_TEXRECT   = 0x0008,	// texture is rectangular
	PBUF_TEXFLOAT  = 0x0010,	// texture is floating point
};


// ----------------------------------------------------------------------------
//
#define MAX_PBUFFERS			63
#define MAX_VISIBLE_CONTEXTS	32


// info for all allocated pbuffers --------------------------------------------
//
static int num_pbuffer_infos = 0;
static pbuf_s pbuffer_infos[ MAX_PBUFFERS + 1 ];	// pbuffer 0 is invalid


// device and rendering contexts of visible rendering windows -----------------
//
static HDC   visible_hdc[ MAX_VISIBLE_CONTEXTS ];
static HGLRC visible_hglrc[ MAX_VISIBLE_CONTEXTS ];
static int   visible_valid[ MAX_VISIBLE_CONTEXTS ];

static int   visible_id = 0;


// currently active pbuffer/context -------------------------------------------
//
static int	cur_active_context = 0;	// <=0: visible context >0: pbuffer


// initialize context variables from currently active window ------------------
//
int GL_InitPbufferHandling( int windowid )
{
	ASSERT( (dword)windowid < MAX_VISIBLE_CONTEXTS );
	ASSERT( !visible_valid[ windowid ] );

	/*
	if ( !ext_opengl_available.GLExt_pbuffer ||
		 !ext_opengl_available.GLExt_pixel_format ) {
		return FALSE;
	}
	*/

#ifdef SYSTEM_WIN32

	// get device and rendering contexts for current gl context
	visible_hdc[ windowid ]   = wglGetCurrentDC();
	visible_hglrc[ windowid ] = wglGetCurrentContext();

	ASSERT( visible_hdc[ windowid ] != NULL );
	ASSERT( visible_hglrc[ windowid ] != NULL );

	visible_valid[ windowid ] = TRUE;

#else // SYSTEM_WIN32

	return FALSE;

#endif // SYSTEM_WIN32

	// now the visible context is active by definition
	cur_active_context = -windowid;
	return TRUE;
}


// make a different visible context current -----------------------------------
//
int GL_ActivatePbufferWindow( int windowid )
{
	ASSERT( (dword)windowid < MAX_VISIBLE_CONTEXTS );
	ASSERT( visible_valid[ windowid ] );

	visible_id = windowid;

	return TRUE;
}


// create a new pbuffer (off-screen rendering buffer) -------------------------
//
int GL_CreatePbuffer( pbuffer_create_info_s *createinfo )
{
	ASSERT( createinfo != NULL );

	//ASSERT( ext_opengl_available.GLExt_pbuffer );
	//ASSERT( ext_opengl_available.GLExt_pixel_format );

	ASSERT( visible_valid[ visible_id ] );

	// prevent overflowing the pbuffer array
	if ( num_pbuffer_infos >= MAX_PBUFFERS ) {
		return 0;
	}

	// ids must be reused, otherwise destroying pbuffers
	// will not prevent overflowing the array
	for ( int pbid = 1; pbid <= num_pbuffer_infos; pbid++ ) {
		if ( pbuffer_infos[ pbid ].hpbuf == 0 ) {
			break;
		}
	}
	if ( pbid > num_pbuffer_infos ) {
		pbid = num_pbuffer_infos + 1;
	}

	// pbuffers that will be bound to textures must either be
	// power-of-two or have a rectangular texture target
	if ( ( createinfo->flags & PBCREATE_TEXBIND ) != 0 ) {
		if ( createinfo->tex_target != PBTARGET_TEX_RECT ) {
			if ( ( createinfo->width & ( createinfo->width - 1 ) ) != 0 ) {
				return 0;
			}
			if ( ( createinfo->height & ( createinfo->height - 1 ) ) != 0 ) {
				return 0;
			}
		}
	}

	// for later distinction of nvidia/ati hardware

	//int hwnvidia = ext_opengl_available.GLExt_register_combiners;
	//int hwati = ext_opengl_available.GLExt_fragment_shader;
	int hwnvidia = 0;
	int hwati = 1;

#ifdef SYSTEM_WIN32

	// specify desired pixel format attributes
	int attribs[ 65 ];
	int aindx = 0;

	attribs[ aindx++ ] = WGL_SUPPORT_OPENGL_ARB;
	attribs[ aindx++ ] = 1;
	attribs[ aindx++ ] = WGL_DRAW_TO_PBUFFER_ARB;
	attribs[ aindx++ ] = 1;
	attribs[ aindx++ ] = WGL_DOUBLE_BUFFER_ARB;
	attribs[ aindx++ ] = 0;

//	attribs[ aindx++ ] = WGL_PIXEL_TYPE_ARB;
//	attribs[ aindx++ ] = WGL_TYPE_RGBA_ARB;

//	attribs[ aindx++ ] = WGL_AUX_BUFFERS_ARB;
//	attribs[ aindx++ ] = 0;

	attribs[ aindx++ ] = WGL_RED_BITS_ARB;
	attribs[ aindx++ ] = createinfo->bits_red;
	attribs[ aindx++ ] = WGL_GREEN_BITS_ARB;
	attribs[ aindx++ ] = createinfo->bits_green;
	attribs[ aindx++ ] = WGL_BLUE_BITS_ARB;
	attribs[ aindx++ ] = createinfo->bits_blue;
	attribs[ aindx++ ] = WGL_ALPHA_BITS_ARB;
	attribs[ aindx++ ] = createinfo->bits_alpha;
	attribs[ aindx++ ] = WGL_DEPTH_BITS_ARB;
	attribs[ aindx++ ] = createinfo->bits_depth;
	attribs[ aindx++ ] = WGL_STENCIL_BITS_ARB;
	attribs[ aindx++ ] = createinfo->bits_stencil;

	// float pixel formats need special flags specified
	if ( createinfo->tex_format & PBFORMAT_IS_FLOAT ) {

		// special case for NVIDIA hardware
		if ( hwnvidia ) {

			// check for extension
			/*
			if ( !ext_opengl_available.GLExt_float_buffer ) {
				return 0;
			}
			*/

			// float targets only for rectangular textures
			if ( ( createinfo->flags & PBCREATE_TEXBIND ) != 0 ) {
				if ( createinfo->tex_target != PBTARGET_TEX_RECT ) {
					return 0;
				}
			}

			// set flag
			attribs[ aindx++ ] = WGL_FLOAT_COMPONENTS_NV;
			attribs[ aindx++ ] = 1;
		}

		// special case for ATI hardware
		if ( hwati ) {

			// check for extension
			/*
			if ( !ext_opengl_available.GLExt_pixel_format_float_ati ) {
				return 0;
			}
			*/

			// set pixel type to float
			attribs[ aindx++ ] = WGL_PIXEL_TYPE_ARB;
			if ( createinfo->tex_format == PBFORMAT_FLOAT_RGBA ) {
				attribs[ aindx++ ] = WGL_TYPE_RGBA_FLOAT_ATI;
			} else if ( createinfo->tex_format == PBFORMAT_FLOAT_RGB ) {
//				attribs[ aindx++ ] = WGL_TYPE_RGB_FLOAT_ATI;
				return 0;
			}
		}
	}

	// pixel format for render-to-texture pbuffer
	if ( ( createinfo->flags & PBCREATE_TEXBIND ) != 0 ) {

		// need render-to-texture extension
		/*
		if ( !ext_opengl_available.GLExt_render_texture ) {
			return 0;
		}
		*/

		// there are different pixel formats for
		// rectangular and power-of-two textures
		if ( createinfo->tex_target == PBTARGET_TEX_RECT ) {

			//NOTE:
			// render to rectangular texture is currently only
			// supported for NVIDIA cards.

			// need rectangular textures
			/*
			if ( !ext_opengl_available.GLExt_render_texture_rectangle ) {
				return 0;
			}
			*/

			switch ( createinfo->tex_format ) {
				case PBFORMAT_RGB:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV;
					attribs[ aindx++ ] = 1;
					break;
				case PBFORMAT_RGBA:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV;
					attribs[ aindx++ ] = 1;
					break;
				case PBFORMAT_FLOAT_R:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV;
					attribs[ aindx++ ] = 1;
					break;
				case PBFORMAT_FLOAT_RG:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV;
					attribs[ aindx++ ] = 1;
					break;
				case PBFORMAT_FLOAT_RGB:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV;
					attribs[ aindx++ ] = 1;
					break;
				case PBFORMAT_FLOAT_RGBA:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV;
					attribs[ aindx++ ] = 1;
					break;
				default:
					return 0;
			}

		} else {

			switch ( createinfo->tex_format ) {
				case PBFORMAT_RGB:
				case PBFORMAT_FLOAT_RGB:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RGB_ARB;
					attribs[ aindx++ ] = 1;
					break;
				case PBFORMAT_RGBA:
				case PBFORMAT_FLOAT_RGBA:
					attribs[ aindx++ ] = WGL_BIND_TO_TEXTURE_RGBA_ARB;
					attribs[ aindx++ ] = 1;
					break;
				default:
					return 0;
			}
		}
	}

	attribs[ aindx++ ] = 0;

	// choose pixel format (create list of formats)
	int formats[ 16 ];
	UINT numformats = 0;
	if ( !wglChoosePixelFormatARB( visible_hdc[ visible_id ], attribs, NULL, 16, formats, &numformats ) ) {
		//GetLastError();
		return 0;
	}
	if ( numformats == 0 ) {
		return 0;
	}

	// there might be more formats than fit into the array
	if ( numformats > 16 ) {
		numformats = 16;
	}

	// list of attributes to query for each pixel format in list
	aindx = 0;
	attribs[ aindx++ ] = WGL_SUPPORT_OPENGL_ARB;
	attribs[ aindx++ ] = WGL_ACCELERATION_ARB;
	attribs[ aindx++ ] = WGL_DRAW_TO_PBUFFER_ARB;
	attribs[ aindx++ ] = WGL_DOUBLE_BUFFER_ARB;
	attribs[ aindx++ ] = WGL_PIXEL_TYPE_ARB;
	attribs[ aindx++ ] = WGL_RED_BITS_ARB;
	attribs[ aindx++ ] = WGL_GREEN_BITS_ARB;
	attribs[ aindx++ ] = WGL_BLUE_BITS_ARB;
	attribs[ aindx++ ] = WGL_ALPHA_BITS_ARB;
	attribs[ aindx++ ] = WGL_DEPTH_BITS_ARB;
	attribs[ aindx++ ] = WGL_STENCIL_BITS_ARB;
	if ( hwnvidia ) {
		attribs[ aindx++ ] = WGL_FLOAT_COMPONENTS_NV;
	}

	// select pixel format from list
	int fid = formats[ 0 ];
	int accfound = FALSE;
	int queryvalues[ 20 ];
	for ( unsigned int findx = 0; findx < numformats; findx++ ) {

		if ( !wglGetPixelFormatAttribivARB( visible_hdc[ visible_id ], formats[ findx ],
				0, aindx, attribs, queryvalues ) ) {
			ASSERT( 0 );
		}
		// opengl must be supported (WGL_SUPPORT_OPENGL_ARB)
		if ( !queryvalues[ 0 ] ) {
			continue;
		}
		// pixel type (WGL_PIXEL_TYPE_ARB) must be rgba
		if ( ( createinfo->tex_format & PBFORMAT_IS_FLOAT ) && hwati ) {
			if ( queryvalues[ 4 ] != WGL_TYPE_RGBA_FLOAT_ATI ) {
				continue;
			}
		} else {
			if ( queryvalues[ 4 ] != WGL_TYPE_RGBA_ARB ) {
				continue;
			}
		}
		if ( ( createinfo->tex_format & PBFORMAT_IS_FLOAT ) && hwnvidia ) {
			// check WGL_FLOAT_COMPONENTS_NV flag
			if ( queryvalues[ 11 ] != 1 ) {
				continue;
			}
		}
		// we want a fully accelerated format (WGL_ACCELERATION_ARB)
		if ( queryvalues[ 1 ] == WGL_NO_ACCELERATION_ARB ) {
			continue;
		} else if ( queryvalues[ 1 ] == WGL_FULL_ACCELERATION_ARB ) {
			if ( !accfound ) {
				fid = formats[ findx ];
				accfound = TRUE;
			}
		} else if ( queryvalues[ 1 ] == WGL_GENERIC_ACCELERATION_ARB ) {
			if ( !accfound ) {
				fid = formats[ findx ];
			}
		}
	}

	// pbuffer attributes
	aindx = 0;

	// render-to-texture pbuffer?
	if ( ( createinfo->flags & PBCREATE_TEXBIND ) != 0 ) {

		// select texture binding format (i.e., the internal
		// format of the texture that will be used for binding)
		attribs[ aindx++ ] = WGL_TEXTURE_FORMAT_ARB;

		switch ( createinfo->tex_format ) {
			case PBFORMAT_RGB:
				attribs[ aindx++ ] = WGL_TEXTURE_RGB_ARB;
				break;
			case PBFORMAT_RGBA:
				attribs[ aindx++ ] = WGL_TEXTURE_RGBA_ARB;
				break;
			case PBFORMAT_FLOAT_R:
				if ( hwnvidia ) {
					attribs[ aindx++ ] = WGL_TEXTURE_FLOAT_R_NV;
				} else if ( hwati ) {
					return 0;
				}
				break;
			case PBFORMAT_FLOAT_RG:
				if ( hwnvidia ) {
					attribs[ aindx++ ] = WGL_TEXTURE_FLOAT_RG_NV;
				} else if ( hwati ) {
					return 0;
				}
				break;
			case PBFORMAT_FLOAT_RGB:
				if ( hwnvidia ) {
					attribs[ aindx++ ] = WGL_TEXTURE_FLOAT_RGB_NV;
				} else if ( hwati ) {
//					attribs[ aindx++ ] = WGL_TEXTURE_RGB_ARB;
					return 0;
				}
				break;
			case PBFORMAT_FLOAT_RGBA:
				if ( hwnvidia ) {
					attribs[ aindx++ ] = WGL_TEXTURE_FLOAT_RGBA_NV;
				} else if ( hwati ) {
					attribs[ aindx++ ] = WGL_TEXTURE_RGBA_ARB;
				}
				break;
		}

		// select texture binding target
		attribs[ aindx++ ] = WGL_TEXTURE_TARGET_ARB;

		switch ( createinfo->tex_target ) {
			case PBTARGET_TEX_1D:
				attribs[ aindx++ ] = WGL_TEXTURE_1D_ARB;
				break;
			case PBTARGET_TEX_2D:
				attribs[ aindx++ ] = WGL_TEXTURE_2D_ARB;
				break;
			case PBTARGET_TEX_RECT:
				attribs[ aindx++ ] = WGL_TEXTURE_RECTANGLE_NV;
				break;
			case PBTARGET_TEX_CUBE:
				attribs[ aindx++ ] = WGL_TEXTURE_CUBE_MAP_ARB;
				break;
		}

		// optionally specify mip mapping flag (only for 2d targets)
		if ( ( createinfo->flags & PBCREATE_TEXMIPMAP ) != 0 ) {
			ASSERT( createinfo->tex_target == PBTARGET_TEX_2D );
			attribs[ aindx++ ] = WGL_MIPMAP_TEXTURE_ARB;
			attribs[ aindx++ ] = 1;
		}
	}

	attribs[ aindx++ ] = 0;

	// create pbuffer
	HPBUFFERARB hpbuf = wglCreatePbufferARB( visible_hdc[ visible_id ], fid,
		createinfo->width, createinfo->height, attribs );
	if ( hpbuf == NULL ) {
		ASSERT( 0 );
	}

	// fetch pbuffer dc
	HDC hdcpbuf = wglGetPbufferDCARB( hpbuf );
	if ( hdcpbuf == NULL ) {
		ASSERT( 0 );
	}

	// create/share rendering context
	HGLRC hglrcpbuf = visible_hglrc[ visible_id ];

	if ( ( createinfo->flags & PBCREATE_SHAREDRC ) == 0 ) {

		// create a pbuffer-exclusive rendering context
		hglrcpbuf = wglCreateContext( hdcpbuf );
		if ( hglrcpbuf == NULL ) {
			ASSERT( 0 );
		}

		// share lists and textures with visible dc
		if ( !wglShareLists( visible_hglrc[ visible_id ], hglrcpbuf ) ) {
			ASSERT( 0 );
		}
	}

	// store pbuffer info
	pbuffer_infos[ pbid ].flags		= PBUF_DEFAULT;
	pbuffer_infos[ pbid ].hpbuf		= hpbuf;
	pbuffer_infos[ pbid ].hdc		= hdcpbuf;
	pbuffer_infos[ pbid ].hglrc		= hglrcpbuf;
	pbuffer_infos[ pbid ].width		= createinfo->width;
	pbuffer_infos[ pbid ].height	= createinfo->height;
	pbuffer_infos[ pbid ].visibleid	= visible_id;

	if ( ( createinfo->flags & PBCREATE_SHAREDRC ) != 0 ) {
		pbuffer_infos[ pbid ].flags |= PBUF_SHAREDRC;
	}
	if ( ( createinfo->flags & PBCREATE_TEXBIND ) != 0 ) {
		pbuffer_infos[ pbid ].flags |= PBUF_TEXBIND;
	}
	if ( ( createinfo->tex_target & PBTARGET_TEX_RECT ) != 0 ) {
		pbuffer_infos[ pbid ].flags |= PBUF_TEXRECT;
	}
	if ( ( createinfo->tex_format & PBFORMAT_IS_FLOAT ) != 0 ) {
		pbuffer_infos[ pbid ].flags |= PBUF_TEXFLOAT;
	}

	// avoid counting reused ids, and buffers not
	// actually created due to an error condition
	if ( pbid == num_pbuffer_infos + 1 ) {
		++num_pbuffer_infos;
	}

	// id is one-based
	return pbid;

#else // SYSTEM_WIN32

	// id 0 is invalid
	return 0;

#endif // SYSTEM_WIN32

}


// determine whether a pbuffer is still valid or has been lost ----------------
//
int GL_IsValidPbuffer( int pbid )
{
	ASSERT( ( pbid > 0 ) && ( pbid <= num_pbuffer_infos ) );

//	int lost = FALSE;
//	wglQueryPbufferARB( pbuffer_infos[ pbid ].hpbuf, WGL_PBUFFER_LOST_ARB, &lost );
//	if ( lost ) {
//		return FALSE;
//	}

	return TRUE;
}


// bind a pbuffer for rendering -----------------------------------------------
//
int GL_BindPbuffer( int pbid )
{
	ASSERT( ( pbid >= 0 ) && ( pbid <= num_pbuffer_infos ) );

	// pseudo id 0 refers to visible context
	if ( pbid == 0 ) {
		return GL_UnbindPbuffer();
	}

#ifdef SYSTEM_WIN32

//	if ( cur_active_context <= 0 ) {
//
//		// get device and rendering contexts for current visible gl context
//		int windowid = -cur_active_context;
//		visible_hdc[ windowid ]   = wglGetCurrentDC();
//		visible_hglrc[ windowid ] = wglGetCurrentContext();
//
//		ASSERT( visible_hdc[ windowid ] != NULL );
//		ASSERT( visible_hglrc[ windowid ] != NULL );
//	}

	// bind pbuffer context
	if ( !wglMakeCurrent( pbuffer_infos[ pbid ].hdc, pbuffer_infos[ pbid ].hglrc ) ) {
		DWORD err = GetLastError();
		ASSERT( 0 );
	}

//	if ( !wglMakeContextCurrentARB(
//		pbuffer_infos[ pbid ].hdc, pbuffer_infos[ pbid ].hdc,
//		pbuffer_infos[ pbid ].hglrc ) ) {

//		DWORD err = GetLastError();
//		ASSERT( 0 );
//	}

//	glDrawBuffer( GL_FRONT );
//	glReadBuffer( GL_FRONT );

#endif // SYSTEM_WIN32

	// now the pbuffer context is active
	cur_active_context = pbid;
	return TRUE;
}


// bind the visible buffer for rendering (unbinds the pbuffer) ----------------
//
int GL_UnbindPbuffer()
{
	ASSERT( visible_valid[ visible_id ] );

#ifdef SYSTEM_WIN32

	// bind visible context
	if ( !wglMakeCurrent( visible_hdc[ visible_id ], visible_hglrc[ visible_id ] ) ) {
		DWORD err = GetLastError();
		ASSERT( 0 );
	}

//	if ( !wglMakeContextCurrentARB( visible_hdc[ visible_id ], visible_hdc[ visible_id ], visible_hglrc[ visible_id ] ) ) {
//		DWORD err = GetLastError();
//		ASSERT( 0 );
//	}

//	glDrawBuffer( GL_FRONT );
//	glReadBuffer( GL_FRONT );

#endif // SYSTEM_WIN32

	// now the visible context is active
	cur_active_context = -visible_id;
	return TRUE;
}


// switch to another context (visible or pbuffer) for texture binding ---------
//
PRIVATE
void GL_SwitchTexBindContext( GLenum texunit, int pbid, int dstisbound )
{
	ASSERT( ( pbid >= 0 ) && ( pbid <= num_pbuffer_infos ) );

	// make other pbuffer current before binding the texture
	if ( !dstisbound ) {
		GL_BindPbuffer( pbid );
	}

	// make sure texture will be bound to desired unit
	/*
	if ( ext_opengl_available.GLExt_multitexture ) {
		glActiveTextureARB( texunit );
	}
	*/
}


// turn source pbuffer into a texture to use in destination pbuffer -----------
//
int GL_BindPbufferTexture( GLenum texunit, int pbid, int dstpbid, int dstisbound )
{
	ASSERT( ( pbid > 0 ) && ( pbid <= num_pbuffer_infos ) );
	ASSERT( ( dstpbid >= 0 ) && ( dstpbid <= num_pbuffer_infos ) );
//	ASSERT( pbid != dstpbid );

	//NOTE:
	// this function will automatically bind the destination pbuffer and
	// bind the texture in its context. it will stay bound on function exit.

	//NOTE:
	// if render-to-texture is not supported this function will bind a
	// temporary texture in the source pbuffer context on whatever
	// texture unit is currently active in that context and not restore
	// it afterwards.

#ifdef SYSTEM_WIN32

	if ( !GL_IsValidPbuffer( pbid ) ) {
		return FALSE;
	}

	// acquire texture object if not already done
	if ( ( pbuffer_infos[ pbid ].flags & PBUF_TEXVALID ) == 0 ) {
		glGenTextures( 1, &pbuffer_infos[ pbid ].texname );
	}

	GLint filter = GL_NEAREST; //GL_LINEAR
	GLint wrap   = GL_CLAMP; //GL_REPEAT

	// NV30 requires clamp to edge for floating point textures
	/*
	if ( ext_opengl_available.GLExt_register_combiners &&
		 ( pbuffer_infos[ pbid ].flags & PBUF_TEXFLOAT ) ) {
		ASSERT( ext_opengl_available.GLExt_texture_border_clamp );
		wrap = GL_CLAMP_TO_EDGE;
	}
	*/

	 // use render-to-texture if available
	if ( pbuffer_infos[ pbid ].flags & PBUF_TEXBIND ) {

		// switch context to destination
		GL_SwitchTexBindContext( texunit, dstpbid, dstisbound );

		// allow standard 2d and rectangular textures
		GLenum target = GL_TEXTURE_2D;
		if ( pbuffer_infos[ pbid ].flags & PBUF_TEXRECT ) {
//			ASSERT( ext_opengl_available.GLExt_texture_rectangle );
//			ASSERT( ext_opengl_available.GLExt_texture_rectangle ||
//					ext_opengl_available.GLExt_texture_rectangle_ext );
//			ASSERT( ext_opengl_available.GLExt_render_texture_rectangle );
			target = GL_TEXTURE_RECTANGLE_NV;
		} else {
//			ASSERT( ext_opengl_available.GLExt_render_texture );
		}

		// bind pbuffer to texture object
		glBindTexture( target, pbuffer_infos[ pbid ].texname );
		if ( ( pbuffer_infos[ pbid ].flags & PBUF_TEXVALID ) == 0 ) {

			glTexParameteri( target, GL_TEXTURE_MAG_FILTER, filter );
			glTexParameteri( target, GL_TEXTURE_MIN_FILTER, filter );

			glTexParameteri( target, GL_TEXTURE_WRAP_S, wrap );
			glTexParameteri( target, GL_TEXTURE_WRAP_T, wrap );
		}

		// bind source pbuffer as texture without actually copying it
		if ( !wglBindTexImageARB( pbuffer_infos[ pbid ].hpbuf, WGL_FRONT_LEFT_ARB ) ) {
			ASSERT( 0 );
		}

	} else {

		// make source pbuffer current
		GL_BindPbuffer( pbid );

		glReadBuffer( GL_FRONT );

		// allow standard 2d and rectangular textures
		GLenum target = GL_TEXTURE_2D;
		if ( pbuffer_infos[ pbid ].flags & PBUF_TEXRECT ) {
//			ASSERT( ext_opengl_available.GLExt_texture_rectangle );
//			ASSERT( ext_opengl_available.GLExt_texture_rectangle ||
//					ext_opengl_available.GLExt_texture_rectangle_ext );
			target = GL_TEXTURE_RECTANGLE_NV;
		}

		// copy pbuffer into texture
		glBindTexture( target, pbuffer_infos[ pbid ].texname );
		if ( ( pbuffer_infos[ pbid ].flags & PBUF_TEXVALID ) == 0 ) {

			glTexParameteri( target, GL_TEXTURE_MAG_FILTER, filter );
			glTexParameteri( target, GL_TEXTURE_MIN_FILTER, filter );

			glTexParameteri( target, GL_TEXTURE_WRAP_S, wrap );
			glTexParameteri( target, GL_TEXTURE_WRAP_T, wrap );

			// alloc texture data on first use
			glCopyTexImage2D( target, 0, GL_RGBA8, 0, 0,
				pbuffer_infos[ pbid ].width, pbuffer_infos[ pbid ].height, 0 );
			GL_ERROR();

		} else {

			// only replace texture data after first use
			glCopyTexSubImage2D( target, 0,
				0, 0, 0, 0,
				pbuffer_infos[ pbid ].width, pbuffer_infos[ pbid ].height );
			GL_ERROR();
		}

		// switch context to destination
		GL_SwitchTexBindContext( texunit, dstpbid, dstisbound );

		// also bind in destination context
		glBindTexture( target, pbuffer_infos[ pbid ].texname );
	}

	// must not be set earlier
	pbuffer_infos[ pbid ].flags |= PBUF_TEXVALID;

#endif // SYSTEM_WIN32

	return TRUE;
}


// release a pbuffer locked as a texture --------------------------------------
//
int GL_UnbindPbufferTexture( int pbid )
{
	ASSERT( ( pbid > 0 ) && ( pbid <= num_pbuffer_infos ) );

#ifdef SYSTEM_WIN32

//	if ( ext_opengl_available.GLExt_render_texture &&
//		 ( pbuffer_infos[ pbid ].flags & PBUF_TEXBIND ) ) {

		if ( !GL_IsValidPbuffer( pbid ) ) {
			return TRUE;
		}

//		ASSERT( ( pbuffer_infos[ pbid ].flags & PBUF_TEXVALID ) != 0 );
//		glBindTexture( GL_TEXTURE_2D, pbuffer_infos[ pbid ].texname );

		if ( !wglReleaseTexImageARB( pbuffer_infos[ pbid ].hpbuf, WGL_FRONT_LEFT_ARB ) ) {
			ASSERT( 0 );
		}
//	}

#endif // SYSTEM_WIN32

	return TRUE;
}


// destroy an exisiting pbuffer -----------------------------------------------
//
int GL_DestroyPbuffer( int pbid )
{
	ASSERT( ( pbid > 0 ) && ( pbid <= num_pbuffer_infos ) );

#ifdef SYSTEM_WIN32

	// delete context if it belongs to the pbuffer
	if ( ( pbuffer_infos[ pbid ].flags & PBUF_SHAREDRC ) == 0 ) {
		if ( !wglDeleteContext( pbuffer_infos[ pbid ].hglrc ) ) {
			ASSERT( 0 );
		}
	}

	if ( !wglReleasePbufferDCARB( pbuffer_infos[ pbid ].hpbuf, pbuffer_infos[ pbid ].hdc ) ) {
		ASSERT( 0 );
	}
	if ( !wglDestroyPbufferARB( pbuffer_infos[ pbid ].hpbuf ) ) {
		ASSERT( 0 );
	}

	// mark as free slot
	pbuffer_infos[ pbid ].hpbuf = 0;

#endif // SYSTEM_WIN32

	return TRUE;
}

